]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Rewrite large chunks of EAP-SIM so it behaves like the RFC says it should
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Tue, 12 Dec 2017 12:25:38 +0000 (12:25 +0000)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Tue, 12 Dec 2017 12:26:28 +0000 (12:26 +0000)
I don't know if this was written pre RFC 4186 but large parts of it don't follow the RFC.

src/modules/rlm_eap/types/rlm_eap_sim/eap_sim.h
src/modules/rlm_eap/types/rlm_eap_sim/rlm_eap_sim.c

index 6ddebbc37304019e16fad285f6ccec196c383409..3c5380bdbb7e2849d26cddfcd4ed9e9809830a68 100644 (file)
@@ -33,14 +33,33 @@ RCSIDH(rlm_eap_sim_eap_sim_h, "$Id$")
  * In server_start, we send a EAP-SIM Start message.
  */
 typedef enum {
-       EAP_SIM_SERVER_START            = 0,
-       EAP_SIM_SERVER_CHALLENGE        = 1,
-       EAP_SIM_SERVER_SUCCESS          = 10,
+       EAP_SIM_SERVER_START = 0,
+       EAP_SIM_SERVER_CHALLENGE,
+       EAP_SIM_SERVER_SUCCESS_NOTIFICATION,
+       EAP_SIM_SERVER_SUCCESS,
+       EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION,
+       EAP_SIM_SERVER_FAILURE,
        EAP_SIM_SERVER_MAX_STATES
 } eap_sim_server_state_t;
 
 typedef struct eap_aka_session {
-       eap_sim_server_state_t  state;          //!< Current session state.
-       fr_sim_keys_t           keys;           //!< Various EAP-AKA keys.
-       int                     sim_id;         //!< Packet ID. (replay protection)
+       eap_sim_server_state_t          state;                  //!< Current session state.
+       fr_sim_keys_t                   keys;                   //!< Various EAP-AKA keys.
+       fr_sim_id_req_type_t            id_req;                 //!< The type of identity we're requesting
+                                                               ///< or previously requested.
+
+       bool                            request_identity;       //!< Always send an identity request before a
+                                                               ///< challenge.
+
+       bool                            send_result_ind;        //!< Say that we would like to use protected result
+                                                               ///< indications (SIM-Notification-Success).
+
+       int                             sim_id;                 //!< Packet ID. (replay protection)
 } eap_sim_session_t;
+
+
+typedef struct {
+       char const                      *virtual_server;        //!< Virtual server for HLR integration.
+       bool                            request_identity;       //!< Whether we always request the identity of
+                                                               ///< the subscriber.
+} rlm_eap_sim_t;
index a4793d89e97a1c715f7db7f74ea1b0473451ab8c..a4d5b6783e6a59233356e89d68d1920e1407ea73 100644 (file)
@@ -39,6 +39,21 @@ RCSID("$Id$")
 #  define EAP_TLS_MPPE_KEY_LEN     32
 #endif
 
+FR_NAME_NUMBER const sim_state_table[] = {
+       { "START",                              EAP_SIM_SERVER_START                            },
+       { "CHALLENGE",                          EAP_SIM_SERVER_CHALLENGE                        },
+       { "SUCCESS-NOTIFICATION",               EAP_SIM_SERVER_SUCCESS_NOTIFICATION             },
+       { "SUCCESS",                            EAP_SIM_SERVER_SUCCESS                          },
+       { "GENERAL-FAILURE-NOTIFICATION",       EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION     },
+       { "FAILURE",                            EAP_SIM_SERVER_FAILURE                          },
+       { NULL }
+};
+
+static CONF_PARSER submodule_config[] = {
+       { FR_CONF_OFFSET("virtual_server", FR_TYPE_STRING, rlm_eap_sim_t, virtual_server) },
+       CONF_PARSER_TERMINATOR
+};
+
 /*
  *     build a reply to be sent.
  */
@@ -94,7 +109,7 @@ static int eap_sim_compose(eap_session_t *eap_session)
        return 0;
 }
 
-static int eap_sim_send_state(eap_session_t *eap_session)
+static int eap_sim_send_start(eap_session_t *eap_session)
 {
        REQUEST                 *request = eap_session->request;
        VALUE_PAIR              **vps, *vp;
@@ -130,16 +145,41 @@ static int eap_sim_send_state(eap_session_t *eap_session)
        vp->vp_uint32 = eap_sim_session->sim_id++;
        fr_pair_replace(vps, vp);
 
-       /* the ANY_ID attribute. We do not support re-auth or pseudonym */
-       MEM(vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_FULLAUTH_ID_REQ));
+       /*
+        *      Select the right type of identity request attribute
+        */
+       switch (eap_sim_session->id_req) {
+       case SIM_ANY_ID_REQ:
+               vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_ANY_ID_REQ);
+               break;
+
+       case SIM_PERMANENT_ID_REQ:
+               vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_PERMANENT_ID_REQ);
+               break;
+
+       case SIM_FULLAUTH_ID_REQ:
+               vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_FULLAUTH_ID_REQ);
+               break;
+
+       default:
+               rad_assert(0);
+       }
        vp->vp_bool = true;
-       fr_pair_add(vps, vp);
+       fr_pair_replace(vps, vp);
 
        /* the SUBTYPE, set to start. */
        vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_SUBTYPE);
        vp->vp_uint32 = EAP_SIM_START;
        fr_pair_replace(vps, vp);
 
