From f456940ef359b420b54df2f2578b49c6ff2baa04 Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Wed, 25 Apr 2018 01:23:30 +0300 Subject: [PATCH] HS 2.0: CoA-Request processing for Terms and Conditions filtering Extend RADIUS DAS to support CoA-Request packets for the case where the HS 2.0 Terms And Conditions filtering VSA is used to remove filtering. Signed-off-by: Jouni Malinen --- src/ap/hostapd.c | 44 +++++++++ src/ap/hs20.c | 25 +++++ src/ap/hs20.h | 2 + src/ap/ieee802_1x.c | 9 +- src/common/wpa_ctrl.h | 3 + src/radius/radius_das.c | 209 +++++++++++++++++++++++++++++++++++++--- src/radius/radius_das.h | 5 + 7 files changed, 276 insertions(+), 21 deletions(-) diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c index d2eb0441c..49cceb88b 100644 --- a/src/ap/hostapd.c +++ b/src/ap/hostapd.c @@ -49,6 +49,7 @@ #include "rrm.h" #include "fils_hlp.h" #include "acs.h" +#include "hs20.h" static int hostapd_flush_old_stations(struct hostapd_data *hapd, u16 reason); @@ -900,6 +901,48 @@ hostapd_das_disconnect(void *ctx, struct radius_das_attrs *attr) return RADIUS_DAS_SUCCESS; } + +#ifdef CONFIG_HS20 +static enum radius_das_res +hostapd_das_coa(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, &multi); + if (!sta) { + 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 + " - CoA", MAC2STR(sta->addr)); + + if (attr->hs20_t_c_filtering) { + if (attr->hs20_t_c_filtering[0] & BIT(0)) { + wpa_printf(MSG_DEBUG, + "HS 2.0: Unexpected Terms and Conditions filtering required in CoA-Request"); + return RADIUS_DAS_COA_FAILED; + } + + hs20_t_c_filtering(hapd, sta, 0); + } + + return RADIUS_DAS_SUCCESS; +} +#else /* CONFIG_HS20 */ +#define hostapd_das_coa NULL +#endif /* CONFIG_HS20 */ + #endif /* CONFIG_NO_RADIUS */ @@ -1074,6 +1117,7 @@ static int hostapd_setup_bss(struct hostapd_data *hapd, int first) conf->radius_das_require_message_authenticator; das_conf.ctx = hapd; das_conf.disconnect = hostapd_das_disconnect; + das_conf.coa = hostapd_das_coa; hapd->radius_das = radius_das_init(&das_conf); if (hapd->radius_das == NULL) { wpa_printf(MSG_ERROR, "RADIUS DAS initialization " diff --git a/src/ap/hs20.c b/src/ap/hs20.c index 79523f91e..9770c34d2 100644 --- a/src/ap/hs20.c +++ b/src/ap/hs20.c @@ -11,9 +11,11 @@ #include "common.h" #include "common/ieee802_11_defs.h" +#include "common/wpa_ctrl.h" #include "hostapd.h" #include "ap_config.h" #include "ap_drv_ops.h" +#include "sta_info.h" #include "hs20.h" @@ -218,3 +220,26 @@ int hs20_send_wnm_notification_t_c(struct hostapd_data *hapd, return ret; } + + +void hs20_t_c_filtering(struct hostapd_data *hapd, struct sta_info *sta, + int enabled) +{ + if (enabled) { + wpa_printf(MSG_DEBUG, + "HS 2.0: Terms and Conditions filtering required for " + MACSTR, MAC2STR(sta->addr)); + sta->hs20_t_c_filtering = 1; + /* TODO: Enable firewall filtering for the STA */ + wpa_msg(hapd->msg_ctx, MSG_INFO, HS20_T_C_FILTERING_ADD MACSTR, + MAC2STR(sta->addr)); + } else { + wpa_printf(MSG_DEBUG, + "HS 2.0: Terms and Conditions filtering not required for " + MACSTR, MAC2STR(sta->addr)); + sta->hs20_t_c_filtering = 0; + /* TODO: Disable firewall filtering for the STA */ + wpa_msg(hapd->msg_ctx, MSG_INFO, + HS20_T_C_FILTERING_REMOVE MACSTR, MAC2STR(sta->addr)); + } +} diff --git a/src/ap/hs20.h b/src/ap/hs20.h index 70ecc9473..bf3980628 100644 --- a/src/ap/hs20.h +++ b/src/ap/hs20.h @@ -20,5 +20,7 @@ int hs20_send_wnm_notification_deauth_req(struct hostapd_data *hapd, const struct wpabuf *payload); int hs20_send_wnm_notification_t_c(struct hostapd_data *hapd, const u8 *addr); +void hs20_t_c_filtering(struct hostapd_data *hapd, struct sta_info *sta, + int enabled); #endif /* HS20_H */ diff --git a/src/ap/ieee802_1x.c b/src/ap/ieee802_1x.c index 630787de4..51043ba6a 100644 --- a/src/ap/ieee802_1x.c +++ b/src/ap/ieee802_1x.c @@ -1632,14 +1632,7 @@ static void ieee802_1x_hs20_t_c_filtering(struct hostapd_data *hapd, wpa_printf(MSG_DEBUG, "HS 2.0: Terms and Conditions filtering %02x %02x %02x %02x", pos[0], pos[1], pos[2], pos[3]); - if (pos[0] & BIT(0)) { - wpa_printf(MSG_DEBUG, - "HS 2.0: Terms and Conditions filtering required"); - sta->hs20_t_c_filtering = 1; - /* TODO: Enable firewall filtering for the STA */ - } else { - sta->hs20_t_c_filtering = 0; - } + hs20_t_c_filtering(hapd, sta, pos[0] & BIT(0)); } #endif /* CONFIG_HS20 */ diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h index a9da1cd80..c7a7c1e27 100644 --- a/src/common/wpa_ctrl.h +++ b/src/common/wpa_ctrl.h @@ -298,6 +298,9 @@ extern "C" { #define AP_REJECTED_MAX_STA "AP-REJECTED-MAX-STA " #define AP_REJECTED_BLOCKED_STA "AP-REJECTED-BLOCKED-STA " +#define HS20_T_C_FILTERING_ADD "HS20-T-C-FILTERING-ADD " +#define HS20_T_C_FILTERING_REMOVE "HS20-T-C-FILTERING-REMOVE " + #define AP_EVENT_ENABLED "AP-ENABLED " #define AP_EVENT_DISABLED "AP-DISABLED " diff --git a/src/radius/radius_das.c b/src/radius/radius_das.c index ed24c192d..0a0cf8ed4 100644 --- a/src/radius/radius_das.c +++ b/src/radius/radius_das.c @@ -27,6 +27,7 @@ struct radius_das_data { void *ctx; enum radius_das_res (*disconnect)(void *ctx, struct radius_das_attrs *attr); + enum radius_das_res (*coa)(void *ctx, struct radius_das_attrs *attr); }; @@ -161,6 +162,10 @@ static struct radius_msg * radius_das_disconnect(struct radius_das_data *das, abuf, from_port); error = 508; break; + case RADIUS_DAS_COA_FAILED: + /* not used with Disconnect-Request */ + error = 405; + break; case RADIUS_DAS_SUCCESS: error = 0; break; @@ -184,6 +189,195 @@ fail: } +static struct radius_msg * radius_das_coa(struct radius_das_data *das, + struct radius_msg *msg, + const char *abuf, int from_port) +{ + struct radius_hdr *hdr; + struct radius_msg *reply; + u8 allowed[] = { + RADIUS_ATTR_USER_NAME, + RADIUS_ATTR_NAS_IP_ADDRESS, + RADIUS_ATTR_CALLING_STATION_ID, + RADIUS_ATTR_NAS_IDENTIFIER, + RADIUS_ATTR_ACCT_SESSION_ID, + RADIUS_ATTR_ACCT_MULTI_SESSION_ID, + RADIUS_ATTR_EVENT_TIMESTAMP, + RADIUS_ATTR_MESSAGE_AUTHENTICATOR, + RADIUS_ATTR_CHARGEABLE_USER_IDENTITY, +#ifdef CONFIG_HS20 + RADIUS_ATTR_VENDOR_SPECIFIC, +#endif /* CONFIG_HS20 */ +#ifdef CONFIG_IPV6 + RADIUS_ATTR_NAS_IPV6_ADDRESS, +#endif /* CONFIG_IPV6 */ + 0 + }; + int error = 405; + u8 attr; + enum radius_das_res res; + struct radius_das_attrs attrs; + u8 *buf; + size_t len; + char tmp[100]; + u8 sta_addr[ETH_ALEN]; + + hdr = radius_msg_get_hdr(msg); + + if (!das->coa) { + wpa_printf(MSG_INFO, "DAS: CoA not supported"); + goto fail; + } + + attr = radius_msg_find_unlisted_attr(msg, allowed); + if (attr) { + wpa_printf(MSG_INFO, + "DAS: Unsupported attribute %u in CoA-Request from %s:%d", + attr, abuf, from_port); + error = 401; + goto fail; + } + + os_memset(&attrs, 0, sizeof(attrs)); + + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IP_ADDRESS, + &buf, &len, NULL) == 0) { + if (len != 4) { + wpa_printf(MSG_INFO, "DAS: Invalid NAS-IP-Address from %s:%d", + abuf, from_port); + error = 407; + goto fail; + } + attrs.nas_ip_addr = buf; + } + +#ifdef CONFIG_IPV6 + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IPV6_ADDRESS, + &buf, &len, NULL) == 0) { + if (len != 16) { + wpa_printf(MSG_INFO, "DAS: Invalid NAS-IPv6-Address from %s:%d", + abuf, from_port); + error = 407; + goto fail; + } + attrs.nas_ipv6_addr = buf; + } +#endif /* CONFIG_IPV6 */ + + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IDENTIFIER, + &buf, &len, NULL) == 0) { + attrs.nas_identifier = buf; + attrs.nas_identifier_len = len; + } + + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_CALLING_STATION_ID, + &buf, &len, NULL) == 0) { + if (len >= sizeof(tmp)) + len = sizeof(tmp) - 1; + os_memcpy(tmp, buf, len); + tmp[len] = '\0'; + if (hwaddr_aton2(tmp, sta_addr) < 0) { + wpa_printf(MSG_INFO, "DAS: Invalid Calling-Station-Id " + "'%s' from %s:%d", tmp, abuf, from_port); + error = 407; + goto fail; + } + attrs.sta_addr = sta_addr; + } + + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_USER_NAME, + &buf, &len, NULL) == 0) { + attrs.user_name = buf; + attrs.user_name_len = len; + } + + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_ACCT_SESSION_ID, + &buf, &len, NULL) == 0) { + attrs.acct_session_id = buf; + attrs.acct_session_id_len = len; + } + + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_ACCT_MULTI_SESSION_ID, + &buf, &len, NULL) == 0) { + attrs.acct_multi_session_id = buf; + attrs.acct_multi_session_id_len = len; + } + + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_CHARGEABLE_USER_IDENTITY, + &buf, &len, NULL) == 0) { + attrs.cui = buf; + attrs.cui_len = len; + } + +#ifdef CONFIG_HS20 + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_VENDOR_SPECIFIC, + &buf, &len, NULL) == 0) { + if (len < 10 || WPA_GET_BE32(buf) != RADIUS_VENDOR_ID_WFA || + buf[4] != RADIUS_VENDOR_ATTR_WFA_HS20_T_C_FILTERING || + buf[5] < 6) { + wpa_printf(MSG_INFO, + "DAS: Unsupported attribute %u in CoA-Request from %s:%d", + attr, abuf, from_port); + error = 401; + goto fail; + } + attrs.hs20_t_c_filtering = &buf[6]; + } + + if (!attrs.hs20_t_c_filtering) { + wpa_printf(MSG_INFO, + "DAS: No supported authorization change attribute in CoA-Request from %s:%d", + abuf, from_port); + error = 402; + goto fail; + } +#endif /* CONFIG_HS20 */ + + res = das->coa(das->ctx, &attrs); + switch (res) { + case RADIUS_DAS_NAS_MISMATCH: + wpa_printf(MSG_INFO, "DAS: NAS mismatch from %s:%d", + abuf, from_port); + error = 403; + break; + case RADIUS_DAS_SESSION_NOT_FOUND: + wpa_printf(MSG_INFO, + "DAS: Session not found for request from %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_COA_FAILED: + wpa_printf(MSG_INFO, "DAS: CoA failed for request from %s:%d", + abuf, from_port); + error = 407; + break; + case RADIUS_DAS_SUCCESS: + error = 0; + break; + } + +fail: + reply = radius_msg_new(error ? RADIUS_CODE_COA_NAK : + RADIUS_CODE_COA_ACK, hdr->identifier); + if (!reply) + return NULL; + + if (error && + !radius_msg_add_attr_int32(reply, RADIUS_ATTR_ERROR_CAUSE, error)) { + radius_msg_free(reply); + return NULL; + } + + return reply; +} + + static void radius_das_receive(int sock, void *eloop_ctx, void *sock_ctx) { struct radius_das_data *das = eloop_ctx; @@ -270,19 +464,7 @@ static void radius_das_receive(int sock, void *eloop_ctx, void *sock_ctx) reply = radius_das_disconnect(das, msg, abuf, from_port); break; case RADIUS_CODE_COA_REQUEST: - /* TODO */ - reply = radius_msg_new(RADIUS_CODE_COA_NAK, - hdr->identifier); - if (reply == NULL) - break; - - /* Unsupported Service */ - if (!radius_msg_add_attr_int32(reply, RADIUS_ATTR_ERROR_CAUSE, - 405)) { - radius_msg_free(reply); - reply = NULL; - break; - } + reply = radius_das_coa(das, msg, abuf, from_port); break; default: wpa_printf(MSG_DEBUG, "DAS: Unexpected RADIUS code %u in " @@ -369,6 +551,7 @@ radius_das_init(struct radius_das_conf *conf) conf->require_message_authenticator; das->ctx = conf->ctx; das->disconnect = conf->disconnect; + das->coa = conf->coa; os_memcpy(&das->client_addr, conf->client_addr, sizeof(das->client_addr)); diff --git a/src/radius/radius_das.h b/src/radius/radius_das.h index 9863fdc1e..233d662f6 100644 --- a/src/radius/radius_das.h +++ b/src/radius/radius_das.h @@ -16,6 +16,7 @@ enum radius_das_res { RADIUS_DAS_NAS_MISMATCH, RADIUS_DAS_SESSION_NOT_FOUND, RADIUS_DAS_MULTI_SESSION_MATCH, + RADIUS_DAS_COA_FAILED, }; struct radius_das_attrs { @@ -35,6 +36,9 @@ struct radius_das_attrs { size_t acct_multi_session_id_len; const u8 *cui; size_t cui_len; + + /* Authorization changes */ + const u8 *hs20_t_c_filtering; }; struct radius_das_conf { @@ -48,6 +52,7 @@ struct radius_das_conf { void *ctx; enum radius_das_res (*disconnect)(void *ctx, struct radius_das_attrs *attr); + enum radius_das_res (*coa)(void *ctx, struct radius_das_attrs *attr); }; struct radius_das_data *