diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c index 6e4169ba9..dea552c1c 100644 --- a/src/ap/hostapd.c +++ b/src/ap/hostapd.c @@ -614,51 +614,145 @@ static int hostapd_das_nas_mismatch(struct hostapd_data *hapd, static struct sta_info * hostapd_das_find_sta(struct hostapd_data *hapd, - struct radius_das_attrs *attr) + struct radius_das_attrs *attr, + int *multi) { - struct sta_info *sta = NULL; + struct sta_info *selected, *sta; char buf[128]; + int num_attr = 0; + int count; - if (attr->sta_addr) + *multi = 0; + + for (sta = hapd->sta_list; sta; sta = sta->next) + sta->radius_das_match = 1; + + if (attr->sta_addr) { + num_attr++; sta = ap_get_sta(hapd, attr->sta_addr); + if (!sta) { + wpa_printf(MSG_DEBUG, + "RADIUS DAS: No Calling-Station-Id match"); + return NULL; + } - if (sta == NULL && attr->acct_session_id && - attr->acct_session_id_len == 17) { + selected = sta; for (sta = hapd->sta_list; sta; sta = sta->next) { + if (sta != selected) + sta->radius_das_match = 0; + } + wpa_printf(MSG_DEBUG, "RADIUS DAS: Calling-Station-Id match"); + } + + if (attr->acct_session_id) { + num_attr++; + if (attr->acct_session_id_len != 17) { + wpa_printf(MSG_DEBUG, + "RADIUS DAS: Acct-Session-Id cannot match"); + return NULL; + } + count = 0; + + for (sta = hapd->sta_list; sta; sta = sta->next) { + if (!sta->radius_das_match) + continue; os_snprintf(buf, sizeof(buf), "%08X-%08X", sta->acct_session_id_hi, sta->acct_session_id_lo); - if (os_memcmp(attr->acct_session_id, buf, 17) == 0) - break; + if (os_memcmp(attr->acct_session_id, buf, 17) != 0) + sta->radius_das_match = 0; + else + count++; } + + if (count == 0) { + wpa_printf(MSG_DEBUG, + "RADIUS DAS: No matches remaining after Acct-Session-Id check"); + return NULL; + } + wpa_printf(MSG_DEBUG, "RADIUS DAS: Acct-Session-Id match"); } - if (sta == NULL && attr->cui) { + if (attr->cui) { + num_attr++; + count = 0; + for (sta = hapd->sta_list; sta; sta = sta->next) { struct wpabuf *cui; + + if (!sta->radius_das_match) + continue; cui = ieee802_1x_get_radius_cui(sta->eapol_sm); - if (cui && wpabuf_len(cui) == attr->cui_len && + if (!cui || wpabuf_len(cui) != attr->cui_len || os_memcmp(wpabuf_head(cui), attr->cui, - attr->cui_len) == 0) - break; + attr->cui_len) != 0) + sta->radius_das_match = 0; + else + count++; } + + if (count == 0) { + wpa_printf(MSG_DEBUG, + "RADIUS DAS: No matches remaining after Chargeable-User-Identity check"); + return NULL; + } + wpa_printf(MSG_DEBUG, + "RADIUS DAS: Chargeable-User-Identity match"); } - if (sta == NULL && attr->user_name) { + if (attr->user_name) { + num_attr++; + count = 0; + for (sta = hapd->sta_list; sta; sta = sta->next) { u8 *identity; size_t identity_len; + + if (!sta->radius_das_match) + continue; identity = ieee802_1x_get_identity(sta->eapol_sm, &identity_len); - if (identity && - identity_len == attr->user_name_len && + if (!identity || + identity_len != attr->user_name_len || os_memcmp(identity, attr->user_name, identity_len) - == 0) - break; + != 0) + sta->radius_das_match = 0; + else + count++; + } + + if (count == 0) { + wpa_printf(MSG_DEBUG, + "RADIUS DAS: No matches remaining after User-Name check"); + return NULL; + } + wpa_printf(MSG_DEBUG, + "RADIUS DAS: User-Name match"); + } + + if (num_attr == 0) { + /* + * In theory, we could match all current associations, but it + * seems safer to just reject requests that do not include any + * session identification attributes. + */ + wpa_printf(MSG_DEBUG, + "RADIUS DAS: No session identification attributes included"); + return NULL; + } + + selected = NULL; + for (sta = hapd->sta_list; sta; sta = sta->next) { + if (sta->radius_das_match) { + if (selected) { + *multi = 1; + return NULL; + } + selected = sta; } } - return sta; + return selected; } @@ -667,14 +761,24 @@ hostapd_das_disconnect(void *ctx, struct radius_das_attrs *attr) { struct hostapd_data *hapd = ctx; struct sta_info *sta; + int multi; if (hostapd_das_nas_mismatch(hapd, attr)) return RADIUS_DAS_NAS_MISMATCH; - sta = hostapd_das_find_sta(hapd, attr); - if (sta == NULL) + sta = hostapd_das_find_sta(hapd, attr, &multi); + if (sta == NULL) { + if (multi) { + wpa_printf(MSG_DEBUG, + "RADIUS DAS: Multiple sessions match - not supported"); + return RADIUS_DAS_MULTI_SESSION_MATCH; + } + wpa_printf(MSG_DEBUG, "RADIUS DAS: No matching session found"); return RADIUS_DAS_SESSION_NOT_FOUND; + } + wpa_printf(MSG_DEBUG, "RADIUS DAS: Found a matching session " MACSTR + " - disconnecting", MAC2STR(sta->addr)); wpa_auth_pmksa_remove(hapd->wpa_auth, sta->addr); hostapd_drv_sta_deauth(hapd, sta->addr, diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h index 59d0e2915..57551ab17 100644 --- a/src/ap/sta_info.h +++ b/src/ap/sta_info.h @@ -85,6 +85,7 @@ struct sta_info { unsigned int remediation:1; unsigned int hs20_deauth_requested:1; unsigned int session_timeout_set:1; + unsigned int radius_das_match:1; u16 auth_alg; diff --git a/src/radius/radius_das.c b/src/radius/radius_das.c index 9655f4cea..7aa703cc6 100644 --- a/src/radius/radius_das.c +++ b/src/radius/radius_das.c @@ -147,6 +147,12 @@ static struct radius_msg * radius_das_disconnect(struct radius_das_data *das, "%s:%d", abuf, from_port); error = 503; break; + case RADIUS_DAS_MULTI_SESSION_MATCH: + wpa_printf(MSG_INFO, + "DAS: Multiple sessions match for request from %s:%d", + abuf, from_port); + error = 508; + break; case RADIUS_DAS_SUCCESS: error = 0; break; diff --git a/src/radius/radius_das.h b/src/radius/radius_das.h index e3ed5408e..1d76c2662 100644 --- a/src/radius/radius_das.h +++ b/src/radius/radius_das.h @@ -14,7 +14,8 @@ struct radius_das_data; enum radius_das_res { RADIUS_DAS_SUCCESS, RADIUS_DAS_NAS_MISMATCH, - RADIUS_DAS_SESSION_NOT_FOUND + RADIUS_DAS_SESSION_NOT_FOUND, + RADIUS_DAS_MULTI_SESSION_MATCH, }; struct radius_das_attrs {