+       /*
+        *      Encode the packet
+        */
+       if (eap_sim_compose(eap_session) < 0) {
+               fr_pair_list_free(&packet->vps);
+               return -1;
+       }
+
        return 0;
 }
 
@@ -164,91 +204,120 @@ static int eap_sim_send_challenge(eap_session_t *eap_session)
        static uint8_t          hmac_zero[16] = { 0x00 };
 
        REQUEST                 *request = eap_session->request;
-       eap_sim_session_t       *eap_sim_session;
-       VALUE_PAIR              **from_peer, **to_client, *vp;
+       eap_sim_session_t       *eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t);
+       VALUE_PAIR              **to_peer, *vp;
        RADIUS_PACKET           *packet;
+       fr_sim_vector_src_t     src = SIM_VECTOR_SRC_AUTO;
 
-       eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t);
        rad_assert(eap_session->request != NULL);
        rad_assert(eap_session->request->reply);
 
-       RDEBUG2("Sending SIM-Challenge");
-       eap_session->this_round->request->code = FR_EAP_CODE_REQUEST;
+       RDEBUG2("Acquiring GSM vector(s)");
+       if ((fr_sim_vector_gsm_from_attrs(eap_session, request->control, 0, &eap_sim_session->keys, &src) != 0) ||
+           (fr_sim_vector_gsm_from_attrs(eap_session, request->control, 1, &eap_sim_session->keys, &src) != 0) ||
+           (fr_sim_vector_gsm_from_attrs(eap_session, request->control, 2, &eap_sim_session->keys, &src) != 0)) {
+               REDEBUG("Failed retrieving SIM vectors");
+               return RLM_MODULE_FAIL;
+       }
 
        /*
-        *      from_peer is the data from the client but this is for non-protocol data here.
-        *      We should already have consumed any client originated data.
+        *      All set, calculate keys!
         */
-       from_peer = &eap_session->request->packet->vps;
+       fr_sim_crypto_kdf_0_gsm(&eap_sim_session->keys);
+       if (RDEBUG_ENABLED3) fr_sim_crypto_keys_log(request, &eap_sim_session->keys);
+
+       RDEBUG2("Sending SIM-Challenge");
+       eap_session->this_round->request->code = FR_EAP_CODE_REQUEST;
 
        /*
-        *      to_client is the data to the client
+        *      to_peer is the data to the client
         */
        packet = eap_session->request->reply;
-       to_client = &packet->vps;
+       to_peer = &packet->vps;
 
        /*
         *      Okay, we got the challenges! Put them into attributes.
         */
        MEM(vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_RAND));
        fr_pair_value_memcpy(vp, eap_sim_session->keys.gsm.vector[0].rand, SIM_VECTOR_GSM_RAND_SIZE);
-       fr_pair_add(to_client, vp);
+       fr_pair_add(to_peer, vp);
 
        MEM(vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_RAND));
        fr_pair_value_memcpy(vp, eap_sim_session->keys.gsm.vector[1].rand, SIM_VECTOR_GSM_RAND_SIZE);
-       fr_pair_add(to_client, vp);
+       fr_pair_add(to_peer, vp);
 
        MEM(vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_RAND));
        fr_pair_value_memcpy(vp, eap_sim_session->keys.gsm.vector[2].rand, SIM_VECTOR_GSM_RAND_SIZE);
-       fr_pair_add(to_client, vp);
+       fr_pair_add(to_peer, vp);
 
        /*
-        *      Set the EAP_ID - new value
+        *      Set subtype to challenge.
         */
-       vp = fr_pair_afrom_child_num(packet, fr_dict_root(fr_dict_internal), FR_EAP_ID);
-       vp->vp_uint32 = eap_sim_session->sim_id++;
-       fr_pair_replace(to_client, vp);
+       vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_SUBTYPE);
+       vp->vp_uint32 = EAP_SIM_CHALLENGE;
+       fr_pair_replace(to_peer, vp);
 
        /*
-        *      Use the SIM identity, if available
+        *      Indicate we'd like to use protected success messages
         */
-       vp = fr_pair_find_by_child_num(*from_peer, dict_sim_root, FR_EAP_SIM_IDENTITY, TAG_ANY);
-       if (vp) {
-               MEM(eap_sim_session->keys.identity = (uint8_t *)talloc_bstrndup(eap_sim_session,
-                                                                               vp->vp_strvalue, vp->vp_length));
-               eap_sim_session->keys.identity_len = vp->vp_length;
-       /*
-        *      Make a copy of the identity
-        */
-       } else {
-               if (eap_sim_session->keys.identity) talloc_const_free(eap_sim_session->keys.identity);
-
-               eap_sim_session->keys.identity_len = talloc_array_length(eap_session->identity) - 1;
-               MEM(eap_sim_session->keys.identity = talloc_memdup(eap_sim_session, eap_session->identity,
-                                                                  eap_sim_session->keys.identity_len));
+       if (eap_sim_session->send_result_ind) {
+               MEM(vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_RESULT_IND));
+               vp->vp_bool = true;
+               fr_pair_replace(to_peer, vp);
        }
 
