diff --git a/hostapd/config_file.c b/hostapd/config_file.c index d38c3f836..be05840e8 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -2814,6 +2814,8 @@ static int hostapd_config_fill(struct hostapd_config *conf, bss->osen = atoi(pos); } else if (os_strcmp(buf, "anqp_domain_id") == 0) { bss->anqp_domain_id = atoi(pos); + } else if (os_strcmp(buf, "hs20_deauth_req_timeout") == 0) { + bss->hs20_deauth_req_timeout = atoi(pos); } else if (os_strcmp(buf, "hs20_oper_friendly_name") == 0) { if (hs20_parse_oper_friendly_name(bss, pos, line) < 0) errors++; diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c index 53d8d3dc9..dbdc8c675 100644 --- a/hostapd/ctrl_iface.c +++ b/hostapd/ctrl_iface.c @@ -642,6 +642,56 @@ static int hostapd_ctrl_iface_hs20_wnm_notif(struct hostapd_data *hapd, return hs20_send_wnm_notification(hapd, addr, 1, url); } + +static int hostapd_ctrl_iface_hs20_deauth_req(struct hostapd_data *hapd, + const char *cmd) +{ + u8 addr[ETH_ALEN]; + int code, reauth_delay, ret; + const char *pos; + size_t url_len; + struct wpabuf *req; + + /* [URL] */ + if (hwaddr_aton(cmd, addr)) + return -1; + + pos = os_strchr(cmd, ' '); + if (pos == NULL) + return -1; + pos++; + code = atoi(pos); + + pos = os_strchr(pos, ' '); + if (pos == NULL) + return -1; + pos++; + reauth_delay = atoi(pos); + + url_len = 0; + pos = os_strchr(pos, ' '); + if (pos) { + pos++; + url_len = os_strlen(pos); + } + + req = wpabuf_alloc(4 + url_len); + if (req == NULL) + return -1; + wpabuf_put_u8(req, code); + wpabuf_put_le16(req, reauth_delay); + wpabuf_put_u8(req, url_len); + if (pos) + wpabuf_put_data(req, pos, url_len); + + wpa_printf(MSG_DEBUG, "HS 2.0: Send WNM-Notification to " MACSTR + " to indicate imminent deauthentication (code=%d " + "reauth_delay=%d)", MAC2STR(addr), code, reauth_delay); + ret = hs20_send_wnm_notification_deauth_req(hapd, addr, req); + wpabuf_free(req); + return ret; +} + #endif /* CONFIG_HS20 */ @@ -1379,6 +1429,9 @@ static void hostapd_ctrl_iface_receive(int sock, void *eloop_ctx, } else if (os_strncmp(buf, "HS20_WNM_NOTIF ", 15) == 0) { if (hostapd_ctrl_iface_hs20_wnm_notif(hapd, buf + 15)) reply_len = -1; + } else if (os_strncmp(buf, "HS20_DEAUTH_REQ ", 16) == 0) { + if (hostapd_ctrl_iface_hs20_deauth_req(hapd, buf + 16)) + reply_len = -1; #endif /* CONFIG_HS20 */ #ifdef CONFIG_WNM } else if (os_strncmp(buf, "DISASSOC_IMMINENT ", 18) == 0) { diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index 332cee0fd..2903df81c 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -1590,6 +1590,13 @@ own_ip_addr=127.0.0.1 # information. 0 = Some of the ANQP information is unique to this AP (default). #anqp_domain_id=1234 +# Deauthentication request timeout +# If the RADIUS server indicates that the station is not allowed to connect to +# the BSS/ESS, the AP can allow the station some time to download a +# notification page (URL included in the message). This parameter sets that +# timeout in seconds. +#hs20_deauth_req_timeout=60 + # Operator Friendly Name # This parameter can be used to configure one or more Operator Friendly Name # Duples. Each entry has a two or three character language code (ISO-639) diff --git a/hostapd/hostapd_cli.c b/hostapd/hostapd_cli.c index 4d5f269c4..8caca4f6a 100644 --- a/hostapd/hostapd_cli.c +++ b/hostapd/hostapd_cli.c @@ -763,6 +763,31 @@ static int hostapd_cli_cmd_hs20_wnm_notif(struct wpa_ctrl *ctrl, int argc, } +static int hostapd_cli_cmd_hs20_deauth_req(struct wpa_ctrl *ctrl, int argc, + char *argv[]) +{ + char buf[300]; + int res; + + if (argc < 3) { + printf("Invalid 'hs20_deauth_req' command - at least three arguments (STA addr, Code, Re-auth Delay) are needed\n"); + return -1; + } + + if (argc > 3) + res = os_snprintf(buf, sizeof(buf), + "HS20_DEAUTH_REQ %s %s %s %s", + argv[0], argv[1], argv[2], argv[3]); + else + res = os_snprintf(buf, sizeof(buf), + "HS20_DEAUTH_REQ %s %s %s", + argv[0], argv[1], argv[2]); + if (res < 0 || res >= (int) sizeof(buf)) + return -1; + return wpa_ctrl_command(ctrl, buf); +} + + static int hostapd_cli_cmd_quit(struct wpa_ctrl *ctrl, int argc, char *argv[]) { hostapd_cli_quit = 1; @@ -962,6 +987,7 @@ static struct hostapd_cli_cmd hostapd_cli_commands[] = { { "send_qos_map_conf", hostapd_cli_cmd_send_qos_map_conf }, { "chan_switch", hostapd_cli_cmd_chan_switch }, { "hs20_wnm_notif", hostapd_cli_cmd_hs20_wnm_notif }, + { "hs20_deauth_req", hostapd_cli_cmd_hs20_deauth_req }, { NULL, NULL } }; diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index 73645cdd5..2eea266f2 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -465,6 +465,7 @@ struct hostapd_bss_config { size_t hs20_connection_capability_len; u8 *hs20_operating_class; u8 hs20_operating_class_len; + unsigned int hs20_deauth_req_timeout; #endif /* CONFIG_HS20 */ u8 wps_rf_bands; /* RF bands for WPS (WPS_RF_*) */ diff --git a/src/ap/hs20.c b/src/ap/hs20.c index d521d2cef..d7909fad4 100644 --- a/src/ap/hs20.c +++ b/src/ap/hs20.c @@ -140,3 +140,38 @@ int hs20_send_wnm_notification(struct hostapd_data *hapd, const u8 *addr, return ret; } + + +int hs20_send_wnm_notification_deauth_req(struct hostapd_data *hapd, + const u8 *addr, + const struct wpabuf *payload) +{ + struct wpabuf *buf; + int ret; + + /* TODO: should refuse to send notification if the STA is not associated + * or if the STA did not indicate support for WNM-Notification */ + + buf = wpabuf_alloc(4 + 6 + wpabuf_len(payload)); + if (buf == NULL) + return -1; + + wpabuf_put_u8(buf, WLAN_ACTION_WNM); + wpabuf_put_u8(buf, WNM_NOTIFICATION_REQ); + wpabuf_put_u8(buf, 1); /* Dialog token */ + wpabuf_put_u8(buf, 1); /* Type - 1 reserved for WFA */ + + /* Deauthentication Imminent Notice subelement */ + wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC); + wpabuf_put_u8(buf, 4 + wpabuf_len(payload)); + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, HS20_WNM_DEAUTH_IMMINENT_NOTICE); + wpabuf_put_buf(buf, payload); + + ret = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, addr, + wpabuf_head(buf), wpabuf_len(buf)); + + wpabuf_free(buf); + + return ret; +} diff --git a/src/ap/hs20.h b/src/ap/hs20.h index c7ffa3687..152439f4d 100644 --- a/src/ap/hs20.h +++ b/src/ap/hs20.h @@ -15,5 +15,8 @@ u8 * hostapd_eid_hs20_indication(struct hostapd_data *hapd, u8 *eid); u8 * hostapd_eid_osen(struct hostapd_data *hapd, u8 *eid); int hs20_send_wnm_notification(struct hostapd_data *hapd, const u8 *addr, u8 osu_method, const char *url); +int hs20_send_wnm_notification_deauth_req(struct hostapd_data *hapd, + const u8 *addr, + const struct wpabuf *payload); #endif /* HS20_H */ diff --git a/src/ap/ieee802_1x.c b/src/ap/ieee802_1x.c index f07b6bf87..3c7b1edee 100644 --- a/src/ap/ieee802_1x.c +++ b/src/ap/ieee802_1x.c @@ -1265,6 +1265,27 @@ static void ieee802_1x_hs20_sub_rem(struct sta_info *sta, u8 *pos, size_t len) /* TODO: assign the STA into remediation VLAN or add filtering */ } + +static void ieee802_1x_hs20_deauth_req(struct hostapd_data *hapd, + struct sta_info *sta, u8 *pos, + size_t len) +{ + if (len < 3) + return; /* Malformed information */ + sta->hs20_deauth_requested = 1; + wpa_printf(MSG_DEBUG, "HS 2.0: Deauthentication request - Code %u " + "Re-auth Delay %u", + *pos, WPA_GET_LE16(pos + 1)); + wpabuf_free(sta->hs20_deauth_req); + sta->hs20_deauth_req = wpabuf_alloc(len + 1); + if (sta->hs20_deauth_req) { + wpabuf_put_data(sta->hs20_deauth_req, pos, 3); + wpabuf_put_u8(sta->hs20_deauth_req, len - 3); + wpabuf_put_data(sta->hs20_deauth_req, pos + 3, len - 3); + } + ap_sta_session_timeout(hapd, sta, hapd->conf->hs20_deauth_req_timeout); +} + #endif /* CONFIG_HS20 */ @@ -1278,6 +1299,8 @@ static void ieee802_1x_check_hs20(struct hostapd_data *hapd, buf = NULL; sta->remediation = 0; + sta->hs20_deauth_requested = 0; + for (;;) { if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_VENDOR_SPECIFIC, &buf, &len, buf) < 0) @@ -1302,6 +1325,9 @@ static void ieee802_1x_check_hs20(struct hostapd_data *hapd, case RADIUS_VENDOR_ATTR_WFA_HS20_SUBSCR_REMEDIATION: ieee802_1x_hs20_sub_rem(sta, pos, sublen); break; + case RADIUS_VENDOR_ATTR_WFA_HS20_DEAUTH_REQ: + ieee802_1x_hs20_deauth_req(hapd, sta, pos, sublen); + break; } } #endif /* CONFIG_HS20 */ @@ -1468,6 +1494,7 @@ ieee802_1x_receive_auth(struct radius_msg *msg, struct radius_msg *req, ieee802_1x_update_sta_cui(hapd, sta, msg); ieee802_1x_check_hs20(hapd, sta, msg); if (sm->eap_if->eapKeyAvailable && !sta->remediation && + !sta->hs20_deauth_requested && wpa_auth_pmksa_add(sta->wpa_sm, sm->eapol_key_crypt, session_timeout_set ? (int) session_timeout : -1, sm) == 0) { @@ -2234,11 +2261,20 @@ static void ieee802_1x_finished(struct hostapd_data *hapd, os_free(sta->remediation_url); sta->remediation_url = NULL; } + + if (sta->hs20_deauth_req) { + wpa_printf(MSG_DEBUG, "HS 2.0: Send WNM-Notification " + "to " MACSTR " to indicate imminent " + "deauthentication", MAC2STR(sta->addr)); + hs20_send_wnm_notification_deauth_req( + hapd, sta->addr, sta->hs20_deauth_req); + } } #endif /* CONFIG_HS20 */ key = ieee802_1x_get_key(sta->eapol_sm, &len); if (success && key && len >= PMK_LEN && !sta->remediation && + !sta->hs20_deauth_requested && wpa_auth_pmksa_add(sta->wpa_sm, key, dot11RSNAConfigPMKLifetime, sta->eapol_sm) == 0) { hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_WPA, diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c index 811a42a17..ada110641 100644 --- a/src/ap/sta_info.c +++ b/src/ap/sta_info.c @@ -266,6 +266,7 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta) os_free(sta->identity); os_free(sta->radius_cui); os_free(sta->remediation_url); + wpabuf_free(sta->hs20_deauth_req); #ifdef CONFIG_SAE sae_clear_data(sta->sae); diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h index a784439cf..df86cc257 100644 --- a/src/ap/sta_info.h +++ b/src/ap/sta_info.h @@ -58,6 +58,7 @@ struct sta_info { unsigned int no_p2p_set:1; unsigned int qos_map_enabled:1; unsigned int remediation:1; + unsigned int hs20_deauth_requested:1; u16 auth_alg; u8 previous_ap[6]; @@ -128,6 +129,7 @@ struct sta_info { struct wpabuf *hs20_ie; /* HS 2.0 IE from (Re)Association Request */ u8 remediation_method; char *remediation_url; /* HS 2.0 Subscription Remediation Server URL */ + struct wpabuf *hs20_deauth_req; struct os_reltime connected_time; diff --git a/src/radius/radius.h b/src/radius/radius.h index b39aa7b0c..8cb7a0f17 100644 --- a/src/radius/radius.h +++ b/src/radius/radius.h @@ -171,6 +171,7 @@ enum { RADIUS_VENDOR_ATTR_WFA_HS20_SUBSCR_REMEDIATION = 1, RADIUS_VENDOR_ATTR_WFA_HS20_AP_VERSION = 2, RADIUS_VENDOR_ATTR_WFA_HS20_STA_VERSION = 3, + RADIUS_VENDOR_ATTR_WFA_HS20_DEAUTH_REQ = 4, }; #ifdef _MSC_VER