| /* |
| * EAPOL supplicant state machines |
| * Copyright (c) 2004-2012, Jouni Malinen <j@w1.fi> |
| * |
| * This software may be distributed under the terms of the BSD license. |
| * See README for more details. |
| */ |
| |
| #include "includes.h" |
| |
| #include "common.h" |
| #include "state_machine.h" |
| #include "wpabuf.h" |
| #include "eloop.h" |
| #include "crypto/crypto.h" |
| #include "crypto/md5.h" |
| #include "common/eapol_common.h" |
| #include "eap_peer/eap.h" |
| #include "eapol_supp_sm.h" |
| |
| #define STATE_MACHINE_DATA struct eapol_sm |
| #define STATE_MACHINE_DEBUG_PREFIX "EAPOL" |
| |
| |
| /* IEEE 802.1X-2004 - Supplicant - EAPOL state machines */ |
| |
| /** |
| * struct eapol_sm - Internal data for EAPOL state machines |
| */ |
| struct eapol_sm { |
| /* Timers */ |
| unsigned int authWhile; |
| unsigned int heldWhile; |
| unsigned int startWhen; |
| unsigned int idleWhile; /* for EAP state machine */ |
| int timer_tick_enabled; |
| |
| /* Global variables */ |
| Boolean eapFail; |
| Boolean eapolEap; |
| Boolean eapSuccess; |
| Boolean initialize; |
| Boolean keyDone; |
| Boolean keyRun; |
| PortControl portControl; |
| Boolean portEnabled; |
| PortStatus suppPortStatus; /* dot1xSuppControlledPortStatus */ |
| Boolean portValid; |
| Boolean suppAbort; |
| Boolean suppFail; |
| Boolean suppStart; |
| Boolean suppSuccess; |
| Boolean suppTimeout; |
| |
| /* Supplicant PAE state machine */ |
| enum { |
| SUPP_PAE_UNKNOWN = 0, |
| SUPP_PAE_DISCONNECTED = 1, |
| SUPP_PAE_LOGOFF = 2, |
| SUPP_PAE_CONNECTING = 3, |
| SUPP_PAE_AUTHENTICATING = 4, |
| SUPP_PAE_AUTHENTICATED = 5, |
| /* unused(6) */ |
| SUPP_PAE_HELD = 7, |
| SUPP_PAE_RESTART = 8, |
| SUPP_PAE_S_FORCE_AUTH = 9, |
| SUPP_PAE_S_FORCE_UNAUTH = 10 |
| } SUPP_PAE_state; /* dot1xSuppPaeState */ |
| /* Variables */ |
| Boolean userLogoff; |
| Boolean logoffSent; |
| unsigned int startCount; |
| Boolean eapRestart; |
| PortControl sPortMode; |
| /* Constants */ |
| unsigned int heldPeriod; /* dot1xSuppHeldPeriod */ |
| unsigned int startPeriod; /* dot1xSuppStartPeriod */ |
| unsigned int maxStart; /* dot1xSuppMaxStart */ |
| |
| /* Key Receive state machine */ |
| enum { |
| KEY_RX_UNKNOWN = 0, |
| KEY_RX_NO_KEY_RECEIVE, KEY_RX_KEY_RECEIVE |
| } KEY_RX_state; |
| /* Variables */ |
| Boolean rxKey; |
| |
| /* Supplicant Backend state machine */ |
| enum { |
| SUPP_BE_UNKNOWN = 0, |
| SUPP_BE_INITIALIZE = 1, |
| SUPP_BE_IDLE = 2, |
| SUPP_BE_REQUEST = 3, |
| SUPP_BE_RECEIVE = 4, |
| SUPP_BE_RESPONSE = 5, |
| SUPP_BE_FAIL = 6, |
| SUPP_BE_TIMEOUT = 7, |
| SUPP_BE_SUCCESS = 8 |
| } SUPP_BE_state; /* dot1xSuppBackendPaeState */ |
| /* Variables */ |
| Boolean eapNoResp; |
| Boolean eapReq; |
| Boolean eapResp; |
| /* Constants */ |
| unsigned int authPeriod; /* dot1xSuppAuthPeriod */ |
| |
| /* Statistics */ |
| unsigned int dot1xSuppEapolFramesRx; |
| unsigned int dot1xSuppEapolFramesTx; |
| unsigned int dot1xSuppEapolStartFramesTx; |
| unsigned int dot1xSuppEapolLogoffFramesTx; |
| unsigned int dot1xSuppEapolRespFramesTx; |
| unsigned int dot1xSuppEapolReqIdFramesRx; |
| unsigned int dot1xSuppEapolReqFramesRx; |
| unsigned int dot1xSuppInvalidEapolFramesRx; |
| unsigned int dot1xSuppEapLengthErrorFramesRx; |
| unsigned int dot1xSuppLastEapolFrameVersion; |
| unsigned char dot1xSuppLastEapolFrameSource[6]; |
| |
| /* Miscellaneous variables (not defined in IEEE 802.1X-2004) */ |
| Boolean changed; |
| struct eap_sm *eap; |
| struct eap_peer_config *config; |
| Boolean initial_req; |
| u8 *last_rx_key; |
| size_t last_rx_key_len; |
| struct wpabuf *eapReqData; /* for EAP */ |
| Boolean altAccept; /* for EAP */ |
| Boolean altReject; /* for EAP */ |
| Boolean replay_counter_valid; |
| u8 last_replay_counter[16]; |
| struct eapol_config conf; |
| struct eapol_ctx *ctx; |
| enum { EAPOL_CB_IN_PROGRESS = 0, EAPOL_CB_SUCCESS, EAPOL_CB_FAILURE } |
| cb_status; |
| Boolean cached_pmk; |
| |
| Boolean unicast_key_received, broadcast_key_received; |
| }; |
| |
| |
| static void eapol_sm_txLogoff(struct eapol_sm *sm); |
| static void eapol_sm_txStart(struct eapol_sm *sm); |
| static void eapol_sm_processKey(struct eapol_sm *sm); |
| static void eapol_sm_getSuppRsp(struct eapol_sm *sm); |
| static void eapol_sm_txSuppRsp(struct eapol_sm *sm); |
| static void eapol_sm_abortSupp(struct eapol_sm *sm); |
| static void eapol_sm_abort_cached(struct eapol_sm *sm); |
| static void eapol_sm_step_timeout(void *eloop_ctx, void *timeout_ctx); |
| static void eapol_sm_set_port_authorized(struct eapol_sm *sm); |
| static void eapol_sm_set_port_unauthorized(struct eapol_sm *sm); |
| |
| |
| /* Port Timers state machine - implemented as a function that will be called |
| * once a second as a registered event loop timeout */ |
| static void eapol_port_timers_tick(void *eloop_ctx, void *timeout_ctx) |
| { |
| struct eapol_sm *sm = timeout_ctx; |
| |
| if (sm->authWhile > 0) { |
| sm->authWhile--; |
| if (sm->authWhile == 0) |
| wpa_printf(MSG_DEBUG, "EAPOL: authWhile --> 0"); |
| } |
| if (sm->heldWhile > 0) { |
| sm->heldWhile--; |
| if (sm->heldWhile == 0) |
| wpa_printf(MSG_DEBUG, "EAPOL: heldWhile --> 0"); |
| } |
| if (sm->startWhen > 0) { |
| sm->startWhen--; |
| if (sm->startWhen == 0) |
| wpa_printf(MSG_DEBUG, "EAPOL: startWhen --> 0"); |
| } |
| if (sm->idleWhile > 0) { |
| sm->idleWhile--; |
| if (sm->idleWhile == 0) |
| wpa_printf(MSG_DEBUG, "EAPOL: idleWhile --> 0"); |
| } |
| |
| if (sm->authWhile | sm->heldWhile | sm->startWhen | sm->idleWhile) { |
| eloop_register_timeout(1, 0, eapol_port_timers_tick, eloop_ctx, |
| sm); |
| } else { |
| wpa_printf(MSG_DEBUG, "EAPOL: disable timer tick"); |
| sm->timer_tick_enabled = 0; |
| } |
| eapol_sm_step(sm); |
| } |
| |
| |
| static void eapol_enable_timer_tick(struct eapol_sm *sm) |
| { |
| if (sm->timer_tick_enabled) |
| return; |
| wpa_printf(MSG_DEBUG, "EAPOL: enable timer tick"); |
| sm->timer_tick_enabled = 1; |
| eloop_cancel_timeout(eapol_port_timers_tick, NULL, sm); |
| eloop_register_timeout(1, 0, eapol_port_timers_tick, NULL, sm); |
| } |
| |
| |
| SM_STATE(SUPP_PAE, LOGOFF) |
| { |
| SM_ENTRY(SUPP_PAE, LOGOFF); |
| eapol_sm_txLogoff(sm); |
| sm->logoffSent = TRUE; |
| sm->suppPortStatus = Unauthorized; |
| eapol_sm_set_port_unauthorized(sm); |
| } |
| |
| |
| SM_STATE(SUPP_PAE, DISCONNECTED) |
| { |
| SM_ENTRY(SUPP_PAE, DISCONNECTED); |
| sm->sPortMode = Auto; |
| sm->startCount = 0; |
| sm->logoffSent = FALSE; |
| sm->suppPortStatus = Unauthorized; |
| eapol_sm_set_port_unauthorized(sm); |
| sm->suppAbort = TRUE; |
| |
| sm->unicast_key_received = FALSE; |
| sm->broadcast_key_received = FALSE; |
| |
| /* |
| * IEEE Std 802.1X-2004 does not clear heldWhile here, but doing so |
| * allows the timer tick to be stopped more quickly when the port is |
| * not enabled. Since this variable is used only within HELD state, |
| * clearing it on initialization does not change actual state machine |
| * behavior. |
| */ |
| sm->heldWhile = 0; |
| } |
| |
| |
| SM_STATE(SUPP_PAE, CONNECTING) |
| { |
| int send_start = sm->SUPP_PAE_state == SUPP_PAE_CONNECTING; |
| SM_ENTRY(SUPP_PAE, CONNECTING); |
| if (send_start) { |
| sm->startWhen = sm->startPeriod; |
| sm->startCount++; |
| } else { |
| /* |
| * Do not send EAPOL-Start immediately since in most cases, |
| * Authenticator is going to start authentication immediately |
| * after association and an extra EAPOL-Start is just going to |
| * delay authentication. Use a short timeout to send the first |
| * EAPOL-Start if Authenticator does not start authentication. |
| */ |
| #ifdef CONFIG_WPS |
| /* Reduce latency on starting WPS negotiation. */ |
| sm->startWhen = 1; |
| #else /* CONFIG_WPS */ |
| sm->startWhen = 3; |
| #endif /* CONFIG_WPS */ |
| } |
| eapol_enable_timer_tick(sm); |
| sm->eapolEap = FALSE; |
| if (send_start) |
| eapol_sm_txStart(sm); |
| } |
| |
| |
| SM_STATE(SUPP_PAE, AUTHENTICATING) |
| { |
| SM_ENTRY(SUPP_PAE, AUTHENTICATING); |
| sm->startCount = 0; |
| sm->suppSuccess = FALSE; |
| sm->suppFail = FALSE; |
| sm->suppTimeout = FALSE; |
| sm->keyRun = FALSE; |
| sm->keyDone = FALSE; |
| sm->suppStart = TRUE; |
| } |
| |
| |
| SM_STATE(SUPP_PAE, HELD) |
| { |
| SM_ENTRY(SUPP_PAE, HELD); |
| sm->heldWhile = sm->heldPeriod; |
| eapol_enable_timer_tick(sm); |
| sm->suppPortStatus = Unauthorized; |
| eapol_sm_set_port_unauthorized(sm); |
| sm->cb_status = EAPOL_CB_FAILURE; |
| } |
| |
| |
| SM_STATE(SUPP_PAE, AUTHENTICATED) |
| { |
| SM_ENTRY(SUPP_PAE, AUTHENTICATED); |
| sm->suppPortStatus = Authorized; |
| eapol_sm_set_port_authorized(sm); |
| sm->cb_status = EAPOL_CB_SUCCESS; |
| } |
| |
| |
| SM_STATE(SUPP_PAE, RESTART) |
| { |
| SM_ENTRY(SUPP_PAE, RESTART); |
| sm->eapRestart = TRUE; |
| } |
| |
| |
| SM_STATE(SUPP_PAE, S_FORCE_AUTH) |
| { |
| SM_ENTRY(SUPP_PAE, S_FORCE_AUTH); |
| sm->suppPortStatus = Authorized; |
| eapol_sm_set_port_authorized(sm); |
| sm->sPortMode = ForceAuthorized; |
| } |
| |
| |
| SM_STATE(SUPP_PAE, S_FORCE_UNAUTH) |
| { |
| SM_ENTRY(SUPP_PAE, S_FORCE_UNAUTH); |
| sm->suppPortStatus = Unauthorized; |
| eapol_sm_set_port_unauthorized(sm); |
| sm->sPortMode = ForceUnauthorized; |
| eapol_sm_txLogoff(sm); |
| } |
| |
| |
| SM_STEP(SUPP_PAE) |
| { |
| if ((sm->userLogoff && !sm->logoffSent) && |
| !(sm->initialize || !sm->portEnabled)) |
| SM_ENTER_GLOBAL(SUPP_PAE, LOGOFF); |
| else if (((sm->portControl == Auto) && |
| (sm->sPortMode != sm->portControl)) || |
| sm->initialize || !sm->portEnabled) |
| SM_ENTER_GLOBAL(SUPP_PAE, DISCONNECTED); |
| else if ((sm->portControl == ForceAuthorized) && |
| (sm->sPortMode != sm->portControl) && |
| !(sm->initialize || !sm->portEnabled)) |
| SM_ENTER_GLOBAL(SUPP_PAE, S_FORCE_AUTH); |
| else if ((sm->portControl == ForceUnauthorized) && |
| (sm->sPortMode != sm->portControl) && |
| !(sm->initialize || !sm->portEnabled)) |
| SM_ENTER_GLOBAL(SUPP_PAE, S_FORCE_UNAUTH); |
| else switch (sm->SUPP_PAE_state) { |
| case SUPP_PAE_UNKNOWN: |
| break; |
| case SUPP_PAE_LOGOFF: |
| if (!sm->userLogoff) |
| SM_ENTER(SUPP_PAE, DISCONNECTED); |
| break; |
| case SUPP_PAE_DISCONNECTED: |
| SM_ENTER(SUPP_PAE, CONNECTING); |
| break; |
| case SUPP_PAE_CONNECTING: |
| if (sm->startWhen == 0 && sm->startCount < sm->maxStart) |
| SM_ENTER(SUPP_PAE, CONNECTING); |
| else if (sm->startWhen == 0 && |
| sm->startCount >= sm->maxStart && |
| sm->portValid) |
| SM_ENTER(SUPP_PAE, AUTHENTICATED); |
| else if (sm->eapSuccess || sm->eapFail) |
| SM_ENTER(SUPP_PAE, AUTHENTICATING); |
| else if (sm->eapolEap) |
| SM_ENTER(SUPP_PAE, RESTART); |
| else if (sm->startWhen == 0 && |
| sm->startCount >= sm->maxStart && |
| !sm->portValid) |
| SM_ENTER(SUPP_PAE, HELD); |
| break; |
| case SUPP_PAE_AUTHENTICATING: |
| if (sm->eapSuccess && !sm->portValid && |
| sm->conf.accept_802_1x_keys && |
| sm->conf.required_keys == 0) { |
| wpa_printf(MSG_DEBUG, "EAPOL: IEEE 802.1X for " |
| "plaintext connection; no EAPOL-Key frames " |
| "required"); |
| sm->portValid = TRUE; |
| if (sm->ctx->eapol_done_cb) |
| sm->ctx->eapol_done_cb(sm->ctx->ctx); |
| } |
| if (sm->eapSuccess && sm->portValid) |
| SM_ENTER(SUPP_PAE, AUTHENTICATED); |
| else if (sm->eapFail || (sm->keyDone && !sm->portValid)) |
| SM_ENTER(SUPP_PAE, HELD); |
| else if (sm->suppTimeout) |
| SM_ENTER(SUPP_PAE, CONNECTING); |
| break; |
| case SUPP_PAE_HELD: |
| if (sm->heldWhile == 0) |
| SM_ENTER(SUPP_PAE, CONNECTING); |
| else if (sm->eapolEap) |
| SM_ENTER(SUPP_PAE, RESTART); |
| break; |
| case SUPP_PAE_AUTHENTICATED: |
| if (sm->eapolEap && sm->portValid) |
| SM_ENTER(SUPP_PAE, RESTART); |
| else if (!sm->portValid) |
| SM_ENTER(SUPP_PAE, DISCONNECTED); |
| break; |
| case SUPP_PAE_RESTART: |
| if (!sm->eapRestart) |
| SM_ENTER(SUPP_PAE, AUTHENTICATING); |
| break; |
| case SUPP_PAE_S_FORCE_AUTH: |
| break; |
| case SUPP_PAE_S_FORCE_UNAUTH: |
| break; |
| } |
| } |
| |
| |
| SM_STATE(KEY_RX, NO_KEY_RECEIVE) |
| { |
| SM_ENTRY(KEY_RX, NO_KEY_RECEIVE); |
| } |
| |
| |
| SM_STATE(KEY_RX, KEY_RECEIVE) |
| { |
| SM_ENTRY(KEY_RX, KEY_RECEIVE); |
| eapol_sm_processKey(sm); |
| sm->rxKey = FALSE; |
| } |
| |
| |
| SM_STEP(KEY_RX) |
| { |
| if (sm->initialize || !sm->portEnabled) |
| SM_ENTER_GLOBAL(KEY_RX, NO_KEY_RECEIVE); |
| switch (sm->KEY_RX_state) { |
| case KEY_RX_UNKNOWN: |
| break; |
| case KEY_RX_NO_KEY_RECEIVE: |
| if (sm->rxKey) |
| SM_ENTER(KEY_RX, KEY_RECEIVE); |
| break; |
| case KEY_RX_KEY_RECEIVE: |
| if (sm->rxKey) |
| SM_ENTER(KEY_RX, KEY_RECEIVE); |
| break; |
| } |
| } |
| |
| |
| SM_STATE(SUPP_BE, REQUEST) |
| { |
| SM_ENTRY(SUPP_BE, REQUEST); |
| sm->authWhile = 0; |
| sm->eapReq = TRUE; |
| eapol_sm_getSuppRsp(sm); |
| } |
| |
| |
| SM_STATE(SUPP_BE, RESPONSE) |
| { |
| SM_ENTRY(SUPP_BE, RESPONSE); |
| eapol_sm_txSuppRsp(sm); |
| sm->eapResp = FALSE; |
| } |
| |
| |
| SM_STATE(SUPP_BE, SUCCESS) |
| { |
| SM_ENTRY(SUPP_BE, SUCCESS); |
| sm->keyRun = TRUE; |
| sm->suppSuccess = TRUE; |
| |
| if (eap_key_available(sm->eap)) { |
| /* New key received - clear IEEE 802.1X EAPOL-Key replay |
| * counter */ |
| sm->replay_counter_valid = FALSE; |
| } |
| } |
| |
| |
| SM_STATE(SUPP_BE, FAIL) |
| { |
| SM_ENTRY(SUPP_BE, FAIL); |
| sm->suppFail = TRUE; |
| } |
| |
| |
| SM_STATE(SUPP_BE, TIMEOUT) |
| { |
| SM_ENTRY(SUPP_BE, TIMEOUT); |
| sm->suppTimeout = TRUE; |
| } |
| |
| |
| SM_STATE(SUPP_BE, IDLE) |
| { |
| SM_ENTRY(SUPP_BE, IDLE); |
| sm->suppStart = FALSE; |
| sm->initial_req = TRUE; |
| } |
| |
| |
| SM_STATE(SUPP_BE, INITIALIZE) |
| { |
| SM_ENTRY(SUPP_BE, INITIALIZE); |
| eapol_sm_abortSupp(sm); |
| sm->suppAbort = FALSE; |
| |
| /* |
| * IEEE Std 802.1X-2004 does not clear authWhile here, but doing so |
| * allows the timer tick to be stopped more quickly when the port is |
| * not enabled. Since this variable is used only within RECEIVE state, |
| * clearing it on initialization does not change actual state machine |
| * behavior. |
| */ |
| sm->authWhile = 0; |
| } |
| |
| |
| SM_STATE(SUPP_BE, RECEIVE) |
| { |
| SM_ENTRY(SUPP_BE, RECEIVE); |
| sm->authWhile = sm->authPeriod; |
| eapol_enable_timer_tick(sm); |
| sm->eapolEap = FALSE; |
| sm->eapNoResp = FALSE; |
| sm->initial_req = FALSE; |
| } |
| |
| |
| SM_STEP(SUPP_BE) |
| { |
| if (sm->initialize || sm->suppAbort) |
| SM_ENTER_GLOBAL(SUPP_BE, INITIALIZE); |
| else switch (sm->SUPP_BE_state) { |
| case SUPP_BE_UNKNOWN: |
| break; |
| case SUPP_BE_REQUEST: |
| /* |
| * IEEE Std 802.1X-2004 has transitions from REQUEST to FAIL |
| * and SUCCESS based on eapFail and eapSuccess, respectively. |
| * However, IEEE Std 802.1X-2004 is also specifying that |
| * eapNoResp should be set in conjunction with eapSuccess and |
| * eapFail which would mean that more than one of the |
| * transitions here would be activated at the same time. |
| * Skipping RESPONSE and/or RECEIVE states in these cases can |
| * cause problems and the direct transitions to do not seem |
| * correct. Because of this, the conditions for these |
| * transitions are verified only after eapNoResp. They are |
| * unlikely to be used since eapNoResp should always be set if |
| * either of eapSuccess or eapFail is set. |
| */ |
| if (sm->eapResp && sm->eapNoResp) { |
| wpa_printf(MSG_DEBUG, "EAPOL: SUPP_BE REQUEST: both " |
| "eapResp and eapNoResp set?!"); |
| } |
| if (sm->eapResp) |
| SM_ENTER(SUPP_BE, RESPONSE); |
| else if (sm->eapNoResp) |
| SM_ENTER(SUPP_BE, RECEIVE); |
| else if (sm->eapFail) |
| SM_ENTER(SUPP_BE, FAIL); |
| else if (sm->eapSuccess) |
| SM_ENTER(SUPP_BE, SUCCESS); |
| break; |
| case SUPP_BE_RESPONSE: |
| SM_ENTER(SUPP_BE, RECEIVE); |
| break; |
| case SUPP_BE_SUCCESS: |
| SM_ENTER(SUPP_BE, IDLE); |
| break; |
| case SUPP_BE_FAIL: |
| SM_ENTER(SUPP_BE, IDLE); |
| break; |
| case SUPP_BE_TIMEOUT: |
| SM_ENTER(SUPP_BE, IDLE); |
| break; |
| case SUPP_BE_IDLE: |
| if (sm->eapFail && sm->suppStart) |
| SM_ENTER(SUPP_BE, FAIL); |
| else if (sm->eapolEap && sm->suppStart) |
| SM_ENTER(SUPP_BE, REQUEST); |
| else if (sm->eapSuccess && sm->suppStart) |
| SM_ENTER(SUPP_BE, SUCCESS); |
| break; |
| case SUPP_BE_INITIALIZE: |
| SM_ENTER(SUPP_BE, IDLE); |
| break; |
| case SUPP_BE_RECEIVE: |
| if (sm->eapolEap) |
| SM_ENTER(SUPP_BE, REQUEST); |
| else if (sm->eapFail) |
| SM_ENTER(SUPP_BE, FAIL); |
| else if (sm->authWhile == 0) |
| SM_ENTER(SUPP_BE, TIMEOUT); |
| else if (sm->eapSuccess) |
| SM_ENTER(SUPP_BE, SUCCESS); |
| break; |
| } |
| } |
| |
| |
| static void eapol_sm_txLogoff(struct eapol_sm *sm) |
| { |
| wpa_printf(MSG_DEBUG, "EAPOL: txLogoff"); |
| sm->ctx->eapol_send(sm->ctx->eapol_send_ctx, |
| IEEE802_1X_TYPE_EAPOL_LOGOFF, (u8 *) "", 0); |
| sm->dot1xSuppEapolLogoffFramesTx++; |
| sm->dot1xSuppEapolFramesTx++; |
| } |
| |
| |
| static void eapol_sm_txStart(struct eapol_sm *sm) |
| { |
| wpa_printf(MSG_DEBUG, "EAPOL: txStart"); |
| sm->ctx->eapol_send(sm->ctx->eapol_send_ctx, |
| IEEE802_1X_TYPE_EAPOL_START, (u8 *) "", 0); |
| sm->dot1xSuppEapolStartFramesTx++; |
| sm->dot1xSuppEapolFramesTx++; |
| } |
| |
| |
| #define IEEE8021X_ENCR_KEY_LEN 32 |
| #define IEEE8021X_SIGN_KEY_LEN 32 |
| |
| struct eap_key_data { |
| u8 encr_key[IEEE8021X_ENCR_KEY_LEN]; |
| u8 sign_key[IEEE8021X_SIGN_KEY_LEN]; |
| }; |
| |
| |
| static void eapol_sm_processKey(struct eapol_sm *sm) |
| { |
| #ifndef CONFIG_FIPS |
| struct ieee802_1x_hdr *hdr; |
| struct ieee802_1x_eapol_key *key; |
| struct eap_key_data keydata; |
| u8 orig_key_sign[IEEE8021X_KEY_SIGN_LEN], datakey[32]; |
| u8 ekey[IEEE8021X_KEY_IV_LEN + IEEE8021X_ENCR_KEY_LEN]; |
| int key_len, res, sign_key_len, encr_key_len; |
| u16 rx_key_length; |
| size_t plen; |
| |
| wpa_printf(MSG_DEBUG, "EAPOL: processKey"); |
| if (sm->last_rx_key == NULL) |
| return; |
| |
| if (!sm->conf.accept_802_1x_keys) { |
| wpa_printf(MSG_WARNING, "EAPOL: Received IEEE 802.1X EAPOL-Key" |
| " even though this was not accepted - " |
| "ignoring this packet"); |
| return; |
| } |
| |
| if (sm->last_rx_key_len < sizeof(*hdr) + sizeof(*key)) |
| return; |
| hdr = (struct ieee802_1x_hdr *) sm->last_rx_key; |
| key = (struct ieee802_1x_eapol_key *) (hdr + 1); |
| plen = be_to_host16(hdr->length); |
| if (sizeof(*hdr) + plen > sm->last_rx_key_len || plen < sizeof(*key)) { |
| wpa_printf(MSG_WARNING, "EAPOL: Too short EAPOL-Key frame"); |
| return; |
| } |
| rx_key_length = WPA_GET_BE16(key->key_length); |
| wpa_printf(MSG_DEBUG, "EAPOL: RX IEEE 802.1X ver=%d type=%d len=%d " |
| "EAPOL-Key: type=%d key_length=%d key_index=0x%x", |
| hdr->version, hdr->type, be_to_host16(hdr->length), |
| key->type, rx_key_length, key->key_index); |
| |
| eapol_sm_notify_lower_layer_success(sm, 1); |
| sign_key_len = IEEE8021X_SIGN_KEY_LEN; |
| encr_key_len = IEEE8021X_ENCR_KEY_LEN; |
| res = eapol_sm_get_key(sm, (u8 *) &keydata, sizeof(keydata)); |
| if (res < 0) { |
| wpa_printf(MSG_DEBUG, "EAPOL: Could not get master key for " |
| "decrypting EAPOL-Key keys"); |
| return; |
| } |
| if (res == 16) { |
| /* LEAP derives only 16 bytes of keying material. */ |
| res = eapol_sm_get_key(sm, (u8 *) &keydata, 16); |
| if (res) { |
| wpa_printf(MSG_DEBUG, "EAPOL: Could not get LEAP " |
| "master key for decrypting EAPOL-Key keys"); |
| return; |
| } |
| sign_key_len = 16; |
| encr_key_len = 16; |
| os_memcpy(keydata.sign_key, keydata.encr_key, 16); |
| } else if (res) { |
| wpa_printf(MSG_DEBUG, "EAPOL: Could not get enough master key " |
| "data for decrypting EAPOL-Key keys (res=%d)", res); |
| return; |
| } |
| |
| /* The key replay_counter must increase when same master key */ |
| if (sm->replay_counter_valid && |
| os_memcmp(sm->last_replay_counter, key->replay_counter, |
| IEEE8021X_REPLAY_COUNTER_LEN) >= 0) { |
| wpa_printf(MSG_WARNING, "EAPOL: EAPOL-Key replay counter did " |
| "not increase - ignoring key"); |
| wpa_hexdump(MSG_DEBUG, "EAPOL: last replay counter", |
| sm->last_replay_counter, |
| IEEE8021X_REPLAY_COUNTER_LEN); |
| wpa_hexdump(MSG_DEBUG, "EAPOL: received replay counter", |
| key->replay_counter, IEEE8021X_REPLAY_COUNTER_LEN); |
| return; |
| } |
| |
| /* Verify key signature (HMAC-MD5) */ |
| os_memcpy(orig_key_sign, key->key_signature, IEEE8021X_KEY_SIGN_LEN); |
| os_memset(key->key_signature, 0, IEEE8021X_KEY_SIGN_LEN); |
| hmac_md5(keydata.sign_key, sign_key_len, |
| sm->last_rx_key, sizeof(*hdr) + be_to_host16(hdr->length), |
| key->key_signature); |
| if (os_memcmp(orig_key_sign, key->key_signature, |
| IEEE8021X_KEY_SIGN_LEN) != 0) { |
| wpa_printf(MSG_DEBUG, "EAPOL: Invalid key signature in " |
| "EAPOL-Key packet"); |
| os_memcpy(key->key_signature, orig_key_sign, |
| IEEE8021X_KEY_SIGN_LEN); |
| return; |
| } |
| wpa_printf(MSG_DEBUG, "EAPOL: EAPOL-Key key signature verified"); |
| |
| key_len = plen - sizeof(*key); |
| if (key_len > 32 || rx_key_length > 32) { |
| wpa_printf(MSG_WARNING, "EAPOL: Too long key data length %d", |
| key_len ? key_len : rx_key_length); |
| return; |
| } |
| if (key_len == rx_key_length) { |
| os_memcpy(ekey, key->key_iv, IEEE8021X_KEY_IV_LEN); |
| os_memcpy(ekey + IEEE8021X_KEY_IV_LEN, keydata.encr_key, |
| encr_key_len); |
| os_memcpy(datakey, key + 1, key_len); |
| rc4_skip(ekey, IEEE8021X_KEY_IV_LEN + encr_key_len, 0, |
| datakey, key_len); |
| wpa_hexdump_key(MSG_DEBUG, "EAPOL: Decrypted(RC4) key", |
| datakey, key_len); |
| } else if (key_len == 0) { |
| /* |
| * IEEE 802.1X-2004 specifies that least significant Key Length |
| * octets from MS-MPPE-Send-Key are used as the key if the key |
| * data is not present. This seems to be meaning the beginning |
| * of the MS-MPPE-Send-Key. In addition, MS-MPPE-Send-Key in |
| * Supplicant corresponds to MS-MPPE-Recv-Key in Authenticator. |
| * Anyway, taking the beginning of the keying material from EAP |
| * seems to interoperate with Authenticators. |
| */ |
| key_len = rx_key_length; |
| os_memcpy(datakey, keydata.encr_key, key_len); |
| wpa_hexdump_key(MSG_DEBUG, "EAPOL: using part of EAP keying " |
| "material data encryption key", |
| datakey, key_len); |
| } else { |
| wpa_printf(MSG_DEBUG, "EAPOL: Invalid key data length %d " |
| "(key_length=%d)", key_len, rx_key_length); |
| return; |
| } |
| |
| sm->replay_counter_valid = TRUE; |
| os_memcpy(sm->last_replay_counter, key->replay_counter, |
| IEEE8021X_REPLAY_COUNTER_LEN); |
| |
| wpa_printf(MSG_DEBUG, "EAPOL: Setting dynamic WEP key: %s keyidx %d " |
| "len %d", |
| key->key_index & IEEE8021X_KEY_INDEX_FLAG ? |
| "unicast" : "broadcast", |
| key->key_index & IEEE8021X_KEY_INDEX_MASK, key_len); |
| |
| if (sm->ctx->set_wep_key && |
| sm->ctx->set_wep_key(sm->ctx->ctx, |
| key->key_index & IEEE8021X_KEY_INDEX_FLAG, |
| key->key_index & IEEE8021X_KEY_INDEX_MASK, |
| datakey, key_len) < 0) { |
| wpa_printf(MSG_WARNING, "EAPOL: Failed to set WEP key to the " |
| " driver."); |
| } else { |
| if (key->key_index & IEEE8021X_KEY_INDEX_FLAG) |
| sm->unicast_key_received = TRUE; |
| else |
| sm->broadcast_key_received = TRUE; |
| |
| if ((sm->unicast_key_received || |
| !(sm->conf.required_keys & EAPOL_REQUIRE_KEY_UNICAST)) && |
| (sm->broadcast_key_received || |
| !(sm->conf.required_keys & EAPOL_REQUIRE_KEY_BROADCAST))) |
| { |
| wpa_printf(MSG_DEBUG, "EAPOL: all required EAPOL-Key " |
| "frames received"); |
| sm->portValid = TRUE; |
| if (sm->ctx->eapol_done_cb) |
| sm->ctx->eapol_done_cb(sm->ctx->ctx); |
| } |
| } |
| #endif /* CONFIG_FIPS */ |
| } |
| |
| |
| static void eapol_sm_getSuppRsp(struct eapol_sm *sm) |
| { |
| wpa_printf(MSG_DEBUG, "EAPOL: getSuppRsp"); |
| /* EAP layer processing; no special code is needed, since Supplicant |
| * Backend state machine is waiting for eapNoResp or eapResp to be set |
| * and these are only set in the EAP state machine when the processing |
| * has finished. */ |
| } |
| |
| |
| static void eapol_sm_txSuppRsp(struct eapol_sm *sm) |
| { |
| struct wpabuf *resp; |
| |
| wpa_printf(MSG_DEBUG, "EAPOL: txSuppRsp"); |
| resp = eap_get_eapRespData(sm->eap); |
| if (resp == NULL) { |
| wpa_printf(MSG_WARNING, "EAPOL: txSuppRsp - EAP response data " |
| "not available"); |
| return; |
| } |
| |
| /* Send EAP-Packet from the EAP layer to the Authenticator */ |
| sm->ctx->eapol_send(sm->ctx->eapol_send_ctx, |
| IEEE802_1X_TYPE_EAP_PACKET, wpabuf_head(resp), |
| wpabuf_len(resp)); |
| |
| /* eapRespData is not used anymore, so free it here */ |
| wpabuf_free(resp); |
| |
| if (sm->initial_req) |
| sm->dot1xSuppEapolReqIdFramesRx++; |
| else |
| sm->dot1xSuppEapolReqFramesRx++; |
| sm->dot1xSuppEapolRespFramesTx++; |
| sm->dot1xSuppEapolFramesTx++; |
| } |
| |
| |
| static void eapol_sm_abortSupp(struct eapol_sm *sm) |
| { |
| /* release system resources that may have been allocated for the |
| * authentication session */ |
| os_free(sm->last_rx_key); |
| sm->last_rx_key = NULL; |
| wpabuf_free(sm->eapReqData); |
| sm->eapReqData = NULL; |
| eap_sm_abort(sm->eap); |
| } |
| |
| |
| static void eapol_sm_step_timeout(void *eloop_ctx, void *timeout_ctx) |
| { |
| eapol_sm_step(timeout_ctx); |
| } |
| |
| |
| static void eapol_sm_set_port_authorized(struct eapol_sm *sm) |
| { |
| if (sm->ctx->port_cb) |
| sm->ctx->port_cb(sm->ctx->ctx, 1); |
| } |
| |
| |
| static void eapol_sm_set_port_unauthorized(struct eapol_sm *sm) |
| { |
| if (sm->ctx->port_cb) |
| sm->ctx->port_cb(sm->ctx->ctx, 0); |
| } |
| |
| |
| /** |
| * eapol_sm_step - EAPOL state machine step function |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * |
| * This function is called to notify the state machine about changed external |
| * variables. It will step through the EAPOL state machines in loop to process |
| * all triggered state changes. |
| */ |
| void eapol_sm_step(struct eapol_sm *sm) |
| { |
| int i; |
| |
| /* In theory, it should be ok to run this in loop until !changed. |
| * However, it is better to use a limit on number of iterations to |
| * allow events (e.g., SIGTERM) to stop the program cleanly if the |
| * state machine were to generate a busy loop. */ |
| for (i = 0; i < 100; i++) { |
| sm->changed = FALSE; |
| SM_STEP_RUN(SUPP_PAE); |
| SM_STEP_RUN(KEY_RX); |
| SM_STEP_RUN(SUPP_BE); |
| if (eap_peer_sm_step(sm->eap)) |
| sm->changed = TRUE; |
| if (!sm->changed) |
| break; |
| } |
| |
| if (sm->changed) { |
| /* restart EAPOL state machine step from timeout call in order |
| * to allow other events to be processed. */ |
| eloop_cancel_timeout(eapol_sm_step_timeout, NULL, sm); |
| eloop_register_timeout(0, 0, eapol_sm_step_timeout, NULL, sm); |
| } |
| |
| if (sm->ctx->cb && sm->cb_status != EAPOL_CB_IN_PROGRESS) { |
| int success = sm->cb_status == EAPOL_CB_SUCCESS ? 1 : 0; |
| sm->cb_status = EAPOL_CB_IN_PROGRESS; |
| sm->ctx->cb(sm, success, sm->ctx->cb_ctx); |
| } |
| } |
| |
| |
| #ifdef CONFIG_CTRL_IFACE |
| static const char *eapol_supp_pae_state(int state) |
| { |
| switch (state) { |
| case SUPP_PAE_LOGOFF: |
| return "LOGOFF"; |
| case SUPP_PAE_DISCONNECTED: |
| return "DISCONNECTED"; |
| case SUPP_PAE_CONNECTING: |
| return "CONNECTING"; |
| case SUPP_PAE_AUTHENTICATING: |
| return "AUTHENTICATING"; |
| case SUPP_PAE_HELD: |
| return "HELD"; |
| case SUPP_PAE_AUTHENTICATED: |
| return "AUTHENTICATED"; |
| case SUPP_PAE_RESTART: |
| return "RESTART"; |
| default: |
| return "UNKNOWN"; |
| } |
| } |
| |
| |
| static const char *eapol_supp_be_state(int state) |
| { |
| switch (state) { |
| case SUPP_BE_REQUEST: |
| return "REQUEST"; |
| case SUPP_BE_RESPONSE: |
| return "RESPONSE"; |
| case SUPP_BE_SUCCESS: |
| return "SUCCESS"; |
| case SUPP_BE_FAIL: |
| return "FAIL"; |
| case SUPP_BE_TIMEOUT: |
| return "TIMEOUT"; |
| case SUPP_BE_IDLE: |
| return "IDLE"; |
| case SUPP_BE_INITIALIZE: |
| return "INITIALIZE"; |
| case SUPP_BE_RECEIVE: |
| return "RECEIVE"; |
| default: |
| return "UNKNOWN"; |
| } |
| } |
| |
| |
| static const char * eapol_port_status(PortStatus status) |
| { |
| if (status == Authorized) |
| return "Authorized"; |
| else |
| return "Unauthorized"; |
| } |
| #endif /* CONFIG_CTRL_IFACE */ |
| |
| |
| #if defined(CONFIG_CTRL_IFACE) || !defined(CONFIG_NO_STDOUT_DEBUG) |
| static const char * eapol_port_control(PortControl ctrl) |
| { |
| switch (ctrl) { |
| case Auto: |
| return "Auto"; |
| case ForceUnauthorized: |
| return "ForceUnauthorized"; |
| case ForceAuthorized: |
| return "ForceAuthorized"; |
| default: |
| return "Unknown"; |
| } |
| } |
| #endif /* CONFIG_CTRL_IFACE || !CONFIG_NO_STDOUT_DEBUG */ |
| |
| |
| /** |
| * eapol_sm_configure - Set EAPOL variables |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * @heldPeriod: dot1xSuppHeldPeriod |
| * @authPeriod: dot1xSuppAuthPeriod |
| * @startPeriod: dot1xSuppStartPeriod |
| * @maxStart: dot1xSuppMaxStart |
| * |
| * Set configurable EAPOL state machine variables. Each variable can be set to |
| * the given value or ignored if set to -1 (to set only some of the variables). |
| */ |
| void eapol_sm_configure(struct eapol_sm *sm, int heldPeriod, int authPeriod, |
| int startPeriod, int maxStart) |
| { |
| if (sm == NULL) |
| return; |
| if (heldPeriod >= 0) |
| sm->heldPeriod = heldPeriod; |
| if (authPeriod >= 0) |
| sm->authPeriod = authPeriod; |
| if (startPeriod >= 0) |
| sm->startPeriod = startPeriod; |
| if (maxStart >= 0) |
| sm->maxStart = maxStart; |
| } |
| |
| |
| /** |
| * eapol_sm_get_method_name - Get EAPOL method name |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * Returns: Static string containing name of current eap method or NULL |
| */ |
| const char * eapol_sm_get_method_name(struct eapol_sm *sm) |
| { |
| if (sm->SUPP_PAE_state != SUPP_PAE_AUTHENTICATED || |
| sm->suppPortStatus != Authorized) |
| return NULL; |
| |
| return eap_sm_get_method_name(sm->eap); |
| } |
| |
| |
| #ifdef CONFIG_CTRL_IFACE |
| /** |
| * eapol_sm_get_status - Get EAPOL state machine status |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * @buf: Buffer for status information |
| * @buflen: Maximum buffer length |
| * @verbose: Whether to include verbose status information |
| * Returns: Number of bytes written to buf. |
| * |
| * Query EAPOL state machine for status information. This function fills in a |
| * text area with current status information from the EAPOL state machine. If |
| * the buffer (buf) is not large enough, status information will be truncated |
| * to fit the buffer. |
| */ |
| int eapol_sm_get_status(struct eapol_sm *sm, char *buf, size_t buflen, |
| int verbose) |
| { |
| int len, ret; |
| if (sm == NULL) |
| return 0; |
| |
| len = os_snprintf(buf, buflen, |
| "Supplicant PAE state=%s\n" |
| "suppPortStatus=%s\n", |
| eapol_supp_pae_state(sm->SUPP_PAE_state), |
| eapol_port_status(sm->suppPortStatus)); |
| if (len < 0 || (size_t) len >= buflen) |
| return 0; |
| |
| if (verbose) { |
| ret = os_snprintf(buf + len, buflen - len, |
| "heldPeriod=%u\n" |
| "authPeriod=%u\n" |
| "startPeriod=%u\n" |
| "maxStart=%u\n" |
| "portControl=%s\n" |
| "Supplicant Backend state=%s\n", |
| sm->heldPeriod, |
| sm->authPeriod, |
| sm->startPeriod, |
| sm->maxStart, |
| eapol_port_control(sm->portControl), |
| eapol_supp_be_state(sm->SUPP_BE_state)); |
| if (ret < 0 || (size_t) ret >= buflen - len) |
| return len; |
| len += ret; |
| } |
| |
| len += eap_sm_get_status(sm->eap, buf + len, buflen - len, verbose); |
| |
| return len; |
| } |
| |
| |
| /** |
| * eapol_sm_get_mib - Get EAPOL state machine MIBs |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * @buf: Buffer for MIB information |
| * @buflen: Maximum buffer length |
| * Returns: Number of bytes written to buf. |
| * |
| * Query EAPOL state machine for MIB information. This function fills in a |
| * text area with current MIB information from the EAPOL state machine. If |
| * the buffer (buf) is not large enough, MIB information will be truncated to |
| * fit the buffer. |
| */ |
| int eapol_sm_get_mib(struct eapol_sm *sm, char *buf, size_t buflen) |
| { |
| size_t len; |
| int ret; |
| |
| if (sm == NULL) |
| return 0; |
| ret = os_snprintf(buf, buflen, |
| "dot1xSuppPaeState=%d\n" |
| "dot1xSuppHeldPeriod=%u\n" |
| "dot1xSuppAuthPeriod=%u\n" |
| "dot1xSuppStartPeriod=%u\n" |
| "dot1xSuppMaxStart=%u\n" |
| "dot1xSuppSuppControlledPortStatus=%s\n" |
| "dot1xSuppBackendPaeState=%d\n", |
| sm->SUPP_PAE_state, |
| sm->heldPeriod, |
| sm->authPeriod, |
| sm->startPeriod, |
| sm->maxStart, |
| sm->suppPortStatus == Authorized ? |
| "Authorized" : "Unauthorized", |
| sm->SUPP_BE_state); |
| |
| if (ret < 0 || (size_t) ret >= buflen) |
| return 0; |
| len = ret; |
| |
| ret = os_snprintf(buf + len, buflen - len, |
| "dot1xSuppEapolFramesRx=%u\n" |
| "dot1xSuppEapolFramesTx=%u\n" |
| "dot1xSuppEapolStartFramesTx=%u\n" |
| "dot1xSuppEapolLogoffFramesTx=%u\n" |
| "dot1xSuppEapolRespFramesTx=%u\n" |
| "dot1xSuppEapolReqIdFramesRx=%u\n" |
| "dot1xSuppEapolReqFramesRx=%u\n" |
| "dot1xSuppInvalidEapolFramesRx=%u\n" |
| "dot1xSuppEapLengthErrorFramesRx=%u\n" |
| "dot1xSuppLastEapolFrameVersion=%u\n" |
| "dot1xSuppLastEapolFrameSource=" MACSTR "\n", |
| sm->dot1xSuppEapolFramesRx, |
| sm->dot1xSuppEapolFramesTx, |
| sm->dot1xSuppEapolStartFramesTx, |
| sm->dot1xSuppEapolLogoffFramesTx, |
| sm->dot1xSuppEapolRespFramesTx, |
| sm->dot1xSuppEapolReqIdFramesRx, |
| sm->dot1xSuppEapolReqFramesRx, |
| sm->dot1xSuppInvalidEapolFramesRx, |
| sm->dot1xSuppEapLengthErrorFramesRx, |
| sm->dot1xSuppLastEapolFrameVersion, |
| MAC2STR(sm->dot1xSuppLastEapolFrameSource)); |
| |
| if (ret < 0 || (size_t) ret >= buflen - len) |
| return len; |
| len += ret; |
| |
| return len; |
| } |
| #endif /* CONFIG_CTRL_IFACE */ |
| |
| |
| /** |
| * eapol_sm_rx_eapol - Process received EAPOL frames |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * @src: Source MAC address of the EAPOL packet |
| * @buf: Pointer to the beginning of the EAPOL data (EAPOL header) |
| * @len: Length of the EAPOL frame |
| * Returns: 1 = EAPOL frame processed, 0 = not for EAPOL state machine, |
| * -1 failure |
| */ |
| int eapol_sm_rx_eapol(struct eapol_sm *sm, const u8 *src, const u8 *buf, |
| size_t len) |
| { |
| const struct ieee802_1x_hdr *hdr; |
| const struct ieee802_1x_eapol_key *key; |
| int data_len; |
| int res = 1; |
| size_t plen; |
| |
| if (sm == NULL) |
| return 0; |
| sm->dot1xSuppEapolFramesRx++; |
| if (len < sizeof(*hdr)) { |
| sm->dot1xSuppInvalidEapolFramesRx++; |
| return 0; |
| } |
| hdr = (const struct ieee802_1x_hdr *) buf; |
| sm->dot1xSuppLastEapolFrameVersion = hdr->version; |
| os_memcpy(sm->dot1xSuppLastEapolFrameSource, src, ETH_ALEN); |
| if (hdr->version < EAPOL_VERSION) { |
| /* TODO: backwards compatibility */ |
| } |
| plen = be_to_host16(hdr->length); |
| if (plen > len - sizeof(*hdr)) { |
| sm->dot1xSuppEapLengthErrorFramesRx++; |
| return 0; |
| } |
| #ifdef CONFIG_WPS |
| if (sm->conf.workaround && |
| plen < len - sizeof(*hdr) && |
| hdr->type == IEEE802_1X_TYPE_EAP_PACKET && |
| len - sizeof(*hdr) > sizeof(struct eap_hdr)) { |
| const struct eap_hdr *ehdr = |
| (const struct eap_hdr *) (hdr + 1); |
| u16 elen; |
| |
| elen = be_to_host16(ehdr->length); |
| if (elen > plen && elen <= len - sizeof(*hdr)) { |
| /* |
| * Buffalo WHR-G125 Ver.1.47 seems to send EAP-WPS |
| * packets with too short EAPOL header length field |
| * (14 octets). This is fixed in firmware Ver.1.49. |
| * As a workaround, fix the EAPOL header based on the |
| * correct length in the EAP packet. |
| */ |
| wpa_printf(MSG_DEBUG, "EAPOL: Workaround - fix EAPOL " |
| "payload length based on EAP header: " |
| "%d -> %d", (int) plen, elen); |
| plen = elen; |
| } |
| } |
| #endif /* CONFIG_WPS */ |
| data_len = plen + sizeof(*hdr); |
| |
| switch (hdr->type) { |
| case IEEE802_1X_TYPE_EAP_PACKET: |
| if (sm->cached_pmk) { |
| /* Trying to use PMKSA caching, but Authenticator did |
| * not seem to have a matching entry. Need to restart |
| * EAPOL state machines. |
| */ |
| eapol_sm_abort_cached(sm); |
| } |
| wpabuf_free(sm->eapReqData); |
| sm->eapReqData = wpabuf_alloc_copy(hdr + 1, plen); |
| if (sm->eapReqData) { |
| wpa_printf(MSG_DEBUG, "EAPOL: Received EAP-Packet " |
| "frame"); |
| sm->eapolEap = TRUE; |
| eapol_sm_step(sm); |
| } |
| break; |
| case IEEE802_1X_TYPE_EAPOL_KEY: |
| if (plen < sizeof(*key)) { |
| wpa_printf(MSG_DEBUG, "EAPOL: Too short EAPOL-Key " |
| "frame received"); |
| break; |
| } |
| key = (const struct ieee802_1x_eapol_key *) (hdr + 1); |
| if (key->type == EAPOL_KEY_TYPE_WPA || |
| key->type == EAPOL_KEY_TYPE_RSN) { |
| /* WPA Supplicant takes care of this frame. */ |
| wpa_printf(MSG_DEBUG, "EAPOL: Ignoring WPA EAPOL-Key " |
| "frame in EAPOL state machines"); |
| res = 0; |
| break; |
| } |
| if (key->type != EAPOL_KEY_TYPE_RC4) { |
| wpa_printf(MSG_DEBUG, "EAPOL: Ignored unknown " |
| "EAPOL-Key type %d", key->type); |
| break; |
| } |
| os_free(sm->last_rx_key); |
| sm->last_rx_key = os_malloc(data_len); |
| if (sm->last_rx_key) { |
| wpa_printf(MSG_DEBUG, "EAPOL: Received EAPOL-Key " |
| "frame"); |
| os_memcpy(sm->last_rx_key, buf, data_len); |
| sm->last_rx_key_len = data_len; |
| sm->rxKey = TRUE; |
| eapol_sm_step(sm); |
| } |
| break; |
| default: |
| wpa_printf(MSG_DEBUG, "EAPOL: Received unknown EAPOL type %d", |
| hdr->type); |
| sm->dot1xSuppInvalidEapolFramesRx++; |
| break; |
| } |
| |
| return res; |
| } |
| |
| |
| /** |
| * eapol_sm_notify_tx_eapol_key - Notification about transmitted EAPOL packet |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * |
| * Notify EAPOL state machine about transmitted EAPOL packet from an external |
| * component, e.g., WPA. This will update the statistics. |
| */ |
| void eapol_sm_notify_tx_eapol_key(struct eapol_sm *sm) |
| { |
| if (sm) |
| sm->dot1xSuppEapolFramesTx++; |
| } |
| |
| |
| /** |
| * eapol_sm_notify_portEnabled - Notification about portEnabled change |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * @enabled: New portEnabled value |
| * |
| * Notify EAPOL state machine about new portEnabled value. |
| */ |
| void eapol_sm_notify_portEnabled(struct eapol_sm *sm, Boolean enabled) |
| { |
| if (sm == NULL) |
| return; |
| wpa_printf(MSG_DEBUG, "EAPOL: External notification - " |
| "portEnabled=%d", enabled); |
| sm->portEnabled = enabled; |
| eapol_sm_step(sm); |
| } |
| |
| |
| /** |
| * eapol_sm_notify_portValid - Notification about portValid change |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * @valid: New portValid value |
| * |
| * Notify EAPOL state machine about new portValid value. |
| */ |
| void eapol_sm_notify_portValid(struct eapol_sm *sm, Boolean valid) |
| { |
| if (sm == NULL) |
| return; |
| wpa_printf(MSG_DEBUG, "EAPOL: External notification - " |
| "portValid=%d", valid); |
| sm->portValid = valid; |
| eapol_sm_step(sm); |
| } |
| |
| |
| /** |
| * eapol_sm_notify_eap_success - Notification of external EAP success trigger |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * @success: %TRUE = set success, %FALSE = clear success |
| * |
| * Notify the EAPOL state machine that external event has forced EAP state to |
| * success (success = %TRUE). This can be cleared by setting success = %FALSE. |
| * |
| * This function is called to update EAP state when WPA-PSK key handshake has |
| * been completed successfully since WPA-PSK does not use EAP state machine. |
| */ |
| void eapol_sm_notify_eap_success(struct eapol_sm *sm, Boolean success) |
| { |
| if (sm == NULL) |
| return; |
| wpa_printf(MSG_DEBUG, "EAPOL: External notification - " |
| "EAP success=%d", success); |
| sm->eapSuccess = success; |
| sm->altAccept = success; |
| if (success) |
| eap_notify_success(sm->eap); |
| eapol_sm_step(sm); |
| } |
| |
| |
| /** |
| * eapol_sm_notify_eap_fail - Notification of external EAP failure trigger |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * @fail: %TRUE = set failure, %FALSE = clear failure |
| * |
| * Notify EAPOL state machine that external event has forced EAP state to |
| * failure (fail = %TRUE). This can be cleared by setting fail = %FALSE. |
| */ |
| void eapol_sm_notify_eap_fail(struct eapol_sm *sm, Boolean fail) |
| { |
| if (sm == NULL) |
| return; |
| wpa_printf(MSG_DEBUG, "EAPOL: External notification - " |
| "EAP fail=%d", fail); |
| sm->eapFail = fail; |
| sm->altReject = fail; |
| eapol_sm_step(sm); |
| } |
| |
| |
| /** |
| * eapol_sm_notify_config - Notification of EAPOL configuration change |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * @config: Pointer to current network EAP configuration |
| * @conf: Pointer to EAPOL configuration data |
| * |
| * Notify EAPOL state machine that configuration has changed. config will be |
| * stored as a backpointer to network configuration. This can be %NULL to clear |
| * the stored pointed. conf will be copied to local EAPOL/EAP configuration |
| * data. If conf is %NULL, this part of the configuration change will be |
| * skipped. |
| */ |
| void eapol_sm_notify_config(struct eapol_sm *sm, |
| struct eap_peer_config *config, |
| const struct eapol_config *conf) |
| { |
| if (sm == NULL) |
| return; |
| |
| sm->config = config; |
| |
| if (conf == NULL) |
| return; |
| |
| sm->conf.accept_802_1x_keys = conf->accept_802_1x_keys; |
| sm->conf.required_keys = conf->required_keys; |
| sm->conf.fast_reauth = conf->fast_reauth; |
| sm->conf.workaround = conf->workaround; |
| if (sm->eap) { |
| eap_set_fast_reauth(sm->eap, conf->fast_reauth); |
| eap_set_workaround(sm->eap, conf->workaround); |
| eap_set_force_disabled(sm->eap, conf->eap_disabled); |
| } |
| } |
| |
| |
| /** |
| * eapol_sm_get_key - Get master session key (MSK) from EAP |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * @key: Pointer for key buffer |
| * @len: Number of bytes to copy to key |
| * Returns: 0 on success (len of key available), maximum available key len |
| * (>0) if key is available but it is shorter than len, or -1 on failure. |
| * |
| * Fetch EAP keying material (MSK, eapKeyData) from EAP state machine. The key |
| * is available only after a successful authentication. |
| */ |
| int eapol_sm_get_key(struct eapol_sm *sm, u8 *key, size_t len) |
| { |
| const u8 *eap_key; |
| size_t eap_len; |
| |
| if (sm == NULL || !eap_key_available(sm->eap)) { |
| wpa_printf(MSG_DEBUG, "EAPOL: EAP key not available"); |
| return -1; |
| } |
| eap_key = eap_get_eapKeyData(sm->eap, &eap_len); |
| if (eap_key == NULL) { |
| wpa_printf(MSG_DEBUG, "EAPOL: Failed to get eapKeyData"); |
| return -1; |
| } |
| if (len > eap_len) { |
| wpa_printf(MSG_DEBUG, "EAPOL: Requested key length (%lu) not " |
| "available (len=%lu)", |
| (unsigned long) len, (unsigned long) eap_len); |
| return eap_len; |
| } |
| os_memcpy(key, eap_key, len); |
| wpa_printf(MSG_DEBUG, "EAPOL: Successfully fetched key (len=%lu)", |
| (unsigned long) len); |
| return 0; |
| } |
| |
| |
| /** |
| * eapol_sm_notify_logoff - Notification of logon/logoff commands |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * @logoff: Whether command was logoff |
| * |
| * Notify EAPOL state machines that user requested logon/logoff. |
| */ |
| void eapol_sm_notify_logoff(struct eapol_sm *sm, Boolean logoff) |
| { |
| if (sm) { |
| sm->userLogoff = logoff; |
| eapol_sm_step(sm); |
| } |
| } |
| |
| |
| /** |
| * eapol_sm_notify_pmkid_attempt - Notification of successful PMKSA caching |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * |
| * Notify EAPOL state machines that PMKSA caching was successful. This is used |
| * to move EAPOL and EAP state machines into authenticated/successful state. |
| */ |
| void eapol_sm_notify_cached(struct eapol_sm *sm) |
| { |
| if (sm == NULL) |
| return; |
| wpa_printf(MSG_DEBUG, "EAPOL: PMKSA caching was used - skip EAPOL"); |
| sm->eapSuccess = TRUE; |
| eap_notify_success(sm->eap); |
| eapol_sm_step(sm); |
| } |
| |
| |
| /** |
| * eapol_sm_notify_pmkid_attempt - Notification of PMKSA caching |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * @attempt: Whether PMKSA caching is tried |
| * |
| * Notify EAPOL state machines whether PMKSA caching is used. |
| */ |
| void eapol_sm_notify_pmkid_attempt(struct eapol_sm *sm, int attempt) |
| { |
| if (sm == NULL) |
| return; |
| if (attempt) { |
| wpa_printf(MSG_DEBUG, "RSN: Trying to use cached PMKSA"); |
| sm->cached_pmk = TRUE; |
| } else { |
| wpa_printf(MSG_DEBUG, "RSN: Do not try to use cached PMKSA"); |
| sm->cached_pmk = FALSE; |
| } |
| } |
| |
| |
| static void eapol_sm_abort_cached(struct eapol_sm *sm) |
| { |
| wpa_printf(MSG_DEBUG, "RSN: Authenticator did not accept PMKID, " |
| "doing full EAP authentication"); |
| if (sm == NULL) |
| return; |
| sm->cached_pmk = FALSE; |
| sm->SUPP_PAE_state = SUPP_PAE_CONNECTING; |
| sm->suppPortStatus = Unauthorized; |
| eapol_sm_set_port_unauthorized(sm); |
| |
| /* Make sure we do not start sending EAPOL-Start frames first, but |
| * instead move to RESTART state to start EAPOL authentication. */ |
| sm->startWhen = 3; |
| eapol_enable_timer_tick(sm); |
| |
| if (sm->ctx->aborted_cached) |
| sm->ctx->aborted_cached(sm->ctx->ctx); |
| } |
| |
| |
| /** |
| * eapol_sm_register_scard_ctx - Notification of smart card context |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * @ctx: Context data for smart card operations |
| * |
| * Notify EAPOL state machines of context data for smart card operations. This |
| * context data will be used as a parameter for scard_*() functions. |
| */ |
| void eapol_sm_register_scard_ctx(struct eapol_sm *sm, void *ctx) |
| { |
| if (sm) { |
| sm->ctx->scard_ctx = ctx; |
| eap_register_scard_ctx(sm->eap, ctx); |
| } |
| } |
| |
| |
| /** |
| * eapol_sm_notify_portControl - Notification of portControl changes |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * @portControl: New value for portControl variable |
| * |
| * Notify EAPOL state machines that portControl variable has changed. |
| */ |
| void eapol_sm_notify_portControl(struct eapol_sm *sm, PortControl portControl) |
| { |
| if (sm == NULL) |
| return; |
| wpa_printf(MSG_DEBUG, "EAPOL: External notification - " |
| "portControl=%s", eapol_port_control(portControl)); |
| sm->portControl = portControl; |
| eapol_sm_step(sm); |
| } |
| |
| |
| /** |
| * eapol_sm_notify_ctrl_attached - Notification of attached monitor |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * |
| * Notify EAPOL state machines that a monitor was attached to the control |
| * interface to trigger re-sending of pending requests for user input. |
| */ |
| void eapol_sm_notify_ctrl_attached(struct eapol_sm *sm) |
| { |
| if (sm == NULL) |
| return; |
| eap_sm_notify_ctrl_attached(sm->eap); |
| } |
| |
| |
| /** |
| * eapol_sm_notify_ctrl_response - Notification of received user input |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * |
| * Notify EAPOL state machines that a control response, i.e., user |
| * input, was received in order to trigger retrying of a pending EAP request. |
| */ |
| void eapol_sm_notify_ctrl_response(struct eapol_sm *sm) |
| { |
| if (sm == NULL) |
| return; |
| if (sm->eapReqData && !sm->eapReq) { |
| wpa_printf(MSG_DEBUG, "EAPOL: received control response (user " |
| "input) notification - retrying pending EAP " |
| "Request"); |
| sm->eapolEap = TRUE; |
| sm->eapReq = TRUE; |
| eapol_sm_step(sm); |
| } |
| } |
| |
| |
| /** |
| * eapol_sm_request_reauth - Request reauthentication |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * |
| * This function can be used to request EAPOL reauthentication, e.g., when the |
| * current PMKSA entry is nearing expiration. |
| */ |
| void eapol_sm_request_reauth(struct eapol_sm *sm) |
| { |
| if (sm == NULL || sm->SUPP_PAE_state != SUPP_PAE_AUTHENTICATED) |
| return; |
| eapol_sm_txStart(sm); |
| } |
| |
| |
| /** |
| * eapol_sm_notify_lower_layer_success - Notification of lower layer success |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * @in_eapol_sm: Whether the caller is already running inside EAPOL state |
| * machine loop (eapol_sm_step()) |
| * |
| * Notify EAPOL (and EAP) state machines that a lower layer has detected a |
| * successful authentication. This is used to recover from dropped EAP-Success |
| * messages. |
| */ |
| void eapol_sm_notify_lower_layer_success(struct eapol_sm *sm, int in_eapol_sm) |
| { |
| if (sm == NULL) |
| return; |
| eap_notify_lower_layer_success(sm->eap); |
| if (!in_eapol_sm) |
| eapol_sm_step(sm); |
| } |
| |
| |
| /** |
| * eapol_sm_invalidate_cached_session - Mark cached EAP session data invalid |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| */ |
| void eapol_sm_invalidate_cached_session(struct eapol_sm *sm) |
| { |
| if (sm) |
| eap_invalidate_cached_session(sm->eap); |
| } |
| |
| |
| static struct eap_peer_config * eapol_sm_get_config(void *ctx) |
| { |
| struct eapol_sm *sm = ctx; |
| return sm ? sm->config : NULL; |
| } |
| |
| |
| static struct wpabuf * eapol_sm_get_eapReqData(void *ctx) |
| { |
| struct eapol_sm *sm = ctx; |
| if (sm == NULL || sm->eapReqData == NULL) |
| return NULL; |
| |
| return sm->eapReqData; |
| } |
| |
| |
| static Boolean eapol_sm_get_bool(void *ctx, enum eapol_bool_var variable) |
| { |
| struct eapol_sm *sm = ctx; |
| if (sm == NULL) |
| return FALSE; |
| switch (variable) { |
| case EAPOL_eapSuccess: |
| return sm->eapSuccess; |
| case EAPOL_eapRestart: |
| return sm->eapRestart; |
| case EAPOL_eapFail: |
| return sm->eapFail; |
| case EAPOL_eapResp: |
| return sm->eapResp; |
| case EAPOL_eapNoResp: |
| return sm->eapNoResp; |
| case EAPOL_eapReq: |
| return sm->eapReq; |
| case EAPOL_portEnabled: |
| return sm->portEnabled; |
| case EAPOL_altAccept: |
| return sm->altAccept; |
| case EAPOL_altReject: |
| return sm->altReject; |
| } |
| return FALSE; |
| } |
| |
| |
| static void eapol_sm_set_bool(void *ctx, enum eapol_bool_var variable, |
| Boolean value) |
| { |
| struct eapol_sm *sm = ctx; |
| if (sm == NULL) |
| return; |
| switch (variable) { |
| case EAPOL_eapSuccess: |
| sm->eapSuccess = value; |
| break; |
| case EAPOL_eapRestart: |
| sm->eapRestart = value; |
| break; |
| case EAPOL_eapFail: |
| sm->eapFail = value; |
| break; |
| case EAPOL_eapResp: |
| sm->eapResp = value; |
| break; |
| case EAPOL_eapNoResp: |
| sm->eapNoResp = value; |
| break; |
| case EAPOL_eapReq: |
| sm->eapReq = value; |
| break; |
| case EAPOL_portEnabled: |
| sm->portEnabled = value; |
| break; |
| case EAPOL_altAccept: |
| sm->altAccept = value; |
| break; |
| case EAPOL_altReject: |
| sm->altReject = value; |
| break; |
| } |
| } |
| |
| |
| static unsigned int eapol_sm_get_int(void *ctx, enum eapol_int_var variable) |
| { |
| struct eapol_sm *sm = ctx; |
| if (sm == NULL) |
| return 0; |
| switch (variable) { |
| case EAPOL_idleWhile: |
| return sm->idleWhile; |
| } |
| return 0; |
| } |
| |
| |
| static void eapol_sm_set_int(void *ctx, enum eapol_int_var variable, |
| unsigned int value) |
| { |
| struct eapol_sm *sm = ctx; |
| if (sm == NULL) |
| return; |
| switch (variable) { |
| case EAPOL_idleWhile: |
| sm->idleWhile = value; |
| if (sm->idleWhile > 0) |
| eapol_enable_timer_tick(sm); |
| break; |
| } |
| } |
| |
| |
| static void eapol_sm_set_config_blob(void *ctx, struct wpa_config_blob *blob) |
| { |
| #ifndef CONFIG_NO_CONFIG_BLOBS |
| struct eapol_sm *sm = ctx; |
| if (sm && sm->ctx && sm->ctx->set_config_blob) |
| sm->ctx->set_config_blob(sm->ctx->ctx, blob); |
| #endif /* CONFIG_NO_CONFIG_BLOBS */ |
| } |
| |
| |
| static const struct wpa_config_blob * |
| eapol_sm_get_config_blob(void *ctx, const char *name) |
| { |
| #ifndef CONFIG_NO_CONFIG_BLOBS |
| struct eapol_sm *sm = ctx; |
| if (sm && sm->ctx && sm->ctx->get_config_blob) |
| return sm->ctx->get_config_blob(sm->ctx->ctx, name); |
| else |
| return NULL; |
| #else /* CONFIG_NO_CONFIG_BLOBS */ |
| return NULL; |
| #endif /* CONFIG_NO_CONFIG_BLOBS */ |
| } |
| |
| |
| static void eapol_sm_notify_pending(void *ctx) |
| { |
| struct eapol_sm *sm = ctx; |
| if (sm == NULL) |
| return; |
| if (sm->eapReqData && !sm->eapReq) { |
| wpa_printf(MSG_DEBUG, "EAPOL: received notification from EAP " |
| "state machine - retrying pending EAP Request"); |
| sm->eapolEap = TRUE; |
| sm->eapReq = TRUE; |
| eapol_sm_step(sm); |
| } |
| } |
| |
| |
| #if defined(CONFIG_CTRL_IFACE) || !defined(CONFIG_NO_STDOUT_DEBUG) |
| static void eapol_sm_eap_param_needed(void *ctx, enum wpa_ctrl_req_type field, |
| const char *txt) |
| { |
| struct eapol_sm *sm = ctx; |
| wpa_printf(MSG_DEBUG, "EAPOL: EAP parameter needed"); |
| if (sm->ctx->eap_param_needed) |
| sm->ctx->eap_param_needed(sm->ctx->ctx, field, txt); |
| } |
| #else /* CONFIG_CTRL_IFACE || !CONFIG_NO_STDOUT_DEBUG */ |
| #define eapol_sm_eap_param_needed NULL |
| #endif /* CONFIG_CTRL_IFACE || !CONFIG_NO_STDOUT_DEBUG */ |
| |
| static void eapol_sm_notify_cert(void *ctx, int depth, const char *subject, |
| const char *cert_hash, |
| const struct wpabuf *cert) |
| { |
| struct eapol_sm *sm = ctx; |
| if (sm->ctx->cert_cb) |
| sm->ctx->cert_cb(sm->ctx->ctx, depth, subject, |
| cert_hash, cert); |
| } |
| |
| |
| static void eapol_sm_notify_status(void *ctx, const char *status, |
| const char *parameter) |
| { |
| struct eapol_sm *sm = ctx; |
| |
| if (sm->ctx->status_cb) |
| sm->ctx->status_cb(sm->ctx->ctx, status, parameter); |
| } |
| |
| |
| static void eapol_sm_set_anon_id(void *ctx, const u8 *id, size_t len) |
| { |
| struct eapol_sm *sm = ctx; |
| |
| if (sm->ctx->set_anon_id) |
| sm->ctx->set_anon_id(sm->ctx->ctx, id, len); |
| } |
| |
| |
| static struct eapol_callbacks eapol_cb = |
| { |
| eapol_sm_get_config, |
| eapol_sm_get_bool, |
| eapol_sm_set_bool, |
| eapol_sm_get_int, |
| eapol_sm_set_int, |
| eapol_sm_get_eapReqData, |
| eapol_sm_set_config_blob, |
| eapol_sm_get_config_blob, |
| eapol_sm_notify_pending, |
| eapol_sm_eap_param_needed, |
| eapol_sm_notify_cert, |
| eapol_sm_notify_status, |
| eapol_sm_set_anon_id |
| }; |
| |
| |
| /** |
| * eapol_sm_init - Initialize EAPOL state machine |
| * @ctx: Pointer to EAPOL context data; this needs to be an allocated buffer |
| * and EAPOL state machine will free it in eapol_sm_deinit() |
| * Returns: Pointer to the allocated EAPOL state machine or %NULL on failure |
| * |
| * Allocate and initialize an EAPOL state machine. |
| */ |
| struct eapol_sm *eapol_sm_init(struct eapol_ctx *ctx) |
| { |
| struct eapol_sm *sm; |
| struct eap_config conf; |
| sm = os_zalloc(sizeof(*sm)); |
| if (sm == NULL) |
| return NULL; |
| sm->ctx = ctx; |
| |
| sm->portControl = Auto; |
| |
| /* Supplicant PAE state machine */ |
| sm->heldPeriod = 60; |
| sm->startPeriod = 30; |
| sm->maxStart = 3; |
| |
| /* Supplicant Backend state machine */ |
| sm->authPeriod = 30; |
| |
| os_memset(&conf, 0, sizeof(conf)); |
| conf.opensc_engine_path = ctx->opensc_engine_path; |
| conf.pkcs11_engine_path = ctx->pkcs11_engine_path; |
| conf.pkcs11_module_path = ctx->pkcs11_module_path; |
| conf.wps = ctx->wps; |
| conf.cert_in_cb = ctx->cert_in_cb; |
| |
| sm->eap = eap_peer_sm_init(sm, &eapol_cb, sm->ctx->msg_ctx, &conf); |
| if (sm->eap == NULL) { |
| os_free(sm); |
| return NULL; |
| } |
| |
| /* Initialize EAPOL state machines */ |
| sm->initialize = TRUE; |
| eapol_sm_step(sm); |
| sm->initialize = FALSE; |
| eapol_sm_step(sm); |
| |
| sm->timer_tick_enabled = 1; |
| eloop_register_timeout(1, 0, eapol_port_timers_tick, NULL, sm); |
| |
| return sm; |
| } |
| |
| |
| /** |
| * eapol_sm_deinit - Deinitialize EAPOL state machine |
| * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() |
| * |
| * Deinitialize and free EAPOL state machine. |
| */ |
| void eapol_sm_deinit(struct eapol_sm *sm) |
| { |
| if (sm == NULL) |
| return; |
| eloop_cancel_timeout(eapol_sm_step_timeout, NULL, sm); |
| eloop_cancel_timeout(eapol_port_timers_tick, NULL, sm); |
| eap_peer_sm_deinit(sm->eap); |
| os_free(sm->last_rx_key); |
| wpabuf_free(sm->eapReqData); |
| os_free(sm->ctx); |
| os_free(sm); |
| } |
| |
| |
| void eapol_sm_set_ext_pw_ctx(struct eapol_sm *sm, |
| struct ext_password_data *ext) |
| { |
| if (sm && sm->eap) |
| eap_sm_set_ext_pw_ctx(sm->eap, ext); |
| } |
| |
| |
| int eapol_sm_failed(struct eapol_sm *sm) |
| { |
| if (sm == NULL) |
| return 0; |
| return !sm->eapSuccess && sm->eapFail; |
| } |