-       /*
-        *      All set, calculate keys!
-        */
-       fr_sim_crypto_kdf_0_gsm(&eap_sim_session->keys);
-
-       if (RDEBUG_ENABLED3) fr_sim_crypto_keys_log(request, &eap_sim_session->keys);
-
        /*
         *      Need to include an AT_MAC attribute so that it will get
         *      calculated.
         */
        vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_MAC);
        fr_pair_value_memcpy(vp, hmac_zero, sizeof(hmac_zero));
-       fr_pair_replace(to_client, vp);
+       fr_pair_replace(to_peer, vp);
 
        /*
-        *      Set subtype to challenge.
+        *      Encode the packet
+        */
+       if (eap_sim_compose(eap_session) < 0) {
+               fr_pair_list_free(&packet->vps);
+               return -1;
+       }
+
+       return 0;
+}
+
+/** Send a success notification
+ *
+ */
+static int eap_sim_send_eap_success_notification(eap_session_t *eap_session)
+{
+       REQUEST         *request = eap_session->request;
+       RADIUS_PACKET   *packet = eap_session->request->reply;
+       fr_cursor_t     cursor;
+       VALUE_PAIR      *vp;
+
+       RDEBUG2("Sending SIM-Notification (Success)");
+       eap_session->this_round->request->code = FR_EAP_CODE_REQUEST;
+
+       fr_cursor_init(&cursor, &packet->vps);
+
+       /*
+        *      Set the subtype to notification
         */
        vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_SUBTYPE);
-       vp->vp_uint32 = EAP_SIM_CHALLENGE;
-       fr_pair_replace(to_client, vp);
+       vp->vp_uint32 = FR_EAP_SIM_SUBTYPE_VALUE_SIM_NOTIFICATION;
+       fr_cursor_append(&cursor, vp);
+
+       vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_NOTIFICATION);
+       vp->vp_uint32 = FR_EAP_SIM_NOTIFICATION_VALUE_SUCCESS;
+       fr_cursor_append(&cursor, vp);
+
+       /*
+        *      Encode the packet
+        */
+       if (eap_sim_compose(eap_session) < 0) {
+               fr_pair_list_free(&packet->vps);
+               return -1;
+       }
 
        return 0;
 }
@@ -258,28 +327,16 @@ static int eap_sim_send_challenge(eap_session_t *eap_session)
  * The only work to be done is the add the appropriate SEND/RECV
  * radius attributes derived from the MSK.
  */
-static int eap_sim_send_success(eap_session_t *eap_session)
+static int eap_sim_send_eap_success(eap_session_t *eap_session)
 {
-       uint8_t                 *p;
-
        REQUEST                 *request = eap_session->request;
-       eap_sim_session_t       *eap_sim_session;
-       VALUE_PAIR              *vp;
-       RADIUS_PACKET           *packet;
+       uint8_t                 *p;
+       eap_sim_session_t       *eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t);
 
        RDEBUG2("Sending SIM-Success");
        eap_session->this_round->request->code = FR_EAP_CODE_SUCCESS;
        eap_session->finished = true;
 
-       /* to_client is the data to the client. */
-       packet = eap_session->request->reply;
-       eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t);
-
-       /* set the EAP_ID - new value */
-       vp = fr_pair_afrom_child_num(packet, fr_dict_root(fr_dict_internal), FR_EAP_ID);
-       vp->vp_uint32 = eap_sim_session->sim_id++;
-       fr_pair_replace(&eap_session->request->reply->vps, vp);
-
        p = eap_sim_session->keys.msk;
        eap_add_reply(eap_session->request, "MS-MPPE-Recv-Key", p, EAP_TLS_MPPE_KEY_LEN);
        p += EAP_TLS_MPPE_KEY_LEN;
@@ -288,49 +345,129 @@ static int eap_sim_send_success(eap_session_t *eap_session)
        return 0;
 }
 
+/** Send a failure message
+ *
+ */
+static int eap_sim_send_eap_failure_notification(eap_session_t *eap_session)
+{
+       REQUEST         *request = eap_session->request;
+       RADIUS_PACKET   *packet = eap_session->request->reply;
+       fr_cursor_t     cursor;
+       VALUE_PAIR      *vp;
+
+       RDEBUG2("Sending SIM-Notification (General-Failure)");
+       eap_session->this_round->request->code = FR_EAP_CODE_REQUEST;
+
+       fr_cursor_init(&cursor, &packet->vps);
+
+       /*
+        *      Set the subtype to notification
+        */
+       vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_SUBTYPE);
+       vp->vp_uint32 = FR_EAP_SIM_SUBTYPE_VALUE_SIM_NOTIFICATION;
+       fr_cursor_append(&cursor, vp);
+
+       vp = fr_pair_afrom_child_num(packet, dict_sim_root, FR_EAP_SIM_NOTIFICATION);
+       vp->vp_uint32 = FR_EAP_SIM_NOTIFICATION_VALUE_GENERAL_FAILURE;
+       fr_cursor_append(&cursor, vp);
+
+       /*
+        *      Encode the packet
+        */
+       if (eap_sim_compose(eap_session) < 0) {
+               fr_pair_list_free(&packet->vps);
+               return -1;
+       }
+
+       return 0;
+}
+
+static int eap_sim_send_eap_failure(eap_session_t *eap_session)
+{
+       REQUEST         *request = eap_session->request;
+
+       RDEBUG2("Sending EAP-Failure");
+
+       eap_session->this_round->request->code = FR_EAP_CODE_FAILURE;
+       eap_session->finished = true;
+
+       return 0;
+}
+
 /** Run the server state machine
  *
  */
