From 7ec4eb033d83d1150a5d461349c32d7113770a57 Mon Sep 17 00:00:00 2001 From: sam Date: Tue, 7 Mar 2006 05:51:52 +0000 Subject: resolve merge conflicts MFC after: 2 weeks --- contrib/hostapd/FREEBSD-Xlist | 4 +- contrib/hostapd/FREEBSD-upgrade | 8 +- contrib/hostapd/eap.c | 27 ++++- contrib/hostapd/eap.h | 7 +- contrib/hostapd/eapol_sm.c | 160 ++++++++++++++++++++--------- contrib/hostapd/hostapd_ctrl.c | 188 ---------------------------------- contrib/hostapd/hostapd_ctrl.h | 18 ---- contrib/hostapd/ieee802_1x.c | 219 +++++++++++++++++++++++++++++----------- contrib/hostapd/ieee802_1x.h | 18 ++-- contrib/hostapd/wpa.c | 110 +++++++++++++++++--- 10 files changed, 419 insertions(+), 340 deletions(-) delete mode 100644 contrib/hostapd/hostapd_ctrl.c delete mode 100644 contrib/hostapd/hostapd_ctrl.h (limited to 'contrib/hostapd') diff --git a/contrib/hostapd/FREEBSD-Xlist b/contrib/hostapd/FREEBSD-Xlist index cba8df0..00330b3 100644 --- a/contrib/hostapd/FREEBSD-Xlist +++ b/contrib/hostapd/FREEBSD-Xlist @@ -4,7 +4,9 @@ driver.c driver_bsd.c driver_madwifi.c driver_prism54.c -l2_packet.c +l2_packet_freebsd.c +l2_packet_linux.c +l2_packet_pcap.c prism54.h priv_netlink.h wireless_copy.h diff --git a/contrib/hostapd/FREEBSD-upgrade b/contrib/hostapd/FREEBSD-upgrade index 5efe2f5..8286ce4 100644 --- a/contrib/hostapd/FREEBSD-upgrade +++ b/contrib/hostapd/FREEBSD-upgrade @@ -6,12 +6,12 @@ WPA/802.1x Authenticator For the import files and directories were pruned by: - tar -X FREEBSD-Xlist -zxf hostapd-0.3.7.tar.gz + tar -X FREEBSD-Xlist -zxf hostapd-0.4.8.tar.gz then imported by: - cvs import -m 'Import of hostapd 0.3.7' \ - src/contrib/hostapd MALINEN v0_3_7 + cvs import -m 'Import of hostapd 0.4.8' \ + src/contrib/hostapd MALINEN v0_4_8 To make local changes to hostapd, simply patch and commit to the main branch (aka HEAD). Never make local changes on the vendor @@ -21,4 +21,4 @@ All local changes should be submitted to Jouni Malinen for inclusion in the next vendor release. sam@FreeBSD.org -4-June-2005 +6-March-2006 diff --git a/contrib/hostapd/eap.c b/contrib/hostapd/eap.c index 1897856..b321ea6 100644 --- a/contrib/hostapd/eap.c +++ b/contrib/hostapd/eap.c @@ -26,6 +26,7 @@ #include "sta_info.h" #include "eap_i.h" +#define EAP_MAX_AUTH_ROUNDS 50 extern const struct eap_method eap_method_identity; #ifdef EAP_MD5 @@ -52,6 +53,12 @@ extern const struct eap_method eap_method_ttls; #ifdef EAP_SIM extern const struct eap_method eap_method_sim; #endif /* EAP_SIM */ +#ifdef EAP_PAX +extern const struct eap_method eap_method_pax; +#endif /* EAP_PAX */ +#ifdef EAP_PSK +extern const struct eap_method eap_method_psk; +#endif /* EAP_PSK */ static const struct eap_method *eap_methods[] = { @@ -80,6 +87,12 @@ static const struct eap_method *eap_methods[] = #ifdef EAP_SIM &eap_method_sim, #endif /* EAP_SIM */ +#ifdef EAP_PAX + &eap_method_pax, +#endif /* EAP_PAX */ +#ifdef EAP_PSK + &eap_method_psk, +#endif /* EAP_PSK */ }; #define NUM_EAP_METHODS (sizeof(eap_methods) / sizeof(eap_methods[0])) @@ -199,6 +212,7 @@ int eap_user_get(struct eap_sm *sm, const u8 *identity, size_t identity_len, SM_STATE(EAP, DISABLED) { SM_ENTRY(EAP, DISABLED); + sm->num_rounds = 0; } @@ -234,6 +248,7 @@ SM_STATE(EAP, INITIALIZE) sm->currentId = sm->respId; } } + sm->num_rounds = 0; } @@ -290,6 +305,7 @@ SM_STATE(EAP, RECEIVED) /* parse rxResp, respId, respMethod */ eap_sm_parseEapResp(sm, sm->eapRespData, sm->eapRespDataLen); + sm->num_rounds++; } @@ -505,7 +521,15 @@ SM_STEP(EAP) SM_ENTER_GLOBAL(EAP, INITIALIZE); else if (!eapol_get_bool(sm, EAPOL_portEnabled)) SM_ENTER_GLOBAL(EAP, DISABLED); - else switch (sm->EAP_state) { + else if (sm->num_rounds > EAP_MAX_AUTH_ROUNDS) { + if (sm->num_rounds == EAP_MAX_AUTH_ROUNDS + 1) { + wpa_printf(MSG_DEBUG, "EAP: more than %d " + "authentication rounds - abort", + EAP_MAX_AUTH_ROUNDS); + sm->num_rounds++; + SM_ENTER_GLOBAL(EAP, FAILURE); + } + } else switch (sm->EAP_state) { case EAP_INITIALIZE: if (sm->backend_auth) { if (!sm->rxResp) @@ -912,6 +936,7 @@ void eap_sm_deinit(struct eap_sm *sm) free(sm); } + void eap_sm_notify_cached(struct eap_sm *sm) { if (sm == NULL) diff --git a/contrib/hostapd/eap.h b/contrib/hostapd/eap.h index c79bffe..ce49b07 100644 --- a/contrib/hostapd/eap.h +++ b/contrib/hostapd/eap.h @@ -32,6 +32,7 @@ struct eapol_callbacks { size_t eapKeyDataLen); int (*get_eap_user)(void *ctx, const u8 *identity, size_t identity_len, int phase2, struct eap_user *user); + const char * (*get_eap_req_id_text)(void *ctx, size_t *len); }; struct eap_config { @@ -41,7 +42,7 @@ struct eap_config { }; -#ifdef EAP_AUTHENTICATOR +#ifdef EAP_SERVER struct eap_sm * eap_sm_init(void *eapol_ctx, struct eapol_callbacks *eapol_cb, struct eap_config *eap_conf); @@ -52,7 +53,7 @@ void eap_set_eapRespData(struct eap_sm *sm, const u8 *eapRespData, size_t eapRespDataLen); void eap_sm_notify_cached(struct eap_sm *sm); -#else /* EAP_AUTHENTICATOR */ +#else /* EAP_SERVER */ static inline struct eap_sm * eap_sm_init(void *eapol_ctx, struct eapol_callbacks *eapol_cb, @@ -85,6 +86,6 @@ static inline void eap_sm_notify_cached(struct eap_sm *sm) { } -#endif /* EAP_AUTHENTICATOR */ +#endif /* EAP_SERVER */ #endif /* EAP_H */ diff --git a/contrib/hostapd/eapol_sm.c b/contrib/hostapd/eapol_sm.c index 50c645a..4ded7e7 100644 --- a/contrib/hostapd/eapol_sm.c +++ b/contrib/hostapd/eapol_sm.c @@ -68,6 +68,9 @@ static void sm_ ## machine ## _Step(struct eapol_state_machine *sm) #define SM_STEP_RUN(machine) sm_ ## machine ## _Step(sm) +static void eapol_sm_step_run(struct eapol_state_machine *sm); +static void eapol_sm_step_cb(void *eloop_ctx, void *timeout_ctx); + /* Port Timers state machine - implemented as a function that will be called * once a second as a registered event loop timeout */ @@ -76,19 +79,34 @@ static void eapol_port_timers_tick(void *eloop_ctx, void *timeout_ctx) { struct eapol_state_machine *state = timeout_ctx; - if (state->aWhile > 0) + if (state->aWhile > 0) { state->aWhile--; - if (state->quietWhile > 0) + 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->reAuthWhen > 0) - state->reAuthWhen--; + if (state->quietWhile == 0) { + wpa_printf(MSG_DEBUG, "IEEE 802.1X: " MACSTR + " - quietWhile --> 0", + MAC2STR(state->addr)); + } + } - if (state->hapd->conf->debug >= HOSTAPD_DEBUG_MSGDUMPS) - printf("IEEE 802.1X: " MACSTR " Port Timers TICK " - "(timers: %d %d %d)\n", MAC2STR(state->addr), - state->aWhile, state->quietWhile, state->reAuthWhen); + if (state->reAuthWhen > 0) { + state->reAuthWhen--; + if (state->reAuthWhen == 0) { + wpa_printf(MSG_DEBUG, "IEEE 802.1X: " MACSTR + " - reAuthWhen --> 0", + MAC2STR(state->addr)); + } + } - eapol_sm_step(state); + eapol_sm_step_run(state); eloop_register_timeout(1, 0, eapol_port_timers_tick, eloop_ctx, state); } @@ -359,6 +377,19 @@ SM_STATE(BE_AUTH, REQUEST) txReq(); sm->be_auth.eapReq = FALSE; sm->be_auth.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; } @@ -705,7 +736,7 @@ eapol_sm_alloc(hostapd *hapd, struct sta_info *sta) else sm->portValid = TRUE; - if (hapd->conf->eap_authenticator) { + if (hapd->conf->eap_server) { struct eap_config eap_conf; memset(&eap_conf, 0, sizeof(eap_conf)); eap_conf.ssl_ctx = hapd->ssl_ctx; @@ -729,6 +760,7 @@ void eapol_sm_free(struct eapol_state_machine *sm) return; eloop_cancel_timeout(eapol_port_timers_tick, sm->hapd, sm); + eloop_cancel_timeout(eapol_sm_step_cb, sm, NULL); if (sm->eap) eap_sm_deinit(sm->eap); free(sm); @@ -745,55 +777,63 @@ static int eapol_sm_sta_entry_alive(struct hostapd_data *hapd, u8 *addr) } -void eapol_sm_step(struct eapol_state_machine *sm) +static void eapol_sm_step_run(struct eapol_state_machine *sm) { struct hostapd_data *hapd = sm->hapd; u8 addr[ETH_ALEN]; int prev_auth_pae, prev_be_auth, prev_reauth_timer, prev_auth_key_tx, prev_key_rx, prev_ctrl_dir; - - /* FIX: could re-run eapol_sm_step from registered timeout (after - * 0 sec) to make sure that other possible timeouts/events are - * processed */ + int max_steps = 100; memcpy(addr, sm->sta->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: - do { - 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(hapd, addr)) - break; + 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(hapd, addr)) SM_STEP_RUN(BE_AUTH); - if (!sm->initializing && !eapol_sm_sta_entry_alive(hapd, addr)) - break; + if (sm->initializing || eapol_sm_sta_entry_alive(hapd, addr)) SM_STEP_RUN(REAUTH_TIMER); - if (!sm->initializing && !eapol_sm_sta_entry_alive(hapd, addr)) - break; + if (sm->initializing || eapol_sm_sta_entry_alive(hapd, addr)) SM_STEP_RUN(AUTH_KEY_TX); - if (!sm->initializing && !eapol_sm_sta_entry_alive(hapd, addr)) - break; + if (sm->initializing || eapol_sm_sta_entry_alive(hapd, addr)) SM_STEP_RUN(KEY_RX); - if (!sm->initializing && !eapol_sm_sta_entry_alive(hapd, addr)) - break; + if (sm->initializing || eapol_sm_sta_entry_alive(hapd, addr)) SM_STEP_RUN(CTRL_DIR); - if (!sm->initializing && !eapol_sm_sta_entry_alive(hapd, addr)) - break; - } while (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 (eapol_sm_sta_entry_alive(hapd, addr) && sm->eap) { - if (eap_sm_step(sm->eap)) + 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_sm_step(sm); + return; + } + + if (eapol_sm_sta_entry_alive(hapd, addr) && sm->eap) { + if (eap_sm_step(sm->eap)) { + if (--max_steps > 0) + goto restart; + /* Re-run from eloop timeout */ + eapol_sm_step(sm); + return; + } } if (eapol_sm_sta_entry_alive(hapd, addr)) @@ -801,15 +841,34 @@ restart: } +static void eapol_sm_step_cb(void *eloop_ctx, void *timeout_ctx) +{ + struct eapol_state_machine *sm = eloop_ctx; + eapol_sm_step_run(sm); +} + + +void eapol_sm_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); +} + + void eapol_sm_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(sm); + eapol_sm_step_run(sm); sm->initialize = FALSE; - eapol_sm_step(sm); + eapol_sm_step_run(sm); sm->initializing = FALSE; /* Start one second tick for port timers state machine */ @@ -1183,6 +1242,14 @@ static int eapol_sm_get_eap_user(void *ctx, const u8 *identity, } +static const char * eapol_sm_get_eap_req_id_text(void *ctx, size_t *len) +{ + struct eapol_state_machine *sm = ctx; + *len = sm->hapd->conf->eap_req_id_text_len; + return sm->hapd->conf->eap_req_id_text; +} + + static struct eapol_callbacks eapol_cb = { .get_bool = eapol_sm_get_bool, @@ -1190,4 +1257,5 @@ static struct eapol_callbacks eapol_cb = .set_eapReqData = eapol_sm_set_eapReqData, .set_eapKeyData = eapol_sm_set_eapKeyData, .get_eap_user = eapol_sm_get_eap_user, + .get_eap_req_id_text = eapol_sm_get_eap_req_id_text, }; diff --git a/contrib/hostapd/hostapd_ctrl.c b/contrib/hostapd/hostapd_ctrl.c deleted file mode 100644 index 55b6904..0000000 --- a/contrib/hostapd/hostapd_ctrl.c +++ /dev/null @@ -1,188 +0,0 @@ -/* - * hostapd - hostapd control interface library - * Copyright (c) 2004, Jouni Malinen - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * Alternatively, this software may be distributed under the terms of BSD - * license. - * - * See README and COPYING for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "hostapd_ctrl.h" - - -struct hostapd_ctrl { - int s; - struct sockaddr_un local; - struct sockaddr_un dest; -}; - - -struct hostapd_ctrl * hostapd_ctrl_open(const char *ctrl_path) -{ - struct hostapd_ctrl *ctrl; - static int counter = 0; - - ctrl = malloc(sizeof(*ctrl)); - if (ctrl == NULL) - return NULL; - memset(ctrl, 0, sizeof(*ctrl)); - - ctrl->s = socket(PF_UNIX, SOCK_DGRAM, 0); - if (ctrl->s < 0) { - free(ctrl); - return NULL; - } - - ctrl->local.sun_family = AF_UNIX; - snprintf(ctrl->local.sun_path, sizeof(ctrl->local.sun_path), - "/tmp/hostapd_ctrl_%d-%d", getpid(), counter++); - if (bind(ctrl->s, (struct sockaddr *) &ctrl->local, - sizeof(ctrl->local.sun_family) + - strlen(ctrl->local.sun_path)) < 0) { - close(ctrl->s); - free(ctrl); - return NULL; - } - - ctrl->dest.sun_family = AF_UNIX; - strncpy(ctrl->dest.sun_path, ctrl_path, sizeof(ctrl->dest.sun_path)); - if (connect(ctrl->s, (struct sockaddr *) &ctrl->dest, - sizeof(ctrl->dest.sun_family) + - strlen(ctrl->dest.sun_path)) < 0) { - close(ctrl->s); - unlink(ctrl->local.sun_path); - free(ctrl); - return NULL; - } - - return ctrl; -} - - -void hostapd_ctrl_close(struct hostapd_ctrl *ctrl) -{ - unlink(ctrl->local.sun_path); - close(ctrl->s); - free(ctrl); -} - - -int hostapd_ctrl_request(struct hostapd_ctrl *ctrl, char *cmd, size_t cmd_len, - char *reply, size_t *reply_len, - void (*msg_cb)(char *msg, size_t len)) -{ - struct timeval tv; - int res; - fd_set rfds; - - if (send(ctrl->s, cmd, cmd_len, 0) < 0) - return -1; - - for (;;) { - tv.tv_sec = 2; - tv.tv_usec = 0; - FD_ZERO(&rfds); - FD_SET(ctrl->s, &rfds); - res = select(ctrl->s + 1, &rfds, NULL, NULL, &tv); - if (FD_ISSET(ctrl->s, &rfds)) { - res = recv(ctrl->s, reply, *reply_len, 0); - if (res < 0) - return res; - if (res > 0 && reply[0] == '<') { - /* This is an unsolicited message from - * wpa_supplicant, not the reply to the - * request. Use msg_cb to report this to the - * caller. */ - if (msg_cb) { - /* Make sure the message is nul - * terminated. */ - if (res == *reply_len) - res = (*reply_len) - 1; - reply[res] = '\0'; - msg_cb(reply, res); - } - continue; - } - *reply_len = res; - break; - } else { - return -2; - } - } - return 0; -} - - -static int hostapd_ctrl_attach_helper(struct hostapd_ctrl *ctrl, int attach) -{ - char buf[10]; - int ret; - size_t len = 10; - - ret = hostapd_ctrl_request(ctrl, attach ? "ATTACH" : "DETACH", 6, - buf, &len, NULL); - if (ret < 0) - return ret; - if (len == 3 && memcmp(buf, "OK\n", 3) == 0) - return 0; - return -1; -} - - -int hostapd_ctrl_attach(struct hostapd_ctrl *ctrl) -{ - return hostapd_ctrl_attach_helper(ctrl, 1); -} - - -int hostapd_ctrl_detach(struct hostapd_ctrl *ctrl) -{ - return hostapd_ctrl_attach_helper(ctrl, 0); -} - - -int hostapd_ctrl_recv(struct hostapd_ctrl *ctrl, char *reply, - size_t *reply_len) -{ - int res; - - res = recv(ctrl->s, reply, *reply_len, 0); - if (res < 0) - return res; - *reply_len = res; - return 0; -} - - -int hostapd_ctrl_pending(struct hostapd_ctrl *ctrl) -{ - struct timeval tv; - int res; - fd_set rfds; - tv.tv_sec = 0; - tv.tv_usec = 0; - FD_ZERO(&rfds); - FD_SET(ctrl->s, &rfds); - res = select(ctrl->s + 1, &rfds, NULL, NULL, &tv); - return FD_ISSET(ctrl->s, &rfds); -} - - -int hostapd_ctrl_get_fd(struct hostapd_ctrl *ctrl) -{ - return ctrl->s; -} diff --git a/contrib/hostapd/hostapd_ctrl.h b/contrib/hostapd/hostapd_ctrl.h deleted file mode 100644 index 7ba221e..0000000 --- a/contrib/hostapd/hostapd_ctrl.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef HOSTAPD_CTRL_H -#define HOSTAPD_CTRL_H - -struct hostapd_ctrl; - -struct hostapd_ctrl * hostapd_ctrl_open(const char *ctrl_path); -void hostapd_ctrl_close(struct hostapd_ctrl *ctrl); -int hostapd_ctrl_request(struct hostapd_ctrl *ctrl, char *cmd, size_t cmd_len, - char *reply, size_t *reply_len, - void (*msg_cb)(char *msg, size_t len)); -int hostapd_ctrl_attach(struct hostapd_ctrl *ctrl); -int hostapd_ctrl_detach(struct hostapd_ctrl *ctrl); -int hostapd_ctrl_recv(struct hostapd_ctrl *ctrl, char *reply, - size_t *reply_len); -int hostapd_ctrl_pending(struct hostapd_ctrl *ctrl); -int hostapd_ctrl_get_fd(struct hostapd_ctrl *ctrl); - -#endif /* HOSTAPD_CTRL_H */ diff --git a/contrib/hostapd/ieee802_1x.c b/contrib/hostapd/ieee802_1x.c index ef1bde4..c8b1602 100644 --- a/contrib/hostapd/ieee802_1x.c +++ b/contrib/hostapd/ieee802_1x.c @@ -135,13 +135,13 @@ void ieee802_1x_request_identity(struct hostapd_data *hapd, { u8 *buf; struct eap_hdr *eap; - int extra, tlen; + int tlen; u8 *pos; struct eapol_state_machine *sm = sta->eapol_sm; - if (hapd->conf->eap_authenticator) { + if (hapd->conf->eap_server) { HOSTAPD_DEBUG(HOSTAPD_DEBUG_MINIMAL, - "IEEE 802.1X: Integrated EAP Authenticator in " + "IEEE 802.1X: Integrated EAP server in " "use - do not generate EAP-Request/Identity\n"); return; } @@ -151,12 +151,7 @@ void ieee802_1x_request_identity(struct hostapd_data *hapd, ieee802_1x_new_auth_session(hapd, sta); - if (hapd->conf->eap_req_id_text) - extra = strlen(hapd->conf->eap_req_id_text); - else - extra = 0; - - tlen = sizeof(*eap) + 1 + extra; + tlen = sizeof(*eap) + 1 + hapd->conf->eap_req_id_text_len; buf = malloc(tlen); if (buf == NULL) { @@ -172,8 +167,10 @@ void ieee802_1x_request_identity(struct hostapd_data *hapd, eap->length = htons(tlen); pos = (u8 *) (eap + 1); *pos++ = EAP_TYPE_IDENTITY; - if (hapd->conf->eap_req_id_text) - memcpy(pos, hapd->conf->eap_req_id_text, extra); + if (hapd->conf->eap_req_id_text) { + memcpy(pos, hapd->conf->eap_req_id_text, + hapd->conf->eap_req_id_text_len); + } sm->be_auth.eapReq = TRUE; free(sm->last_eap_radius); @@ -424,12 +421,22 @@ static void ieee802_1x_encapsulate_radius(hostapd *hapd, struct sta_info *sta, goto fail; } - if (!radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IP_ADDRESS, - (u8 *) &hapd->conf->own_ip_addr, 4)) { + if (hapd->conf->own_ip_addr.af == AF_INET && + !radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IP_ADDRESS, + (u8 *) &hapd->conf->own_ip_addr.u.v4, 4)) { printf("Could not add NAS-IP-Address\n"); goto fail; } +#ifdef CONFIG_IPV6 + if (hapd->conf->own_ip_addr.af == AF_INET6 && + !radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IPV6_ADDRESS, + (u8 *) &hapd->conf->own_ip_addr.u.v6, 16)) { + printf("Could not add NAS-IPv6-Address\n"); + goto fail; + } +#endif /* CONFIG_IPV6 */ + if (hapd->conf->nas_identifier && !radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IDENTIFIER, (u8 *) hapd->conf->nas_identifier, @@ -665,7 +672,8 @@ static void handle_eap(hostapd *hapd, struct sta_info *sta, u8 *buf, /* Process the EAPOL frames from the Supplicant */ -void ieee802_1x_receive(hostapd *hapd, u8 *sa, u8 *buf, size_t len) +void ieee802_1x_receive(struct hostapd_data *hapd, const u8 *sa, const u8 *buf, + size_t len) { struct sta_info *sta; struct ieee802_1x_hdr *hdr; @@ -800,20 +808,17 @@ void ieee802_1x_new_station(hostapd *hapd, struct sta_info *sta) if (!hapd->conf->ieee802_1x || sta->wpa_key_mgmt == WPA_KEY_MGMT_PSK) return; - if (sta->eapol_sm) { - sta->eapol_sm->portEnabled = TRUE; - eapol_sm_step(sta->eapol_sm); - return; - } - - hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X, - HOSTAPD_LEVEL_DEBUG, "start authentication"); - sta->eapol_sm = eapol_sm_alloc(hapd, sta); if (sta->eapol_sm == NULL) { hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X, - HOSTAPD_LEVEL_INFO, "failed to allocate " - "state machine"); - return; + HOSTAPD_LEVEL_DEBUG, "start authentication"); + sta->eapol_sm = eapol_sm_alloc(hapd, sta); + if (sta->eapol_sm == NULL) { + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_INFO, + "failed to allocate state machine"); + return; + } } sta->eapol_sm->portEnabled = TRUE; @@ -831,7 +836,49 @@ void ieee802_1x_new_station(hostapd *hapd, struct sta_info *sta) sta->eapol_sm->authSuccess = TRUE; if (sta->eapol_sm->eap) eap_sm_notify_cached(sta->eapol_sm->eap); + } else + eapol_sm_step(sta->eapol_sm); +} + + +void ieee802_1x_free_radius_class(struct radius_class_data *class) +{ + int i; + if (class == NULL) + return; + for (i = 0; i < class->count; i++) + free(class->attr[i].data); + free(class->attr); + class->attr = NULL; + class->count = 0; +} + + +int ieee802_1x_copy_radius_class(struct radius_class_data *dst, + struct radius_class_data *src) +{ + size_t i; + + if (src->attr == NULL) + return 0; + + dst->attr = malloc(src->count * sizeof(struct radius_attr_data)); + if (dst->attr == NULL) + return -1; + + memset(dst->attr, 0, src->count * sizeof(struct radius_attr_data)); + dst->count = 0; + + for (i = 0; i < src->count; i++) { + dst->attr[i].data = malloc(src->attr[i].len); + if (dst->attr[i].data == NULL) + break; + dst->count++; + memcpy(dst->attr[i].data, src->attr[i].data, src->attr[i].len); + dst->attr[i].len = src->attr[i].len; } + + return 0; } @@ -854,7 +901,7 @@ void ieee802_1x_free_station(struct sta_info *sta) free(sm->last_eap_supp); free(sm->last_eap_radius); free(sm->identity); - free(sm->radius_class); + ieee802_1x_free_radius_class(&sm->radius_class); free(sm->eapol_key_sign); free(sm->eapol_key_crypt); eapol_sm_free(sm); @@ -997,31 +1044,87 @@ static void ieee802_1x_store_radius_class(struct hostapd_data *hapd, u8 *class; size_t class_len; struct eapol_state_machine *sm = sta->eapol_sm; + int count, i; + struct radius_attr_data *nclass; + size_t nclass_count; - if (!hapd->conf->acct_server || hapd->radius == NULL || sm == NULL) + if (!hapd->conf->radius->acct_server || hapd->radius == NULL || + sm == NULL) return; - if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_CLASS, &class, - &class_len) < 0 || - class_len < 1) { - free(sm->radius_class); - sm->radius_class = NULL; - sm->radius_class_len = 0; + ieee802_1x_free_radius_class(&sm->radius_class); + count = radius_msg_count_attr(msg, RADIUS_ATTR_CLASS, 1); + if (count <= 0) return; - } - if (sm->radius_class == NULL || - sm->radius_class_len < class_len) { - free(sm->radius_class); - sm->radius_class = malloc(class_len); - if (sm->radius_class == NULL) { - sm->radius_class_len = 0; - return; - } + nclass = malloc(count * sizeof(struct radius_attr_data)); + if (nclass == NULL) + return; + + nclass_count = 0; + memset(nclass, 0, count * sizeof(struct radius_attr_data)); + + class = NULL; + for (i = 0; i < count; i++) { + do { + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_CLASS, + &class, &class_len, + class) < 0) { + i = count; + break; + } + } while (class_len < 1); + + nclass[nclass_count].data = malloc(class_len); + if (nclass[nclass_count].data == NULL) + break; + + memcpy(nclass[nclass_count].data, class, class_len); + nclass[nclass_count].len = class_len; + nclass_count++; } - memcpy(sm->radius_class, class, class_len); - sm->radius_class_len = class_len; + sm->radius_class.attr = nclass; + sm->radius_class.count = nclass_count; + HOSTAPD_DEBUG(HOSTAPD_DEBUG_MINIMAL, "IEEE 802.1X: Stored %lu RADIUS " + "Class attributes for " MACSTR "\n", + (unsigned long) sm->radius_class.count, + MAC2STR(sta->addr)); +} + + +/* Update sta->identity based on User-Name attribute in Access-Accept */ +static void ieee802_1x_update_sta_identity(struct hostapd_data *hapd, + struct sta_info *sta, + struct radius_msg *msg) +{ + u8 *buf, *identity; + size_t len; + struct eapol_state_machine *sm = sta->eapol_sm; + + if (sm == NULL) + return; + + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_USER_NAME, &buf, &len, + NULL) < 0) + return; + + identity = malloc(len + 1); + if (identity == NULL) + return; + + memcpy(identity, buf, len); + identity[len] = '\0'; + + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_DEBUG, "old identity '%s' updated with " + "User-Name from Access-Accept '%s'", + sm->identity ? (char *) sm->identity : "N/A", + (char *) identity); + + free(sm->identity); + sm->identity = identity; + sm->identity_len = len; } @@ -1066,7 +1169,7 @@ ieee802_1x_receive_auth(struct radius_msg *msg, struct radius_msg *req, { struct hostapd_data *hapd = data; struct sta_info *sta; - u32 session_timeout, termination_action, acct_interim_interval; + u32 session_timeout = 0, termination_action, acct_interim_interval; int session_timeout_set; int eap_timeout; struct eapol_state_machine *sm; @@ -1091,7 +1194,7 @@ ieee802_1x_receive_auth(struct radius_msg *msg, struct radius_msg *req, "Access-Reject without Message-Authenticator " "since it does not include EAP-Message\n"); } else if (radius_msg_verify(msg, shared_secret, shared_secret_len, - req)) { + req, 1)) { printf("Incoming RADIUS packet did not have correct " "Message-Authenticator - dropped\n"); return RADIUS_RX_INVALID_AUTHENTICATOR; @@ -1123,7 +1226,7 @@ ieee802_1x_receive_auth(struct radius_msg *msg, struct radius_msg *req, &termination_action)) termination_action = RADIUS_TERMINATION_ACTION_DEFAULT; - if (hapd->conf->radius_acct_interim_interval == 0 && + if (hapd->conf->radius->acct_interim_interval == 0 && msg->hdr->code == RADIUS_CODE_ACCESS_ACCEPT && radius_msg_get_attr_int32(msg, RADIUS_ATTR_ACCT_INTERIM_INTERVAL, &acct_interim_interval) == 0) { @@ -1152,12 +1255,13 @@ ieee802_1x_receive_auth(struct radius_msg *msg, struct radius_msg *req, override_eapReq = 1; ieee802_1x_get_keys(hapd, sta, msg, req, shared_secret, shared_secret_len); + ieee802_1x_store_radius_class(hapd, sta, msg); + ieee802_1x_update_sta_identity(hapd, sta, msg); if (sm->keyAvailable) { pmksa_cache_add(hapd, sta, sm->eapol_key_crypt, session_timeout_set ? session_timeout : -1); } - ieee802_1x_store_radius_class(hapd, sta, msg); break; case RADIUS_CODE_ACCESS_REJECT: sm->eapFail = TRUE; @@ -1199,7 +1303,7 @@ void ieee802_1x_send_resp_to_server(hostapd *hapd, struct sta_info *sta) if (sm == NULL) return; - if (hapd->conf->eap_authenticator) { + if (hapd->conf->eap_server) { eap_set_eapRespData(sm->eap, sm->last_eap_supp, sm->last_eap_supp_len); } else { @@ -1229,9 +1333,6 @@ void ieee802_1x_abort_auth(struct hostapd_data *hapd, struct sta_info *sta) free(sm->last_eap_radius); sm->last_eap_radius = NULL; sm->last_eap_radius_len = 0; - free(sm->radius_class); - sm->radius_class = NULL; - sm->radius_class_len = 0; } @@ -1482,13 +1583,15 @@ u8 * ieee802_1x_get_identity(struct eapol_state_machine *sm, size_t *len) } -u8 * ieee802_1x_get_radius_class(struct eapol_state_machine *sm, size_t *len) +u8 * ieee802_1x_get_radius_class(struct eapol_state_machine *sm, size_t *len, + int idx) { - if (sm == NULL || sm->radius_class == NULL) + if (sm == NULL || sm->radius_class.attr == NULL || + idx >= sm->radius_class.count) return NULL; - *len = sm->radius_class_len; - return sm->radius_class; + *len = sm->radius_class.attr[idx].len; + return sm->radius_class.attr[idx].data; } @@ -1508,6 +1611,7 @@ void ieee802_1x_notify_port_enabled(struct eapol_state_machine *sm, if (sm == NULL) return; sm->portEnabled = enabled ? TRUE : FALSE; + eapol_sm_step(sm); } @@ -1517,6 +1621,7 @@ void ieee802_1x_notify_port_valid(struct eapol_state_machine *sm, if (sm == NULL) return; sm->portValid = valid ? TRUE : FALSE; + eapol_sm_step(sm); } diff --git a/contrib/hostapd/ieee802_1x.h b/contrib/hostapd/ieee802_1x.h index 9d2567a..90cc2ce 100644 --- a/contrib/hostapd/ieee802_1x.h +++ b/contrib/hostapd/ieee802_1x.h @@ -12,13 +12,7 @@ struct ieee802_1x_hdr { /* followed by length octets of data */ } __attribute__ ((packed)); - -#if defined(IEEE802_1X_EAPOL_VERSION_2) #define EAPOL_VERSION 2 -#else -/* Enable support for older Authenticators/Supplicants using EAPOL Version 1 */ -#define EAPOL_VERSION 1 -#endif /* ! IEEE802_1X_EAPOL_VERSION_2 */ enum { IEEE802_1X_TYPE_EAP_PACKET = 0, IEEE802_1X_TYPE_EAPOL_START = 1, @@ -55,7 +49,8 @@ enum { EAPOL_KEY_TYPE_RC4 = 1, EAPOL_KEY_TYPE_RSN = 2, EAPOL_KEY_TYPE_WPA = 254 }; -void ieee802_1x_receive(hostapd *hapd, u8 *sa, u8 *buf, size_t len); +void ieee802_1x_receive(struct hostapd_data *hapd, const u8 *sa, const u8 *buf, + size_t len); void ieee802_1x_new_station(hostapd *hapd, struct sta_info *sta); void ieee802_1x_free_station(struct sta_info *sta); @@ -77,7 +72,8 @@ void ieee802_1x_deinit(hostapd *hapd); int ieee802_1x_tx_status(hostapd *hapd, struct sta_info *sta, u8 *buf, size_t len, int ack); u8 * ieee802_1x_get_identity(struct eapol_state_machine *sm, size_t *len); -u8 * ieee802_1x_get_radius_class(struct eapol_state_machine *sm, size_t *len); +u8 * ieee802_1x_get_radius_class(struct eapol_state_machine *sm, size_t *len, + int idx); u8 * ieee802_1x_get_key_crypt(struct eapol_state_machine *sm, size_t *len); void ieee802_1x_notify_port_enabled(struct eapol_state_machine *sm, int enabled); @@ -91,4 +87,10 @@ void hostapd_get_ntp_timestamp(u8 *buf); void ieee802_1x_finished(struct hostapd_data *hapd, struct sta_info *sta, int success); +struct radius_class_data; + +void ieee802_1x_free_radius_class(struct radius_class_data *class); +int ieee802_1x_copy_radius_class(struct radius_class_data *dst, + struct radius_class_data *src); + #endif /* IEEE802_1X_H */ diff --git a/contrib/hostapd/wpa.c b/contrib/hostapd/wpa.c index c0948e8..ec07b35 100644 --- a/contrib/hostapd/wpa.c +++ b/contrib/hostapd/wpa.c @@ -36,6 +36,8 @@ #include "eloop.h" #include "sta_info.h" #include "l2_packet.h" +#include "accounting.h" +#include "hostap_common.h" static void wpa_send_eapol_timeout(void *eloop_ctx, void *timeout_ctx); @@ -103,7 +105,7 @@ static const u8 RSN_KEY_DATA_PMKID[] = { 0x00, 0x0f, 0xac, 4 }; * Authenticated Key Management Suite Count (2 octets, little endian) * (default: 1) * Authenticated Key Management Suite List (4 * n octets) - * (default: unspec 802.1x) + * (default: unspec 802.1X) * WPA Capabilities (2 octets, little endian) (default: 0) */ @@ -125,7 +127,7 @@ struct wpa_ie_hdr { * Authenticated Key Management Suite Count (2 octets, little endian) * (default: 1) * Authenticated Key Management Suite List (4 * n octets) - * (default: unspec 802.1x) + * (default: unspec 802.1X) * RSN Capabilities (2 octets, little endian) (default: 0) * PMKID Count (2 octets) (default: 0) * PMKID List (16 * n octets) @@ -398,8 +400,8 @@ static void wpa_rekey_gtk(void *eloop_ctx, void *timeout_ctx) #ifdef CONFIG_RSN_PREAUTH -static void rsn_preauth_receive(void *ctx, unsigned char *src_addr, - unsigned char *buf, size_t len) +static void rsn_preauth_receive(void *ctx, const u8 *src_addr, + const u8 *buf, size_t len) { struct rsn_preauth_interface *piface = ctx; struct hostapd_data *hapd = piface->hapd; @@ -482,13 +484,12 @@ static int rsn_preauth_iface_add(struct hostapd_data *hapd, const char *ifname) } piface->l2 = l2_packet_init(piface->ifname, NULL, ETH_P_PREAUTH, - rsn_preauth_receive, piface); + rsn_preauth_receive, piface, 1); if (piface->l2 == NULL) { printf("Failed to open register layer 2 access to " "ETH_P_PREAUTH\n"); goto fail2; } - l2_packet_set_rx_l2_hdr(piface->l2, 1); piface->next = hapd->preauth_iface; hapd->preauth_iface = piface; @@ -553,6 +554,16 @@ static int rsn_preauth_iface_init(struct hostapd_data *hapd) } +static void rsn_preauth_finished_cb(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct sta_info *sta = timeout_ctx; + wpa_printf(MSG_DEBUG, "RSN: Removing pre-authentication STA entry for " + MACSTR, MAC2STR(sta->addr)); + ap_free_sta(hapd, sta); +} + + void rsn_preauth_finished(struct hostapd_data *hapd, struct sta_info *sta, int success) { @@ -567,7 +578,11 @@ void rsn_preauth_finished(struct hostapd_data *hapd, struct sta_info *sta, pmksa_cache_add(hapd, sta, key, dot11RSNAConfigPMKLifetime); } - ap_free_sta(hapd, sta); + /* + * Finish STA entry removal from timeout in order to avoid freeing + * STA data before the caller has finished processing. + */ + eloop_register_timeout(0, 0, rsn_preauth_finished_cb, hapd, sta); } @@ -600,8 +615,8 @@ void rsn_preauth_send(struct hostapd_data *hapd, struct sta_info *sta, ethhdr->h_proto = htons(ETH_P_PREAUTH); memcpy(ethhdr + 1, buf, len); - if (l2_packet_send(piface->l2, (u8 *) ethhdr, sizeof(*ethhdr) + len) < - 0) { + if (l2_packet_send(piface->l2, sta->addr, ETH_P_PREAUTH, (u8 *) ethhdr, + sizeof(*ethhdr) + len) < 0) { printf("Failed to send preauth packet using l2_packet_send\n"); } free(ethhdr); @@ -618,6 +633,10 @@ static inline void rsn_preauth_iface_deinit(struct hostapd_data *hapd) { } +static void rsn_preauth_finished_cb(void *eloop_ctx, void *timeout_ctx) +{ +} + void rsn_preauth_finished(struct hostapd_data *hapd, struct sta_info *sta, int success) { @@ -809,6 +828,16 @@ static void rsn_pmkid(const u8 *pmk, const u8 *aa, const u8 *spa, u8 *pmkid) static void pmksa_cache_set_expiration(struct hostapd_data *hapd); +static void _pmksa_cache_free_entry(struct rsn_pmksa_cache *entry) +{ + if (entry == NULL) + return; + free(entry->identity); + ieee802_1x_free_radius_class(&entry->radius_class); + free(entry); +} + + static void pmksa_cache_free_entry(struct hostapd_data *hapd, struct rsn_pmksa_cache *entry) { @@ -848,7 +877,7 @@ static void pmksa_cache_free_entry(struct hostapd_data *hapd, prev = pos; pos = pos->next; } - free(entry); + _pmksa_cache_free_entry(entry); } @@ -884,6 +913,54 @@ static void pmksa_cache_set_expiration(struct hostapd_data *hapd) } +static void pmksa_cache_from_eapol_data(struct rsn_pmksa_cache *entry, + struct eapol_state_machine *eapol) +{ + if (eapol == NULL) + return; + + if (eapol->identity) { + entry->identity = malloc(eapol->identity_len); + if (entry->identity) { + entry->identity_len = eapol->identity_len; + memcpy(entry->identity, eapol->identity, + eapol->identity_len); + } + } + + ieee802_1x_copy_radius_class(&entry->radius_class, + &eapol->radius_class); +} + + +static void pmksa_cache_to_eapol_data(struct rsn_pmksa_cache *entry, + struct eapol_state_machine *eapol) +{ + if (entry == NULL || eapol == NULL) + return; + + if (entry->identity) { + free(eapol->identity); + eapol->identity = malloc(entry->identity_len); + if (eapol->identity) { + eapol->identity_len = entry->identity_len; + memcpy(eapol->identity, entry->identity, + entry->identity_len); + } + wpa_hexdump_ascii(MSG_DEBUG, "STA identity from PMKSA", + eapol->identity, eapol->identity_len); + } + + ieee802_1x_free_radius_class(&eapol->radius_class); + ieee802_1x_copy_radius_class(&eapol->radius_class, + &entry->radius_class); + if (eapol->radius_class.attr) { + wpa_printf(MSG_DEBUG, "Copied %lu Class attribute(s) from " + "PMKSA", (unsigned long) eapol->radius_class.count); + } +} + + void pmksa_cache_add(struct hostapd_data *hapd, struct sta_info *sta, u8 *pmk, int session_timeout) { @@ -905,6 +982,7 @@ void pmksa_cache_add(struct hostapd_data *hapd, struct sta_info *sta, u8 *pmk, entry->expiration += dot11RSNAConfigPMKLifetime; entry->akmp = WPA_KEY_MGMT_IEEE8021X; memcpy(entry->spa, sta->addr, ETH_ALEN); + pmksa_cache_from_eapol_data(entry, sta->eapol_sm); /* Replace an old entry for the same STA (if found) with the new entry */ @@ -961,7 +1039,7 @@ static void pmksa_cache_free(struct hostapd_data *hapd) while (entry) { prev = entry; entry = entry->next; - free(prev); + _pmksa_cache_free_entry(prev); } eloop_cancel_timeout(pmksa_cache_expire, hapd, NULL); for (i = 0; i < PMKID_HASH_SIZE; i++) @@ -1001,7 +1079,7 @@ struct wpa_ie_data { }; -static int wpa_parse_wpa_ie_wpa(u8 *wpa_ie, size_t wpa_ie_len, +static int wpa_parse_wpa_ie_wpa(const u8 *wpa_ie, size_t wpa_ie_len, struct wpa_ie_data *data) { struct wpa_ie_hdr *hdr; @@ -1080,7 +1158,7 @@ static int wpa_parse_wpa_ie_wpa(u8 *wpa_ie, size_t wpa_ie_len, } -static int wpa_parse_wpa_ie_rsn(u8 *rsn_ie, size_t rsn_ie_len, +static int wpa_parse_wpa_ie_rsn(const u8 *rsn_ie, size_t rsn_ie_len, struct wpa_ie_data *data) { struct rsn_ie_hdr *hdr; @@ -1174,7 +1252,7 @@ static int wpa_parse_wpa_ie_rsn(u8 *rsn_ie, size_t rsn_ie_len, int wpa_validate_wpa_ie(struct hostapd_data *hapd, struct sta_info *sta, - u8 *wpa_ie, size_t wpa_ie_len, int version) + const u8 *wpa_ie, size_t wpa_ie_len, int version) { struct wpa_ie_data data; int ciphers, key_mgmt, res, i; @@ -1374,6 +1452,7 @@ void wpa_free_station(struct sta_info *sta) eloop_cancel_timeout(wpa_send_eapol_timeout, sm->hapd, sta); eloop_cancel_timeout(wpa_sm_call_step, sm->hapd, sta->wpa_sm); + eloop_cancel_timeout(rsn_preauth_finished_cb, sm->hapd, sta); free(sm->last_rx_eapol_key); free(sm); sta->wpa_sm = NULL; @@ -2090,6 +2169,7 @@ SM_STATE(WPA_PTK, INITPMK) if (sm->sta->pmksa) { wpa_printf(MSG_DEBUG, "WPA: PMK from PMKSA cache"); memcpy(sm->PMK, sm->sta->pmksa->pmk, WPA_PMK_LEN); + pmksa_cache_to_eapol_data(sm->sta->pmksa, sm->sta->eapol_sm); } else if ((key = ieee802_1x_get_key_crypt(sm->sta->eapol_sm, &len))) { wpa_printf(MSG_DEBUG, "WPA: PMK from EAPOL state machine " "(len=%lu)", (unsigned long) len); @@ -2296,6 +2376,8 @@ SM_STATE(WPA_PTK, PTKINITDONE) HOSTAPD_LEVEL_INFO, "pairwise key handshake completed " "(%s)", sm->sta->wpa == WPA_VERSION_WPA ? "WPA" : "RSN"); + if (sm->sta->wpa_key_mgmt == WPA_KEY_MGMT_PSK) + accounting_sta_start(sm->hapd, sm->sta); } -- cgit v1.1