hostap/src/eapol_auth/eapol_auth_sm.c
Jouni Malinen dee2020243 EAPOL auth: clear keyRun in AUTH_PAE INITIALIZE
Clearing keyRun here is not specified in IEEE Std 802.1X-2004, but it
looks like this would be logical thing to do here since the EAPOL-Key
exchange is not possible in this state. It is possible to get here on
disconnection event without advancing to the AUTHENTICATING state to
clear keyRun before the IEEE 802.11 RSN authenticator state machine runs
and that may advance from AUTHENTICATION2 to INITPMK if keyRun = TRUE
has been left from the last association. This can be avoided by clearing
keyRun here.

It was possible to hit this corner case in the hwsim test case
ap_wpa2_eap_eke_server_oom in the case getKey operation was forced to
fail memory allocation. The following association resulted in the
station getting disconnected when entering INITPMK without going through
EAP authentication.

Signed-off-by: Jouni Malinen <jouni@qca.qualcomm.com>
2015-08-28 16:30:06 +03:00

1321 lines
33 KiB
C

/*
* IEEE 802.1X-2004 Authenticator - EAPOL state machine
* Copyright (c) 2002-2015, 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 "eloop.h"
#include "state_machine.h"
#include "common/eapol_common.h"
#include "eap_common/eap_defs.h"
#include "eap_common/eap_common.h"
#include "eap_server/eap.h"
#include "eapol_auth_sm.h"
#include "eapol_auth_sm_i.h"
#define STATE_MACHINE_DATA struct eapol_state_machine
#define STATE_MACHINE_DEBUG_PREFIX "IEEE 802.1X"
#define STATE_MACHINE_ADDR sm->addr
static const struct eapol_callbacks eapol_cb;
/* EAPOL state machines are described in IEEE Std 802.1X-2004, Chap. 8.2 */
#define setPortAuthorized() \
sm->eapol->cb.set_port_authorized(sm->eapol->conf.ctx, sm->sta, 1)
#define setPortUnauthorized() \
sm->eapol->cb.set_port_authorized(sm->eapol->conf.ctx, sm->sta, 0)
/* procedures */
#define txCannedFail() eapol_auth_tx_canned_eap(sm, 0)
#define txCannedSuccess() eapol_auth_tx_canned_eap(sm, 1)
#define txReq() eapol_auth_tx_req(sm)
#define abortAuth() sm->eapol->cb.abort_auth(sm->eapol->conf.ctx, sm->sta)
#define txKey() sm->eapol->cb.tx_key(sm->eapol->conf.ctx, sm->sta)
#define processKey() do { } while (0)
static void eapol_sm_step_run(struct eapol_state_machine *sm);
static void eapol_sm_step_cb(void *eloop_ctx, void *timeout_ctx);
static void eapol_auth_initialize(struct eapol_state_machine *sm);
static void eapol_auth_conf_free(struct eapol_auth_config *conf);
static void eapol_auth_logger(struct eapol_authenticator *eapol,
const u8 *addr, eapol_logger_level level,
const char *txt)
{
if (eapol->cb.logger == NULL)
return;
eapol->cb.logger(eapol->conf.ctx, addr, level, txt);
}
static void eapol_auth_vlogger(struct eapol_authenticator *eapol,
const u8 *addr, eapol_logger_level level,
const char *fmt, ...)
{
char *format;
int maxlen;
va_list ap;
if (eapol->cb.logger == NULL)
return;
maxlen = os_strlen(fmt) + 100;
format = os_malloc(maxlen);
if (!format)
return;
va_start(ap, fmt);
vsnprintf(format, maxlen, fmt, ap);
va_end(ap);
eapol_auth_logger(eapol, addr, level, format);
os_free(format);
}
static void eapol_auth_tx_canned_eap(struct eapol_state_machine *sm,
int success)
{
struct eap_hdr eap;
os_memset(&eap, 0, sizeof(eap));
eap.code = success ? EAP_CODE_SUCCESS : EAP_CODE_FAILURE;
eap.identifier = ++sm->last_eap_id;
eap.length = host_to_be16(sizeof(eap));
eapol_auth_vlogger(sm->eapol, sm->addr, EAPOL_LOGGER_DEBUG,
"Sending canned EAP packet %s (identifier %d)",
success ? "SUCCESS" : "FAILURE", eap.identifier);
sm->eapol->cb.eapol_send(sm->eapol->conf.ctx, sm->sta,
IEEE802_1X_TYPE_EAP_PACKET,
(u8 *) &eap, sizeof(eap));
sm->dot1xAuthEapolFramesTx++;
}
static void eapol_auth_tx_req(struct eapol_state_machine *sm)
{
if (sm->eap_if->eapReqData == NULL ||
wpabuf_len(sm->eap_if->eapReqData) < sizeof(struct eap_hdr)) {
eapol_auth_logger(sm->eapol, sm->addr,
EAPOL_LOGGER_DEBUG,
"TxReq called, but there is no EAP request "
"from authentication server");
return;
}
if (sm->flags & EAPOL_SM_WAIT_START) {
wpa_printf(MSG_DEBUG, "EAPOL: Drop EAPOL TX to " MACSTR
" while waiting for EAPOL-Start",
MAC2STR(sm->addr));
return;
}
sm->last_eap_id = eap_get_id(sm->eap_if->eapReqData);
eapol_auth_vlogger(sm->eapol, sm->addr, EAPOL_LOGGER_DEBUG,
"Sending EAP Packet (identifier %d)",
sm->last_eap_id);
sm->eapol->cb.eapol_send(sm->eapol->conf.ctx, sm->sta,
IEEE802_1X_TYPE_EAP_PACKET,
wpabuf_head(sm->eap_if->eapReqData),
wpabuf_len(sm->eap_if->eapReqData));
sm->dot1xAuthEapolFramesTx++;
if (eap_get_type(sm->eap_if->eapReqData) == EAP_TYPE_IDENTITY)
sm->dot1xAuthEapolReqIdFramesTx++;
else
sm->dot1xAuthEapolReqFramesTx++;
}
/**
* eapol_port_timers_tick - Port Timers state machine
* @eloop_ctx: struct eapol_state_machine *
* @timeout_ctx: Not used
*
* This statemachine is 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_state_machine *state = timeout_ctx;
if (state->aWhile > 0) {
state->aWhile--;
if (state->aWhile == 0) {
wpa_printf(MSG_DEBUG, "IEEE 802.1X: " MACSTR
" - aWhile --> 0",
MAC2STR(state->addr));
}
}
if (state->quietWhile > 0) {
state->quietWhile--;
if (state->quietWhile == 0) {
wpa_printf(MSG_DEBUG, "IEEE 802.1X: " MACSTR
" - quietWhile --> 0",
MAC2STR(state->addr));
}
}
if (state->reAuthWhen > 0) {
state->reAuthWhen--;
if (state->reAuthWhen == 0) {
wpa_printf(MSG_DEBUG, "IEEE 802.1X: " MACSTR
" - reAuthWhen --> 0",
MAC2STR(state->addr));
}
}
if (state->eap_if->retransWhile > 0) {
state->eap_if->retransWhile--;
if (state->eap_if->retransWhile == 0) {
wpa_printf(MSG_DEBUG, "IEEE 802.1X: " MACSTR
" - (EAP) retransWhile --> 0",
MAC2STR(state->addr));
}
}
eapol_sm_step_run(state);
eloop_register_timeout(1, 0, eapol_port_timers_tick, eloop_ctx, state);
}
/* Authenticator PAE state machine */
SM_STATE(AUTH_PAE, INITIALIZE)
{
SM_ENTRY_MA(AUTH_PAE, INITIALIZE, auth_pae);
sm->portMode = Auto;
/*
* Clearing keyRun here is not specified in IEEE Std 802.1X-2004, but
* it looks like this would be logical thing to do here since the
* EAPOL-Key exchange is not possible in this state. It is possible to
* get here on disconnection event without advancing to the
* AUTHENTICATING state to clear keyRun before the IEEE 802.11 RSN
* authenticator state machine runs and that may advance from
* AUTHENTICATION2 to INITPMK if keyRun = TRUE has been left from the
* last association. This can be avoided by clearing keyRun here.
*/
sm->keyRun = FALSE;
}
SM_STATE(AUTH_PAE, DISCONNECTED)
{
int from_initialize = sm->auth_pae_state == AUTH_PAE_INITIALIZE;
if (sm->eapolLogoff) {
if (sm->auth_pae_state == AUTH_PAE_CONNECTING)
sm->authEapLogoffsWhileConnecting++;
else if (sm->auth_pae_state == AUTH_PAE_AUTHENTICATED)
sm->authAuthEapLogoffWhileAuthenticated++;
}
SM_ENTRY_MA(AUTH_PAE, DISCONNECTED, auth_pae);
sm->authPortStatus = Unauthorized;
setPortUnauthorized();
sm->reAuthCount = 0;
sm->eapolLogoff = FALSE;
if (!from_initialize) {
sm->eapol->cb.finished(sm->eapol->conf.ctx, sm->sta, 0,
sm->flags & EAPOL_SM_PREAUTH,
sm->remediation);
}
}
SM_STATE(AUTH_PAE, RESTART)
{
if (sm->auth_pae_state == AUTH_PAE_AUTHENTICATED) {
if (sm->reAuthenticate)
sm->authAuthReauthsWhileAuthenticated++;
if (sm->eapolStart)
sm->authAuthEapStartsWhileAuthenticated++;
if (sm->eapolLogoff)
sm->authAuthEapLogoffWhileAuthenticated++;
}
SM_ENTRY_MA(AUTH_PAE, RESTART, auth_pae);
sm->eap_if->eapRestart = TRUE;
}
SM_STATE(AUTH_PAE, CONNECTING)
{
if (sm->auth_pae_state != AUTH_PAE_CONNECTING)
sm->authEntersConnecting++;
SM_ENTRY_MA(AUTH_PAE, CONNECTING, auth_pae);
sm->reAuthenticate = FALSE;
sm->reAuthCount++;
}
SM_STATE(AUTH_PAE, HELD)
{
if (sm->auth_pae_state == AUTH_PAE_AUTHENTICATING && sm->authFail)
sm->authAuthFailWhileAuthenticating++;
SM_ENTRY_MA(AUTH_PAE, HELD, auth_pae);
sm->authPortStatus = Unauthorized;
setPortUnauthorized();
sm->quietWhile = sm->quietPeriod;
sm->eapolLogoff = FALSE;
eapol_auth_vlogger(sm->eapol, sm->addr, EAPOL_LOGGER_WARNING,
"authentication failed - EAP type: %d (%s)",
sm->eap_type_authsrv,
eap_server_get_name(0, sm->eap_type_authsrv));
if (sm->eap_type_authsrv != sm->eap_type_supp) {
eapol_auth_vlogger(sm->eapol, sm->addr, EAPOL_LOGGER_INFO,
"Supplicant used different EAP type: "
"%d (%s)", sm->eap_type_supp,
eap_server_get_name(0, sm->eap_type_supp));
}
sm->eapol->cb.finished(sm->eapol->conf.ctx, sm->sta, 0,
sm->flags & EAPOL_SM_PREAUTH, sm->remediation);
}
SM_STATE(AUTH_PAE, AUTHENTICATED)
{
char *extra = "";
if (sm->auth_pae_state == AUTH_PAE_AUTHENTICATING && sm->authSuccess)
sm->authAuthSuccessesWhileAuthenticating++;
SM_ENTRY_MA(AUTH_PAE, AUTHENTICATED, auth_pae);
sm->authPortStatus = Authorized;
setPortAuthorized();
sm->reAuthCount = 0;
if (sm->flags & EAPOL_SM_PREAUTH)
extra = " (pre-authentication)";
else if (sm->flags & EAPOL_SM_FROM_PMKSA_CACHE)
extra = " (PMKSA cache)";
eapol_auth_vlogger(sm->eapol, sm->addr, EAPOL_LOGGER_INFO,
"authenticated - EAP type: %d (%s)%s",
sm->eap_type_authsrv,
eap_server_get_name(0, sm->eap_type_authsrv),
extra);
sm->eapol->cb.finished(sm->eapol->conf.ctx, sm->sta, 1,
sm->flags & EAPOL_SM_PREAUTH, sm->remediation);
}
SM_STATE(AUTH_PAE, AUTHENTICATING)
{
SM_ENTRY_MA(AUTH_PAE, AUTHENTICATING, auth_pae);
sm->eapolStart = FALSE;
sm->authSuccess = FALSE;
sm->authFail = FALSE;
sm->authTimeout = FALSE;
sm->authStart = TRUE;
sm->keyRun = FALSE;
sm->keyDone = FALSE;
}
SM_STATE(AUTH_PAE, ABORTING)
{
if (sm->auth_pae_state == AUTH_PAE_AUTHENTICATING) {
if (sm->authTimeout)
sm->authAuthTimeoutsWhileAuthenticating++;
if (sm->eapolStart)
sm->authAuthEapStartsWhileAuthenticating++;
if (sm->eapolLogoff)
sm->authAuthEapLogoffWhileAuthenticating++;
}
SM_ENTRY_MA(AUTH_PAE, ABORTING, auth_pae);
sm->authAbort = TRUE;
sm->keyRun = FALSE;
sm->keyDone = FALSE;
}
SM_STATE(AUTH_PAE, FORCE_AUTH)
{
SM_ENTRY_MA(AUTH_PAE, FORCE_AUTH, auth_pae);
sm->authPortStatus = Authorized;
setPortAuthorized();
sm->portMode = ForceAuthorized;
sm->eapolStart = FALSE;
txCannedSuccess();
}
SM_STATE(AUTH_PAE, FORCE_UNAUTH)
{
SM_ENTRY_MA(AUTH_PAE, FORCE_UNAUTH, auth_pae);
sm->authPortStatus = Unauthorized;
setPortUnauthorized();
sm->portMode = ForceUnauthorized;
sm->eapolStart = FALSE;
txCannedFail();
}
SM_STEP(AUTH_PAE)
{
if ((sm->portControl == Auto && sm->portMode != sm->portControl) ||
sm->initialize || !sm->eap_if->portEnabled)
SM_ENTER_GLOBAL(AUTH_PAE, INITIALIZE);
else if (sm->portControl == ForceAuthorized &&
sm->portMode != sm->portControl &&
!(sm->initialize || !sm->eap_if->portEnabled))
SM_ENTER_GLOBAL(AUTH_PAE, FORCE_AUTH);
else if (sm->portControl == ForceUnauthorized &&
sm->portMode != sm->portControl &&
!(sm->initialize || !sm->eap_if->portEnabled))
SM_ENTER_GLOBAL(AUTH_PAE, FORCE_UNAUTH);
else {
switch (sm->auth_pae_state) {
case AUTH_PAE_INITIALIZE:
SM_ENTER(AUTH_PAE, DISCONNECTED);
break;
case AUTH_PAE_DISCONNECTED:
SM_ENTER(AUTH_PAE, RESTART);
break;
case AUTH_PAE_RESTART:
if (!sm->eap_if->eapRestart)
SM_ENTER(AUTH_PAE, CONNECTING);
break;
case AUTH_PAE_HELD:
if (sm->quietWhile == 0)
SM_ENTER(AUTH_PAE, RESTART);
break;
case AUTH_PAE_CONNECTING:
if (sm->eapolLogoff || sm->reAuthCount > sm->reAuthMax)
SM_ENTER(AUTH_PAE, DISCONNECTED);
else if ((sm->eap_if->eapReq &&
sm->reAuthCount <= sm->reAuthMax) ||
sm->eap_if->eapSuccess || sm->eap_if->eapFail)
SM_ENTER(AUTH_PAE, AUTHENTICATING);
break;
case AUTH_PAE_AUTHENTICATED:
if (sm->eapolStart || sm->reAuthenticate)
SM_ENTER(AUTH_PAE, RESTART);
else if (sm->eapolLogoff || !sm->portValid)
SM_ENTER(AUTH_PAE, DISCONNECTED);
break;
case AUTH_PAE_AUTHENTICATING:
if (sm->authSuccess && sm->portValid)
SM_ENTER(AUTH_PAE, AUTHENTICATED);
else if (sm->authFail ||
(sm->keyDone && !sm->portValid))
SM_ENTER(AUTH_PAE, HELD);
else if (sm->eapolStart || sm->eapolLogoff ||
sm->authTimeout)
SM_ENTER(AUTH_PAE, ABORTING);
break;
case AUTH_PAE_ABORTING:
if (sm->eapolLogoff && !sm->authAbort)
SM_ENTER(AUTH_PAE, DISCONNECTED);
else if (!sm->eapolLogoff && !sm->authAbort)
SM_ENTER(AUTH_PAE, RESTART);
break;
case AUTH_PAE_FORCE_AUTH:
if (sm->eapolStart)
SM_ENTER(AUTH_PAE, FORCE_AUTH);
break;
case AUTH_PAE_FORCE_UNAUTH:
if (sm->eapolStart)
SM_ENTER(AUTH_PAE, FORCE_UNAUTH);
break;
}
}
}
/* Backend Authentication state machine */
SM_STATE(BE_AUTH, INITIALIZE)
{
SM_ENTRY_MA(BE_AUTH, INITIALIZE, be_auth);
abortAuth();
sm->eap_if->eapNoReq = FALSE;
sm->authAbort = FALSE;
}
SM_STATE(BE_AUTH, REQUEST)
{
SM_ENTRY_MA(BE_AUTH, REQUEST, be_auth);
txReq();
sm->eap_if->eapReq = FALSE;
sm->backendOtherRequestsToSupplicant++;
/*
* Clearing eapolEap here is not specified in IEEE Std 802.1X-2004, but
* it looks like this would be logical thing to do there since the old
* EAP response would not be valid anymore after the new EAP request
* was sent out.
*
* A race condition has been reported, in which hostapd ended up
* sending out EAP-Response/Identity as a response to the first
* EAP-Request from the main EAP method. This can be avoided by
* clearing eapolEap here.
*/
sm->eapolEap = FALSE;
}
SM_STATE(BE_AUTH, RESPONSE)
{
SM_ENTRY_MA(BE_AUTH, RESPONSE, be_auth);
sm->authTimeout = FALSE;
sm->eapolEap = FALSE;
sm->eap_if->eapNoReq = FALSE;
sm->aWhile = sm->serverTimeout;
sm->eap_if->eapResp = TRUE;
/* sendRespToServer(); */
sm->backendResponses++;
}
SM_STATE(BE_AUTH, SUCCESS)
{
SM_ENTRY_MA(BE_AUTH, SUCCESS, be_auth);
txReq();
sm->authSuccess = TRUE;
sm->keyRun = TRUE;
}
SM_STATE(BE_AUTH, FAIL)
{
SM_ENTRY_MA(BE_AUTH, FAIL, be_auth);
txReq();
sm->authFail = TRUE;
}
SM_STATE(BE_AUTH, TIMEOUT)
{
SM_ENTRY_MA(BE_AUTH, TIMEOUT, be_auth);
sm->authTimeout = TRUE;
}
SM_STATE(BE_AUTH, IDLE)
{
SM_ENTRY_MA(BE_AUTH, IDLE, be_auth);
sm->authStart = FALSE;
}
SM_STATE(BE_AUTH, IGNORE)
{
SM_ENTRY_MA(BE_AUTH, IGNORE, be_auth);
sm->eap_if->eapNoReq = FALSE;
}
SM_STEP(BE_AUTH)
{
if (sm->portControl != Auto || sm->initialize || sm->authAbort) {
SM_ENTER_GLOBAL(BE_AUTH, INITIALIZE);
return;
}
switch (sm->be_auth_state) {
case BE_AUTH_INITIALIZE:
SM_ENTER(BE_AUTH, IDLE);
break;
case BE_AUTH_REQUEST:
if (sm->eapolEap)
SM_ENTER(BE_AUTH, RESPONSE);
else if (sm->eap_if->eapReq)
SM_ENTER(BE_AUTH, REQUEST);
else if (sm->eap_if->eapTimeout)
SM_ENTER(BE_AUTH, TIMEOUT);
break;
case BE_AUTH_RESPONSE:
if (sm->eap_if->eapNoReq)
SM_ENTER(BE_AUTH, IGNORE);
if (sm->eap_if->eapReq) {
sm->backendAccessChallenges++;
SM_ENTER(BE_AUTH, REQUEST);
} else if (sm->aWhile == 0)
SM_ENTER(BE_AUTH, TIMEOUT);
else if (sm->eap_if->eapFail) {
sm->backendAuthFails++;
SM_ENTER(BE_AUTH, FAIL);
} else if (sm->eap_if->eapSuccess) {
sm->backendAuthSuccesses++;
SM_ENTER(BE_AUTH, SUCCESS);
}
break;
case BE_AUTH_SUCCESS:
SM_ENTER(BE_AUTH, IDLE);
break;
case BE_AUTH_FAIL:
SM_ENTER(BE_AUTH, IDLE);
break;
case BE_AUTH_TIMEOUT:
SM_ENTER(BE_AUTH, IDLE);
break;
case BE_AUTH_IDLE:
if (sm->eap_if->eapFail && sm->authStart)
SM_ENTER(BE_AUTH, FAIL);
else if (sm->eap_if->eapReq && sm->authStart)
SM_ENTER(BE_AUTH, REQUEST);
else if (sm->eap_if->eapSuccess && sm->authStart)
SM_ENTER(BE_AUTH, SUCCESS);
break;
case BE_AUTH_IGNORE:
if (sm->eapolEap)
SM_ENTER(BE_AUTH, RESPONSE);
else if (sm->eap_if->eapReq)
SM_ENTER(BE_AUTH, REQUEST);
else if (sm->eap_if->eapTimeout)
SM_ENTER(BE_AUTH, TIMEOUT);
break;
}
}
/* Reauthentication Timer state machine */
SM_STATE(REAUTH_TIMER, INITIALIZE)
{
SM_ENTRY_MA(REAUTH_TIMER, INITIALIZE, reauth_timer);
sm->reAuthWhen = sm->reAuthPeriod;
}
SM_STATE(REAUTH_TIMER, REAUTHENTICATE)
{
SM_ENTRY_MA(REAUTH_TIMER, REAUTHENTICATE, reauth_timer);
sm->reAuthenticate = TRUE;
sm->eapol->cb.eapol_event(sm->eapol->conf.ctx, sm->sta,
EAPOL_AUTH_REAUTHENTICATE);
}
SM_STEP(REAUTH_TIMER)
{
if (sm->portControl != Auto || sm->initialize ||
sm->authPortStatus == Unauthorized || !sm->reAuthEnabled) {
SM_ENTER_GLOBAL(REAUTH_TIMER, INITIALIZE);
return;
}
switch (sm->reauth_timer_state) {
case REAUTH_TIMER_INITIALIZE:
if (sm->reAuthWhen == 0)
SM_ENTER(REAUTH_TIMER, REAUTHENTICATE);
break;
case REAUTH_TIMER_REAUTHENTICATE:
SM_ENTER(REAUTH_TIMER, INITIALIZE);
break;
}
}
/* Authenticator Key Transmit state machine */
SM_STATE(AUTH_KEY_TX, NO_KEY_TRANSMIT)
{
SM_ENTRY_MA(AUTH_KEY_TX, NO_KEY_TRANSMIT, auth_key_tx);
}
SM_STATE(AUTH_KEY_TX, KEY_TRANSMIT)
{
SM_ENTRY_MA(AUTH_KEY_TX, KEY_TRANSMIT, auth_key_tx);
txKey();
sm->eap_if->eapKeyAvailable = FALSE;
sm->keyDone = TRUE;
}
SM_STEP(AUTH_KEY_TX)
{
if (sm->initialize || sm->portControl != Auto) {
SM_ENTER_GLOBAL(AUTH_KEY_TX, NO_KEY_TRANSMIT);
return;
}
switch (sm->auth_key_tx_state) {
case AUTH_KEY_TX_NO_KEY_TRANSMIT:
if (sm->keyTxEnabled && sm->eap_if->eapKeyAvailable &&
sm->keyRun && !(sm->flags & EAPOL_SM_USES_WPA))
SM_ENTER(AUTH_KEY_TX, KEY_TRANSMIT);
break;
case AUTH_KEY_TX_KEY_TRANSMIT:
if (!sm->keyTxEnabled || !sm->keyRun)
SM_ENTER(AUTH_KEY_TX, NO_KEY_TRANSMIT);
else if (sm->eap_if->eapKeyAvailable)
SM_ENTER(AUTH_KEY_TX, KEY_TRANSMIT);
break;
}
}
/* Key Receive state machine */
SM_STATE(KEY_RX, NO_KEY_RECEIVE)
{
SM_ENTRY_MA(KEY_RX, NO_KEY_RECEIVE, key_rx);
}
SM_STATE(KEY_RX, KEY_RECEIVE)
{
SM_ENTRY_MA(KEY_RX, KEY_RECEIVE, key_rx);
processKey();
sm->rxKey = FALSE;
}
SM_STEP(KEY_RX)
{
if (sm->initialize || !sm->eap_if->portEnabled) {
SM_ENTER_GLOBAL(KEY_RX, NO_KEY_RECEIVE);
return;
}
switch (sm->key_rx_state) {
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;
}
}
/* Controlled Directions state machine */
SM_STATE(CTRL_DIR, FORCE_BOTH)
{
SM_ENTRY_MA(CTRL_DIR, FORCE_BOTH, ctrl_dir);
sm->operControlledDirections = Both;
}
SM_STATE(CTRL_DIR, IN_OR_BOTH)
{
SM_ENTRY_MA(CTRL_DIR, IN_OR_BOTH, ctrl_dir);
sm->operControlledDirections = sm->adminControlledDirections;
}
SM_STEP(CTRL_DIR)
{
if (sm->initialize) {
SM_ENTER_GLOBAL(CTRL_DIR, IN_OR_BOTH);
return;
}
switch (sm->ctrl_dir_state) {
case CTRL_DIR_FORCE_BOTH:
if (sm->eap_if->portEnabled && sm->operEdge)
SM_ENTER(CTRL_DIR, IN_OR_BOTH);
break;
case CTRL_DIR_IN_OR_BOTH:
if (sm->operControlledDirections !=
sm->adminControlledDirections)
SM_ENTER(CTRL_DIR, IN_OR_BOTH);
if (!sm->eap_if->portEnabled || !sm->operEdge)
SM_ENTER(CTRL_DIR, FORCE_BOTH);
break;
}
}
struct eapol_state_machine *
eapol_auth_alloc(struct eapol_authenticator *eapol, const u8 *addr,
int flags, const struct wpabuf *assoc_wps_ie,
const struct wpabuf *assoc_p2p_ie, void *sta_ctx,
const char *identity, const char *radius_cui)
{
struct eapol_state_machine *sm;
struct eap_config eap_conf;
if (eapol == NULL)
return NULL;
sm = os_zalloc(sizeof(*sm));
if (sm == NULL) {
wpa_printf(MSG_DEBUG, "IEEE 802.1X state machine allocation "
"failed");
return NULL;
}
sm->radius_identifier = -1;
os_memcpy(sm->addr, addr, ETH_ALEN);
sm->flags = flags;
sm->eapol = eapol;
sm->sta = sta_ctx;
/* Set default values for state machine constants */
sm->auth_pae_state = AUTH_PAE_INITIALIZE;
sm->quietPeriod = AUTH_PAE_DEFAULT_quietPeriod;
sm->reAuthMax = AUTH_PAE_DEFAULT_reAuthMax;
sm->be_auth_state = BE_AUTH_INITIALIZE;
sm->serverTimeout = BE_AUTH_DEFAULT_serverTimeout;
sm->reauth_timer_state = REAUTH_TIMER_INITIALIZE;
sm->reAuthPeriod = eapol->conf.eap_reauth_period;
sm->reAuthEnabled = eapol->conf.eap_reauth_period > 0 ? TRUE : FALSE;
sm->auth_key_tx_state = AUTH_KEY_TX_NO_KEY_TRANSMIT;
sm->key_rx_state = KEY_RX_NO_KEY_RECEIVE;
sm->ctrl_dir_state = CTRL_DIR_IN_OR_BOTH;
sm->portControl = Auto;
if (!eapol->conf.wpa &&
(eapol->default_wep_key || eapol->conf.individual_wep_key_len > 0))
sm->keyTxEnabled = TRUE;
else
sm->keyTxEnabled = FALSE;
if (eapol->conf.wpa)
sm->portValid = FALSE;
else
sm->portValid = TRUE;
os_memset(&eap_conf, 0, sizeof(eap_conf));
eap_conf.eap_server = eapol->conf.eap_server;
eap_conf.ssl_ctx = eapol->conf.ssl_ctx;
eap_conf.msg_ctx = eapol->conf.msg_ctx;
eap_conf.eap_sim_db_priv = eapol->conf.eap_sim_db_priv;
eap_conf.pac_opaque_encr_key = eapol->conf.pac_opaque_encr_key;
eap_conf.eap_fast_a_id = eapol->conf.eap_fast_a_id;
eap_conf.eap_fast_a_id_len = eapol->conf.eap_fast_a_id_len;
eap_conf.eap_fast_a_id_info = eapol->conf.eap_fast_a_id_info;
eap_conf.eap_fast_prov = eapol->conf.eap_fast_prov;
eap_conf.pac_key_lifetime = eapol->conf.pac_key_lifetime;
eap_conf.pac_key_refresh_time = eapol->conf.pac_key_refresh_time;
eap_conf.eap_sim_aka_result_ind = eapol->conf.eap_sim_aka_result_ind;
eap_conf.tnc = eapol->conf.tnc;
eap_conf.wps = eapol->conf.wps;
eap_conf.assoc_wps_ie = assoc_wps_ie;
eap_conf.assoc_p2p_ie = assoc_p2p_ie;
eap_conf.peer_addr = addr;
eap_conf.fragment_size = eapol->conf.fragment_size;
eap_conf.pwd_group = eapol->conf.pwd_group;
eap_conf.pbc_in_m1 = eapol->conf.pbc_in_m1;
eap_conf.server_id = eapol->conf.server_id;
eap_conf.server_id_len = eapol->conf.server_id_len;
eap_conf.erp = eapol->conf.erp;
eap_conf.tls_session_lifetime = eapol->conf.tls_session_lifetime;
sm->eap = eap_server_sm_init(sm, &eapol_cb, &eap_conf);
if (sm->eap == NULL) {
eapol_auth_free(sm);
return NULL;
}
sm->eap_if = eap_get_interface(sm->eap);
eapol_auth_initialize(sm);
if (identity) {
sm->identity = (u8 *) os_strdup(identity);
if (sm->identity)
sm->identity_len = os_strlen(identity);
}
if (radius_cui)
sm->radius_cui = wpabuf_alloc_copy(radius_cui,
os_strlen(radius_cui));
sm->acct_multi_session_id_lo = eapol->acct_multi_session_id_lo++;
if (eapol->acct_multi_session_id_lo == 0)
eapol->acct_multi_session_id_hi++;
sm->acct_multi_session_id_hi = eapol->acct_multi_session_id_hi;
return sm;
}
void eapol_auth_free(struct eapol_state_machine *sm)
{
if (sm == NULL)
return;
eloop_cancel_timeout(eapol_port_timers_tick, NULL, sm);
eloop_cancel_timeout(eapol_sm_step_cb, sm, NULL);
if (sm->eap)
eap_server_sm_deinit(sm->eap);
os_free(sm);
}
static int eapol_sm_sta_entry_alive(struct eapol_authenticator *eapol,
const u8 *addr)
{
return eapol->cb.sta_entry_alive(eapol->conf.ctx, addr);
}
static void eapol_sm_step_run(struct eapol_state_machine *sm)
{
struct eapol_authenticator *eapol = sm->eapol;
u8 addr[ETH_ALEN];
unsigned int prev_auth_pae, prev_be_auth, prev_reauth_timer,
prev_auth_key_tx, prev_key_rx, prev_ctrl_dir;
int max_steps = 100;
os_memcpy(addr, sm->addr, ETH_ALEN);
/*
* Allow EAPOL state machines to run as long as there are state
* changes, but exit and return here through event loop if more than
* 100 steps is needed as a precaution against infinite loops inside
* eloop callback.
*/
restart:
prev_auth_pae = sm->auth_pae_state;
prev_be_auth = sm->be_auth_state;
prev_reauth_timer = sm->reauth_timer_state;
prev_auth_key_tx = sm->auth_key_tx_state;
prev_key_rx = sm->key_rx_state;
prev_ctrl_dir = sm->ctrl_dir_state;
SM_STEP_RUN(AUTH_PAE);
if (sm->initializing || eapol_sm_sta_entry_alive(eapol, addr))
SM_STEP_RUN(BE_AUTH);
if (sm->initializing || eapol_sm_sta_entry_alive(eapol, addr))
SM_STEP_RUN(REAUTH_TIMER);
if (sm->initializing || eapol_sm_sta_entry_alive(eapol, addr))
SM_STEP_RUN(AUTH_KEY_TX);
if (sm->initializing || eapol_sm_sta_entry_alive(eapol, addr))
SM_STEP_RUN(KEY_RX);
if (sm->initializing || eapol_sm_sta_entry_alive(eapol, addr))
SM_STEP_RUN(CTRL_DIR);
if (prev_auth_pae != sm->auth_pae_state ||
prev_be_auth != sm->be_auth_state ||
prev_reauth_timer != sm->reauth_timer_state ||
prev_auth_key_tx != sm->auth_key_tx_state ||
prev_key_rx != sm->key_rx_state ||
prev_ctrl_dir != sm->ctrl_dir_state) {
if (--max_steps > 0)
goto restart;
/* Re-run from eloop timeout */
eapol_auth_step(sm);
return;
}
if (eapol_sm_sta_entry_alive(eapol, addr) && sm->eap) {
if (eap_server_sm_step(sm->eap)) {
if (--max_steps > 0)
goto restart;
/* Re-run from eloop timeout */
eapol_auth_step(sm);
return;
}
/* TODO: find a better location for this */
if (sm->eap_if->aaaEapResp) {
sm->eap_if->aaaEapResp = FALSE;
if (sm->eap_if->aaaEapRespData == NULL) {
wpa_printf(MSG_DEBUG, "EAPOL: aaaEapResp set, "
"but no aaaEapRespData available");
return;
}
sm->eapol->cb.aaa_send(
sm->eapol->conf.ctx, sm->sta,
wpabuf_head(sm->eap_if->aaaEapRespData),
wpabuf_len(sm->eap_if->aaaEapRespData));
}
}
if (eapol_sm_sta_entry_alive(eapol, addr))
sm->eapol->cb.eapol_event(sm->eapol->conf.ctx, sm->sta,
EAPOL_AUTH_SM_CHANGE);
}
static void eapol_sm_step_cb(void *eloop_ctx, void *timeout_ctx)
{
struct eapol_state_machine *sm = eloop_ctx;
eapol_sm_step_run(sm);
}
/**
* eapol_auth_step - Advance EAPOL state machines
* @sm: EAPOL state machine
*
* This function is called to advance EAPOL state machines after any change
* that could affect their state.
*/
void eapol_auth_step(struct eapol_state_machine *sm)
{
/*
* Run eapol_sm_step_run from a registered timeout to make sure that
* other possible timeouts/events are processed and to avoid long
* function call chains.
*/
eloop_register_timeout(0, 0, eapol_sm_step_cb, sm, NULL);
}
static void eapol_auth_initialize(struct eapol_state_machine *sm)
{
sm->initializing = TRUE;
/* Initialize the state machines by asserting initialize and then
* deasserting it after one step */
sm->initialize = TRUE;
eapol_sm_step_run(sm);
sm->initialize = FALSE;
eapol_sm_step_run(sm);
sm->initializing = FALSE;
/* Start one second tick for port timers state machine */
eloop_cancel_timeout(eapol_port_timers_tick, NULL, sm);
eloop_register_timeout(1, 0, eapol_port_timers_tick, NULL, sm);
}
static int eapol_sm_get_eap_user(void *ctx, const u8 *identity,
size_t identity_len, int phase2,
struct eap_user *user)
{
struct eapol_state_machine *sm = ctx;
int ret;
ret = sm->eapol->cb.get_eap_user(sm->eapol->conf.ctx, identity,
identity_len, phase2, user);
if (user->remediation)
sm->remediation = 1;
return ret;
}
static const char * eapol_sm_get_eap_req_id_text(void *ctx, size_t *len)
{
struct eapol_state_machine *sm = ctx;
*len = sm->eapol->conf.eap_req_id_text_len;
return sm->eapol->conf.eap_req_id_text;
}
static int eapol_sm_get_erp_send_reauth_start(void *ctx)
{
struct eapol_state_machine *sm = ctx;
return sm->eapol->conf.erp_send_reauth_start;
}
static const char * eapol_sm_get_erp_domain(void *ctx)
{
struct eapol_state_machine *sm = ctx;
return sm->eapol->conf.erp_domain;
}
static struct eap_server_erp_key * eapol_sm_erp_get_key(void *ctx,
const char *keyname)
{
struct eapol_state_machine *sm = ctx;
return sm->eapol->cb.erp_get_key(sm->eapol->conf.ctx, keyname);
}
static int eapol_sm_erp_add_key(void *ctx, struct eap_server_erp_key *erp)
{
struct eapol_state_machine *sm = ctx;
return sm->eapol->cb.erp_add_key(sm->eapol->conf.ctx, erp);
}
static const struct eapol_callbacks eapol_cb =
{
eapol_sm_get_eap_user,
eapol_sm_get_eap_req_id_text,
NULL,
eapol_sm_get_erp_send_reauth_start,
eapol_sm_get_erp_domain,
eapol_sm_erp_get_key,
eapol_sm_erp_add_key,
};
int eapol_auth_eap_pending_cb(struct eapol_state_machine *sm, void *ctx)
{
if (sm == NULL || ctx == NULL || ctx != sm->eap)
return -1;
eap_sm_pending_cb(sm->eap);
eapol_auth_step(sm);
return 0;
}
void eapol_auth_reauthenticate(struct eapol_state_machine *sm)
{
wpa_printf(MSG_DEBUG, "EAPOL: External reauthentication trigger for "
MACSTR, MAC2STR(sm->addr));
sm->reAuthenticate = TRUE;
eapol_auth_step(sm);
}
int eapol_auth_set_conf(struct eapol_state_machine *sm, const char *param,
const char *value)
{
wpa_printf(MSG_DEBUG, "EAPOL: External configuration operation for "
MACSTR " - param=%s value=%s",
MAC2STR(sm->addr), param, value);
if (os_strcasecmp(param, "AdminControlledDirections") == 0) {
if (os_strcmp(value, "Both") == 0)
sm->adminControlledDirections = Both;
else if (os_strcmp(value, "In") == 0)
sm->adminControlledDirections = In;
else
return -1;
eapol_auth_step(sm);
return 0;
}
if (os_strcasecmp(param, "AdminControlledPortControl") == 0) {
if (os_strcmp(value, "ForceAuthorized") == 0)
sm->portControl = ForceAuthorized;
else if (os_strcmp(value, "ForceUnauthorized") == 0)
sm->portControl = ForceUnauthorized;
else if (os_strcmp(value, "Auto") == 0)
sm->portControl = Auto;
else
return -1;
eapol_auth_step(sm);
return 0;
}
if (os_strcasecmp(param, "quietPeriod") == 0) {
sm->quietPeriod = atoi(value);
return 0;
}
if (os_strcasecmp(param, "serverTimeout") == 0) {
sm->serverTimeout = atoi(value);
return 0;
}
if (os_strcasecmp(param, "reAuthPeriod") == 0) {
sm->reAuthPeriod = atoi(value);
return 0;
}
if (os_strcasecmp(param, "reAuthEnabled") == 0) {
if (os_strcmp(value, "TRUE") == 0)
sm->reAuthEnabled = TRUE;
else if (os_strcmp(value, "FALSE") == 0)
sm->reAuthEnabled = FALSE;
else
return -1;
eapol_auth_step(sm);
return 0;
}
if (os_strcasecmp(param, "KeyTransmissionEnabled") == 0) {
if (os_strcmp(value, "TRUE") == 0)
sm->keyTxEnabled = TRUE;
else if (os_strcmp(value, "FALSE") == 0)
sm->keyTxEnabled = FALSE;
else
return -1;
eapol_auth_step(sm);
return 0;
}
return -1;
}
static int eapol_auth_conf_clone(struct eapol_auth_config *dst,
struct eapol_auth_config *src)
{
dst->ctx = src->ctx;
dst->eap_reauth_period = src->eap_reauth_period;
dst->wpa = src->wpa;
dst->individual_wep_key_len = src->individual_wep_key_len;
dst->eap_server = src->eap_server;
dst->ssl_ctx = src->ssl_ctx;
dst->msg_ctx = src->msg_ctx;
dst->eap_sim_db_priv = src->eap_sim_db_priv;
os_free(dst->eap_req_id_text);
dst->pwd_group = src->pwd_group;
dst->pbc_in_m1 = src->pbc_in_m1;
dst->server_id = src->server_id;
dst->server_id_len = src->server_id_len;
if (src->eap_req_id_text) {
dst->eap_req_id_text = os_malloc(src->eap_req_id_text_len);
if (dst->eap_req_id_text == NULL)
return -1;
os_memcpy(dst->eap_req_id_text, src->eap_req_id_text,
src->eap_req_id_text_len);
dst->eap_req_id_text_len = src->eap_req_id_text_len;
} else {
dst->eap_req_id_text = NULL;
dst->eap_req_id_text_len = 0;
}
if (src->pac_opaque_encr_key) {
dst->pac_opaque_encr_key = os_malloc(16);
if (dst->pac_opaque_encr_key == NULL)
goto fail;
os_memcpy(dst->pac_opaque_encr_key, src->pac_opaque_encr_key,
16);
} else
dst->pac_opaque_encr_key = NULL;
if (src->eap_fast_a_id) {
dst->eap_fast_a_id = os_malloc(src->eap_fast_a_id_len);
if (dst->eap_fast_a_id == NULL)
goto fail;
os_memcpy(dst->eap_fast_a_id, src->eap_fast_a_id,
src->eap_fast_a_id_len);
dst->eap_fast_a_id_len = src->eap_fast_a_id_len;
} else
dst->eap_fast_a_id = NULL;
if (src->eap_fast_a_id_info) {
dst->eap_fast_a_id_info = os_strdup(src->eap_fast_a_id_info);
if (dst->eap_fast_a_id_info == NULL)
goto fail;
} else
dst->eap_fast_a_id_info = NULL;
dst->eap_fast_prov = src->eap_fast_prov;
dst->pac_key_lifetime = src->pac_key_lifetime;
dst->pac_key_refresh_time = src->pac_key_refresh_time;
dst->eap_sim_aka_result_ind = src->eap_sim_aka_result_ind;
dst->tnc = src->tnc;
dst->wps = src->wps;
dst->fragment_size = src->fragment_size;
os_free(dst->erp_domain);
if (src->erp_domain) {
dst->erp_domain = os_strdup(src->erp_domain);
if (dst->erp_domain == NULL)
goto fail;
} else {
dst->erp_domain = NULL;
}
dst->erp_send_reauth_start = src->erp_send_reauth_start;
dst->erp = src->erp;
dst->tls_session_lifetime = src->tls_session_lifetime;
return 0;
fail:
eapol_auth_conf_free(dst);
return -1;
}
static void eapol_auth_conf_free(struct eapol_auth_config *conf)
{
os_free(conf->eap_req_id_text);
conf->eap_req_id_text = NULL;
os_free(conf->pac_opaque_encr_key);
conf->pac_opaque_encr_key = NULL;
os_free(conf->eap_fast_a_id);
conf->eap_fast_a_id = NULL;
os_free(conf->eap_fast_a_id_info);
conf->eap_fast_a_id_info = NULL;
os_free(conf->erp_domain);
conf->erp_domain = NULL;
}
struct eapol_authenticator * eapol_auth_init(struct eapol_auth_config *conf,
struct eapol_auth_cb *cb)
{
struct eapol_authenticator *eapol;
struct os_time now;
eapol = os_zalloc(sizeof(*eapol));
if (eapol == NULL)
return NULL;
if (eapol_auth_conf_clone(&eapol->conf, conf) < 0) {
os_free(eapol);
return NULL;
}
if (conf->individual_wep_key_len > 0) {
/* use key0 in individual key and key1 in broadcast key */
eapol->default_wep_key_idx = 1;
}
eapol->cb.eapol_send = cb->eapol_send;
eapol->cb.aaa_send = cb->aaa_send;
eapol->cb.finished = cb->finished;
eapol->cb.get_eap_user = cb->get_eap_user;
eapol->cb.sta_entry_alive = cb->sta_entry_alive;
eapol->cb.logger = cb->logger;
eapol->cb.set_port_authorized = cb->set_port_authorized;
eapol->cb.abort_auth = cb->abort_auth;
eapol->cb.tx_key = cb->tx_key;
eapol->cb.eapol_event = cb->eapol_event;
eapol->cb.erp_get_key = cb->erp_get_key;
eapol->cb.erp_add_key = cb->erp_add_key;
/* Acct-Multi-Session-Id should be unique over reboots. If reliable
* clock is not available, this could be replaced with reboot counter,
* etc. */
os_get_time(&now);
eapol->acct_multi_session_id_hi = now.sec;
return eapol;
}
void eapol_auth_deinit(struct eapol_authenticator *eapol)
{
if (eapol == NULL)
return;
eapol_auth_conf_free(&eapol->conf);
os_free(eapol->default_wep_key);
os_free(eapol);
}