-static void eap_sim_state_enter(eap_session_t *eap_session,
-                               eap_sim_session_t *eap_sim_session,
-                               eap_sim_server_state_t new_state)
+static void eap_sim_state_enter(eap_session_t *eap_session, eap_sim_server_state_t new_state)
 {
+       REQUEST                 *request = eap_session->request;
+       eap_sim_session_t       *eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t);
+
+       if (new_state != eap_sim_session->state) {
+               RDEBUG2("Changed state %s -> %s",
+                       fr_int2str(sim_state_table, eap_sim_session->state, "<unknown>"),
+                       fr_int2str(sim_state_table, new_state, "<unknown>"));
+               eap_sim_session->state = new_state;
+       } else {
+               RDEBUG2("Reentering state %s",
+                       fr_int2str(sim_state_table, eap_sim_session->state, "<unknown>"));
+       }
+
        switch (new_state) {
        /*
-        *      Send the EAP-SIM Start message, listing the versions that we support.
+        *      Send our version list
         */
        case EAP_SIM_SERVER_START:
-               eap_sim_send_state(eap_session);
-               eap_sim_compose(eap_session);           /* Encode SIM TLVs */
+               if (eap_sim_send_start(eap_session) < 0) {
+               notify_failure:
+                       eap_sim_state_enter(eap_session, EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION);
+                       return;
+               }
                break;
+
        /*
         *      Send the EAP-SIM Challenge message.
         */
        case EAP_SIM_SERVER_CHALLENGE:
-               eap_sim_send_challenge(eap_session);
-               eap_sim_compose(eap_session);           /* Encode SIM TLVs */
+               if (eap_sim_send_challenge(eap_session) < 0) goto notify_failure;
                break;
 
        /*
-        *      Send the EAP Success message
+        *      Sent a protected success notification
+        */
+       case EAP_SIM_SERVER_SUCCESS_NOTIFICATION:
+               if (eap_sim_send_eap_success_notification(eap_session) < 0) goto notify_failure;
+               break;
+
+       /*
+        *      Send the EAP Success message (we're done)
         */
        case EAP_SIM_SERVER_SUCCESS:
-               eap_sim_send_success(eap_session);
+               if (eap_sim_send_eap_success(eap_session) < 0) goto notify_failure;
+               return;
 
-               break;
        /*
-        *      Nothing to do for this transition.
+        *      Send a general failure notification
         */
+       case EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION:
+               if (eap_sim_send_eap_failure_notification(eap_session) < 0) {   /* Fallback to EAP-Failure */
+                       eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE);
+               }
+               return;
+
+       /*
+        *      Send an EAP-Failure (we're done)
+        */
+       case EAP_SIM_SERVER_FAILURE:
+               eap_sim_send_eap_failure(eap_session);
+               return;
+
        default:
-               eap_sim_compose(eap_session);           /* Encode SIM TLVs */
-               break;
+               rad_assert(0);  /* Invalid transition */
+               eap_sim_state_enter(eap_session, EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION);
+               return;
        }
-
-       eap_sim_session->state = new_state;
 }
 
-static rlm_rcode_t CC_HINT(nonnull) mod_process(void *instance, eap_session_t *eap_session);
-
 /** Process an EAP-Sim/Response/Start
  *
  * Verify that client chose a version, and provided a NONCE_MT,
@@ -343,21 +480,27 @@ static int process_eap_sim_start(eap_session_t *eap_session, VALUE_PAIR *vps)
        VALUE_PAIR              *nonce_vp, *selected_version_vp;
        eap_sim_session_t       *eap_sim_session;
        uint16_t                eap_sim_version;
+       VALUE_PAIR              *id;
+       fr_sim_id_type_t        type = SIM_ID_TYPE_UNKNOWN;
+       fr_sim_method_hint_t    method = SIM_METHOD_HINT_UNKNOWN;
 
        eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t);
 
+       /*
+        *      For fullauth We require both the NONCE_MT
+        *      and SELECTED_VERSION from the peer, else
+        *      the packet is invalid.
+        */
        nonce_vp = fr_pair_find_by_child_num(vps, dict_sim_root, FR_EAP_SIM_NONCE_MT, TAG_ANY);
        selected_version_vp = fr_pair_find_by_child_num(vps, dict_sim_root, FR_EAP_SIM_SELECTED_VERSION, TAG_ANY);
        if (!nonce_vp || !selected_version_vp) {
-               RDEBUG2("Client did not select a version and send a NONCE");
-               eap_sim_state_enter(eap_session, eap_sim_session, EAP_SIM_SERVER_START);
-
-               return 0;
+               REDEBUG2("Client did not select a version and send a NONCE");
+               return -1;
        }
 
        eap_sim_version = selected_version_vp->vp_uint16;
        if (eap_sim_version != EAP_SIM_VERSION) {
-               REDEBUG("EAP-SIM version %i is unknown", eap_sim_version);
+               REDEBUG2("EAP-SIM-Version %i is unknown", eap_sim_version);
                return -1;
        }
 
