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