@@ -378,13 +521,56 @@ static int process_eap_sim_start(eap_session_t *eap_session, VALUE_PAIR *vps)
        memcpy(eap_sim_session->keys.gsm.nonce_mt, nonce_vp->vp_octets, 16);
 
        /*
-        *      Everything looks good, change states
+        *      See if we got an AT_IDENTITY
+        */
+       id = fr_pair_find_by_child_num(vps, dict_sim_root, FR_EAP_SIM_IDENTITY, TAG_ANY);
+       if (id) {
+               if (fr_sim_id_type(&type, &method,
+                                  eap_session->identity, talloc_array_length(eap_session->identity) - 1) < 0) {
+                       RWDEBUG2("Failed parsing identity: %s", fr_strerror());
+               }
+
+               /*
+                *      Update cryptographic identity
+                */
+               talloc_const_free(eap_sim_session->keys.identity);
+               eap_sim_session->keys.identity_len = id->vp_length;
+               MEM(eap_sim_session->keys.identity = talloc_memdup(eap_sim_session, id->vp_strvalue, id->vp_length));
+       }
+
+       /*
+        *      @TODO Run a virtual server to see if we can use the
+        *      identity we just acquired, or whether we need to
+        *      negotiate the next permissive ID.
+        */
+
+       /*
+        *      Negotiate the next permissive form
+        *      if identity, or fail.
         */
-       eap_sim_state_enter(eap_session, eap_sim_session, EAP_SIM_SERVER_CHALLENGE);
+       switch (eap_sim_session->id_req) {
+       case SIM_ANY_ID_REQ:
+               eap_sim_session->id_req = SIM_FULLAUTH_ID_REQ;
+               eap_sim_state_enter(eap_session, EAP_SIM_SERVER_START);
+               break;
+
+       case SIM_FULLAUTH_ID_REQ:
+               eap_sim_session->id_req = SIM_PERMANENT_ID_REQ;
+               eap_sim_state_enter(eap_session, EAP_SIM_SERVER_START);
+               break;
+
+       case SIM_NO_ID_REQ:
+       case SIM_PERMANENT_ID_REQ:
+               eap_sim_state_enter(eap_session, EAP_SIM_SERVER_CHALLENGE);
+//             REDEBUG2("Failed to negotiate a usable identity");
+//             eap_sim_state_enter(eap_session, eap_sim_session, EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION);
+               break;
+       }
 
        return 0;
 }
 
+
 /** Process an EAP-Sim/Response/Challenge
  *
  * Verify that MAC that we received matches what we would have
@@ -398,7 +584,7 @@ static int process_eap_sim_challenge(eap_session_t *eap_session, VALUE_PAIR *vps
        uint8_t                 sres_cat[SIM_VECTOR_GSM_SRES_SIZE * 3];
        uint8_t                 *p = sres_cat;
 
-       uint8_t                 calc_mac[SIM_MAC_SIZE];
+       uint8_t                 calc_mac[SIM_MAC_DIGEST_SIZE];
        ssize_t                 slen;
        VALUE_PAIR              *mac;
 
@@ -408,26 +594,24 @@ static int process_eap_sim_challenge(eap_session_t *eap_session, VALUE_PAIR *vps
        p += SIM_VECTOR_GSM_SRES_SIZE;
        memcpy(p, eap_sim_session->keys.gsm.vector[2].sres, SIM_VECTOR_GSM_SRES_SIZE);
 
-       mac = fr_pair_find_by_child_num(vps, dict_aka_root, FR_EAP_AKA_RES, TAG_ANY);
+       mac = fr_pair_find_by_child_num(vps, dict_sim_root, FR_EAP_SIM_MAC, TAG_ANY);
        if (!mac) {
                REDEBUG("Missing AT_MAC attribute");
                return -1;
        }
-       if (mac->vp_length != SIM_MAC_SIZE) {
-               REDEBUG("AT_MAC incorrect length, expected %u bytes got %zu bytes",
-                       SIM_MAC_SIZE, mac->vp_length);
+       if (mac->vp_length != SIM_MAC_DIGEST_SIZE) {
+               REDEBUG("EAP-SIM-MAC has incorrect length, expected %u bytes got %zu bytes",
+                       SIM_MAC_DIGEST_SIZE, mac->vp_length);
                return -1;
        }
 
        slen = fr_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true, EVP_sha1(),
                                         eap_sim_session->keys.k_aut, eap_sim_session->keys.k_aut_len,
-                                        NULL, 0);
+                                        sres_cat, sizeof(sres_cat));
        if (slen < 0) {
                RPEDEBUG("Failed calculating MAC");
                return -1;
-       }
-
-       if (slen == 0) {
+       } else if (slen == 0) {
                REDEBUG("Missing EAP-SIM-MAC attribute in packet buffer");
                return -1;
        }
@@ -436,13 +620,21 @@ static int process_eap_sim_challenge(eap_session_t *eap_session, VALUE_PAIR *vps
                RDEBUG2("EAP-SIM-MAC matches calculated MAC");
        } else {
                REDEBUG("EAP-SIM-MAC does not match calculated MAC");
-               RHEXDUMP_INLINE(L_DBG_LVL_2, mac->vp_octets, SIM_MAC_SIZE, "Received");
-               RHEXDUMP_INLINE(L_DBG_LVL_2, calc_mac, SIM_MAC_SIZE, "Expected");
+               RHEXDUMP_INLINE(L_DBG_LVL_2, mac->vp_octets, SIM_MAC_DIGEST_SIZE, "Received");
+               RHEXDUMP_INLINE(L_DBG_LVL_2, calc_mac, SIM_MAC_DIGEST_SIZE, "Expected");
                return -1;
        }
 
-       /* everything looks good, change states */
-       eap_sim_state_enter(eap_session, eap_sim_session, EAP_SIM_SERVER_SUCCESS);
+       /*
+        *      If the peer wants a Success notification, then
+        *      send a success notification, otherwise send a
+        *      normal EAP-Success.
+        */
+       if (fr_pair_find_by_child_num(vps, dict_aka_root, FR_EAP_SIM_RESULT_IND, TAG_ANY)) {
+               eap_sim_state_enter(eap_session, EAP_SIM_SERVER_SUCCESS_NOTIFICATION);
+       } else {
+               eap_sim_state_enter(eap_session, EAP_SIM_SERVER_SUCCESS);
+       }
 
        return 0;
 }
@@ -455,12 +647,11 @@ static rlm_rcode_t mod_process(UNUSED void *arg, eap_session_t *eap_session)
 {
        REQUEST                 *request = eap_session->request;
        eap_sim_session_t       *eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t);
-
        fr_sim_decode_ctx_t     ctx = {
                                        .keys = &eap_sim_session->keys,
                                        .root = dict_sim_root
                                };
-       VALUE_PAIR              *vp, *vps;
+       VALUE_PAIR              *subtype_vp, *from_peer, *vp;
        vp_cursor_t             cursor;
 
        eap_sim_subtype_t       subtype;
@@ -472,7 +663,7 @@ static rlm_rcode_t mod_process(UNUSED void *arg, eap_session_t *eap_session)
        /*
         *      VPS is the data from the client
         */
-       vps = eap_session->request->packet->vps;
+       from_peer = eap_session->request->packet->vps;
 
        fr_pair_cursor_init(&cursor, &request->packet->vps);
        fr_pair_cursor_last(&cursor);
@@ -482,7 +673,16 @@ static rlm_rcode_t mod_process(UNUSED void *arg, eap_session_t *eap_session)
                            eap_session->this_round->response->type.data,
                            eap_session->this_round->response->type.length,
                            &ctx);
-       if (ret < 0) return 0;
+       /*
+        *      RFC 4186 says we *MUST* notify, not just
+        *      send an EAP-Failure in this case where
+        *      we cannot decode an EAP-AKA packet.
+        */
+       if (ret < 0) {
+               RPEDEBUG2("Failed decoding EAP-SIM attributes");
+               eap_sim_state_enter(eap_session, EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION);
+               return RLM_MODULE_HANDLED;      /* We need to process more packets */
+       }
 
        vp = fr_pair_cursor_current(&cursor);
        if (vp && RDEBUG_ENABLED2) {
@@ -490,57 +690,148 @@ static rlm_rcode_t mod_process(UNUSED void *arg, eap_session_t *eap_session)
                rdebug_pair_list(L_DBG_LVL_2, request, vp, NULL);
        }
 
-       /*
-        *      See what kind of message we have gotten
-        */
-       vp = fr_pair_find_by_child_num(vps, dict_sim_root, FR_EAP_SIM_SUBTYPE, TAG_ANY);
-       if (!vp) {
-               REDEBUG2("No subtype attribute was created, message dropped");
-               return 0;
+       subtype_vp = fr_pair_find_by_child_num(from_peer, dict_sim_root, FR_EAP_SIM_SUBTYPE, TAG_ANY);
+       if (!subtype_vp) {
+               REDEBUG("Missing EAP-SIM-Subtype");
+               eap_sim_state_enter(eap_session, EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION);
+               return RLM_MODULE_HANDLED;                              /* We need to process more packets */
        }
-       subtype = vp->vp_uint32;
+       subtype = subtype_vp->vp_uint32;
 
+       switch (eap_sim_session->state) {
        /*
-        *      Client error supersedes anything else.
+        *      Response to our advertised versions and request for an ID
+        *      This is very similar to Identity negotiation in EAP-AKA[']
         */
-       if (subtype == EAP_SIM_CLIENT_ERROR) {
-               REDEBUG("Client encountered an error");
-               return RLM_MODULE_FAIL;
-       }
-
-       switch (eap_sim_session->state) {
        case EAP_SIM_SERVER_START:
                switch (subtype) {
+               case EAP_SIM_START:
+                       if (process_eap_sim_start(eap_session, from_peer) == 0) return RLM_MODULE_HANDLED;
+                       eap_sim_state_enter(eap_session, EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION);
+                       return RLM_MODULE_HANDLED;      /* We need to process more packets */
+
                /*
-                *      Pretty much anything else here is illegal, so we will retransmit the request.
+                *      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 EAP_SIM_CLIENT_ERROR:
+               {
+                       char buff[20];
+
+                       vp = fr_pair_find_by_child_num(from_peer, dict_sim_root, FR_EAP_SIM_CLIENT_ERROR_CODE, TAG_ANY);
+                       if (!vp) {
+                               REDEBUG("EAP-SIM Peer rejected SIM-Start (%s) with client-error message but "
+                                       "has not supplied a client error code",
+                                       fr_int2str(sim_id_request_table, eap_sim_session->id_req, "<INVALID>"));
+                       } else {
+                               REDEBUG("Client rejected SIM-Start (%s) with error: %s (%i)",
+                                       fr_int2str(sim_id_request_table, eap_sim_session->id_req, "<INVALID>"),
+                                       fr_pair_value_enum(vp, buff), vp->vp_uint16);
+                       }
+                       eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE);
+                       return RLM_MODULE_REJECT;
+               }
+
+               case EAP_SIM_NOTIFICATION:
+               notification:
+               {
+                       char buff[20];
+
+                       vp = fr_pair_afrom_child_num(from_peer, dict_aka_root, FR_EAP_SIM_NOTIFICATION);
+                       if (!vp) {
+                               REDEBUG2("Received SIM-Notification with no notification code");
+                               eap_sim_state_enter(eap_session, EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION);
+                               return RLM_MODULE_HANDLED;                      /* We need to process more packets */
+                       }
+
+                       /*
+                        *      Case 2 where we're allowed to send an EAP-Failure
+                        */
+                       if (!(vp->vp_uint16 & 0x8000)) {
+                               REDEBUG2("SIM-Notification %s (%i) indicates failure", fr_pair_value_enum(vp, buff),
+                                        vp->vp_uint16);
+                               eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE);
+                               return RLM_MODULE_REJECT;
+                       }
+
+                       /*
+                        *      ...if it's not a failure, then re-enter the
+                        *      current state.
+                        */
+                       REDEBUG2("Got SIM-Notification %s (%i)", fr_pair_value_enum(vp, buff), vp->vp_uint16);
+                       eap_sim_state_enter(eap_session, eap_sim_session->state);
+                       return RLM_MODULE_HANDLED;
+
                default:
-                       eap_sim_state_enter(eap_session, eap_sim_session, EAP_SIM_SERVER_START);
-                       return RLM_MODULE_OK;
-               /*
-                *      A response to our EAP-Sim/Request/Start!
-                */
-               case EAP_SIM_START:
-                       return process_eap_sim_start(eap_session, vps);
+               unexpected_subtype:
+                       /*
+                        *      RFC 4186 says we *MUST* notify, not just
+                        *      send an EAP-Failure in this case.
+                        */
+                       REDEBUG("Unexpected subtype %pV", &subtype_vp->data);
+                       eap_sim_state_enter(eap_session, EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION);
+                       return RLM_MODULE_HANDLED;                              /* We need to process more packets */
+               }
                }
 
+       /*
+        *      Process the response to our previous challenge.
+        */
        case EAP_SIM_SERVER_CHALLENGE:
                switch (subtype) {
-               /*
-                *      Pretty much anything else here is illegal, so we will retransmit the request.
-                */
-               default:
-                       eap_sim_state_enter(eap_session, eap_sim_session, EAP_SIM_SERVER_CHALLENGE);
-                       return 1;
                /*
                 *      A response to our EAP-Sim/Request/Challenge!
                 */
                case EAP_SIM_CHALLENGE:
-                       return process_eap_sim_challenge(eap_session, vps);
+                       return process_eap_sim_challenge(eap_session, from_peer);
+
+               case EAP_SIM_CLIENT_ERROR:
+               {
+                       char buff[20];
+
+                       vp = fr_pair_find_by_child_num(from_peer, dict_aka_root, FR_EAP_SIM_CLIENT_ERROR_CODE, TAG_ANY);
+                       if (!vp) {
+                               REDEBUG("EAP-SIM Peer rejected SIM-Challenge with client-error message but "
+                                       "has not supplied a client error code");
+                       } else {
+                               REDEBUG("Client rejected SIM-Challenge with error: %s (%i)",
+                                       fr_pair_value_enum(vp, buff), vp->vp_uint16);
+                       }
+                       eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE);
+                       return RLM_MODULE_REJECT;
+               }
+
+               case EAP_AKA_NOTIFICATION:
+                       goto notification;
+
+               default:
+                       goto unexpected_subtype;
                }
 
+       /*
+        *      Peer acked our failure
+        */
+       case EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION:
+               switch (subtype) {
+               case EAP_SIM_NOTIFICATION:
+                       RDEBUG2("SIM-Notification ACKed, sending EAP-Failure");
+                       eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE);
+                       return RLM_MODULE_REJECT;
+
+               default:
+                       goto unexpected_subtype;
+               }
+
+       /*
+        *      Something bad happened...
+        */
        default:
                rad_assert(0);
+               eap_sim_state_enter(eap_session, EAP_SIM_SERVER_GENERAL_FAILURE_NOTIFICATION);
+               return RLM_MODULE_HANDLED;                              /* We need to process more packets */
        }
 
        return RLM_MODULE_FAIL;
@@ -552,78 +843,81 @@ static rlm_rcode_t mod_process(UNUSED void *arg, eap_session_t *eap_session)
  */
 static rlm_rcode_t mod_session_init(UNUSED void *instance, eap_session_t *eap_session)
 {
-       REQUEST                 *request = eap_session->request;
-       eap_sim_session_t       *eap_sim_session;
-       time_t                  n;
-       fr_sim_vector_src_t     src = SIM_VECTOR_SRC_AUTO;
+       REQUEST                         *request = eap_session->request;
+       eap_sim_session_t               *eap_sim_session;
+//     rlm_eap_sim_t                   *inst = instance;
+       fr_sim_id_type_t                type;
+       fr_sim_method_hint_t            method;
 
        MEM(eap_sim_session = talloc_zero(eap_session, eap_sim_session_t));
 
        eap_session->opaque = eap_sim_session;
 
        /*
-        *      Save the keying material, because it could change on a subsequent retrieval.
+        *      Set default configuration, we may allow these
+        *      to be toggled by attributes later.
         */
-       RDEBUG2("New EAP-SIM session.  Acquiring SIM vectors");
-       if ((fr_sim_vector_gsm_from_attrs(eap_session, request->control, 0, &eap_sim_session->keys, &src) != 0) ||
-           (fr_sim_vector_gsm_from_attrs(eap_session, request->control, 1, &eap_sim_session->keys, &src) != 0) ||
-           (fr_sim_vector_gsm_from_attrs(eap_session, request->control, 2, &eap_sim_session->keys, &src) != 0)) {
-               REDEBUG("Failed retrieving SIM vectors");
-               return RLM_MODULE_FAIL;
-       }
+       eap_sim_session->send_result_ind = true;
+       eap_sim_session->id_req = SIM_ANY_ID_REQ;       /* Set the default */
 
        /*
-        *      This value doesn't have be strong, but it is good if it is different now and then.
+        *      This value doesn't have be strong, but it is
+        *      good if it is different now and then.
         */
-       time(&n);
-       eap_sim_session->sim_id = (n & 0xff);
-
-       eap_sim_state_enter(eap_session, eap_sim_session, EAP_SIM_SERVER_START);
-
-       eap_session->process = mod_process;
-
-       return RLM_MODULE_OK;
-}
+       eap_sim_session->sim_id = (fr_rand() & 0xff);
 
-/*
- *     Attach the module.
- */
-static int mod_instantiate(UNUSED void *instance, CONF_SECTION *cs)
-{
-       fr_dict_attr_t const *da;
-       CONF_SECTION *subcs = NULL;
+       /*
+        *      Save the keying material, because it could change on a subsequent retrieval.
+        */
+       RDEBUG2("New EAP-SIM session");
 
-       da = fr_dict_attr_child_by_num(dict_sim_root, FR_EAP_SIM_SUBTYPE);
-       if (!da) {
-               cf_log_err(cs, "Failed to find EAP-Sim-Subtype attribute");
-               return -1;
+       /*
+        *      Process the identity that we received in the
+        *      EAP-Identity-Response and use it to determine
+        *      the initial request we send to the Supplicant.
+        */
+       if (fr_sim_id_type(&type, &method,
+                          eap_session->identity, talloc_array_length(eap_session->identity) - 1) < 0) {
+               RWDEBUG2("Failed parsing identity, continuing anyway: %s", fr_strerror());
        }
 
-       while ((subcs = cf_section_find_next(cs, subcs, "process", NULL))) {
-               char const *name2;
-               fr_dict_enum_t *dv;
-
-               name2 = cf_section_name2(subcs);
-               if (!name2) {
-                       cf_log_err(subcs, "Cannot compile 'process { ... }' section");
-                       return -1;
-               }
-
-               dv = fr_dict_enum_by_alias(NULL, da, name2);
-               if (!dv) {
-                       cf_log_err(subcs, "Unknown EAP-SIM-Subtype %s", name2);
-                       return -1;
-               }
+       switch (method) {
+       default:
+               RWDEBUG("EAP-Identity-Response hints that EAP-%s should be started, but we're attempting EAP-SIM",
+                       fr_int2str(sim_id_method_hint_table, method, "<INVALID>"));
+               break;
 
-               cf_log_debug(subcs, "Loading process %s {...}", name2);
+       case SIM_METHOD_HINT_SIM:
+       case SIM_METHOD_HINT_UNKNOWN:
+               break;
+       }
+       eap_session->process = mod_process;
 
-               if (unlang_compile(subcs, MOD_AUTHORIZE) < 0) {
-                       cf_log_err(subcs, "Failed compiling 'process %s { ... }' section", name2);
-                       return -1;
-               }
+       /*
+        *      Figure out what type of identity we have
+        *      and use it to determine the initial
+        *      request we send.
+        */
+       switch (type) {
+       /*
+        *      These types need to be transformed into something
+        *      usable before we can do anything.
+        */
+       case SIM_ID_TYPE_UNKNOWN:
+       case SIM_ID_TYPE_PSEUDONYM:
+       case SIM_ID_TYPE_FASTAUTH:
+       /*
+        *      Permanent ID means we can just send the challenge
+        */
+       case SIM_ID_TYPE_PERMANENT:
+               eap_sim_session->keys.identity_len = talloc_array_length(eap_session->identity) - 1;
+               MEM(eap_sim_session->keys.identity = talloc_memdup(eap_sim_session, eap_session->identity,
+                                                                  eap_sim_session->keys.identity_len));
+               eap_sim_state_enter(eap_session, EAP_SIM_SERVER_START);
+               return RLM_MODULE_OK;
        }
 
-       return 0;
+       return RLM_MODULE_OK;
 }
 
 static int mod_load(void)
@@ -654,9 +948,11 @@ rlm_eap_submodule_t rlm_eap_sim = {
        .magic          = RLM_MODULE_INIT,
 
        .provides       = { FR_EAP_SIM },
+       .inst_size      = sizeof(rlm_eap_sim_t),
+       .config         = submodule_config,
+
        .load           = mod_load,
        .unload         = mod_unload,
-       .instantiate    = mod_instantiate,      /* Create new submodule instance */
        .session_init   = mod_session_init,     /* Initialise a new EAP session */
        .process        = mod_process,          /* Process next round of EAP method */
 };