diff options
94 files changed, 4511 insertions, 202 deletions
diff --git a/hostapd/Android.mk b/hostapd/Android.mk index 5d6fe564..888ee2bb 100644 --- a/hostapd/Android.mk +++ b/hostapd/Android.mk @@ -201,6 +201,10 @@ L_CFLAGS += -DCONFIG_PEERKEY OBJS += src/ap/peerkey_auth.c endif +ifdef CONFIG_HS20 +NEED_AES_OMAC1=y +endif + ifdef CONFIG_IEEE80211W L_CFLAGS += -DCONFIG_IEEE80211W NEED_SHA256=y diff --git a/hostapd/Makefile b/hostapd/Makefile index 25c560f2..c541d434 100644 --- a/hostapd/Makefile +++ b/hostapd/Makefile @@ -185,6 +185,10 @@ CFLAGS += -DCONFIG_PEERKEY OBJS += ../src/ap/peerkey_auth.o endif +ifdef CONFIG_HS20 +NEED_AES_OMAC1=y +endif + ifdef CONFIG_IEEE80211W CFLAGS += -DCONFIG_IEEE80211W NEED_SHA256=y diff --git a/hostapd/config_file.c b/hostapd/config_file.c index 19d6ad32..fa7d14a8 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -129,6 +129,8 @@ static int hostapd_config_read_maclist(const char *fname, } while (fgets(buf, sizeof(buf), f)) { + int i, rem = 0; + line++; if (buf[0] == '#') @@ -143,14 +145,32 @@ static int hostapd_config_read_maclist(const char *fname, } if (buf[0] == '\0') continue; + pos = buf; + if (buf[0] == '-') { + rem = 1; + pos++; + } - if (hwaddr_aton(buf, addr)) { + if (hwaddr_aton(pos, addr)) { wpa_printf(MSG_ERROR, "Invalid MAC address '%s' at " - "line %d in '%s'", buf, line, fname); + "line %d in '%s'", pos, line, fname); fclose(f); return -1; } + if (rem) { + i = 0; + while (i < *num) { + if (os_memcmp((*acl)[i].addr, addr, ETH_ALEN) == + 0) { + os_remove_in_array(*acl, *num, + sizeof(**acl), i); + (*num)--; + } else + i++; + } + continue; + } vlan_id = 0; pos = buf; while (*pos != '\0' && *pos != ' ' && *pos != '\t') @@ -1556,6 +1576,196 @@ static int hs20_parse_oper_friendly_name(struct hostapd_bss_config *bss, } return 0; } + + +static int hs20_parse_icon(struct hostapd_bss_config *bss, char *pos) +{ + struct hs20_icon *icon; + char *end; + + icon = os_realloc_array(bss->hs20_icons, bss->hs20_icons_count + 1, + sizeof(struct hs20_icon)); + if (icon == NULL) + return -1; + bss->hs20_icons = icon; + icon = &bss->hs20_icons[bss->hs20_icons_count]; + os_memset(icon, 0, sizeof(*icon)); + + icon->width = atoi(pos); + pos = os_strchr(pos, ':'); + if (pos == NULL) + return -1; + pos++; + + icon->height = atoi(pos); + pos = os_strchr(pos, ':'); + if (pos == NULL) + return -1; + pos++; + + end = os_strchr(pos, ':'); + if (end == NULL || end - pos > 3) + return -1; + os_memcpy(icon->language, pos, end - pos); + pos = end + 1; + + end = os_strchr(pos, ':'); + if (end == NULL || end - pos > 255) + return -1; + os_memcpy(icon->type, pos, end - pos); + pos = end + 1; + + end = os_strchr(pos, ':'); + if (end == NULL || end - pos > 255) + return -1; + os_memcpy(icon->name, pos, end - pos); + pos = end + 1; + + if (os_strlen(pos) > 255) + return -1; + os_memcpy(icon->file, pos, os_strlen(pos)); + + bss->hs20_icons_count++; + + return 0; +} + + +static int hs20_parse_osu_ssid(struct hostapd_bss_config *bss, + char *pos, int line) +{ + size_t slen; + char *str; + + str = wpa_config_parse_string(pos, &slen); + if (str == NULL || slen < 1 || slen > HOSTAPD_MAX_SSID_LEN) { + wpa_printf(MSG_ERROR, "Line %d: Invalid SSID '%s'", line, pos); + return -1; + } + + os_memcpy(bss->osu_ssid, str, slen); + bss->osu_ssid_len = slen; + os_free(str); + + return 0; +} + + +static int hs20_parse_osu_server_uri(struct hostapd_bss_config *bss, + char *pos, int line) +{ + struct hs20_osu_provider *p; + + p = os_realloc_array(bss->hs20_osu_providers, + bss->hs20_osu_providers_count + 1, sizeof(*p)); + if (p == NULL) + return -1; + + bss->hs20_osu_providers = p; + bss->last_osu = &bss->hs20_osu_providers[bss->hs20_osu_providers_count]; + bss->hs20_osu_providers_count++; + os_memset(bss->last_osu, 0, sizeof(*p)); + bss->last_osu->server_uri = os_strdup(pos); + + return 0; +} + + +static int hs20_parse_osu_friendly_name(struct hostapd_bss_config *bss, + char *pos, int line) +{ + if (bss->last_osu == NULL) { + wpa_printf(MSG_ERROR, "Line %d: Unexpected OSU field", line); + return -1; + } + + if (parse_lang_string(&bss->last_osu->friendly_name, + &bss->last_osu->friendly_name_count, pos)) { + wpa_printf(MSG_ERROR, "Line %d: Invalid osu_friendly_name '%s'", + line, pos); + return -1; + } + + return 0; +} + + +static int hs20_parse_osu_nai(struct hostapd_bss_config *bss, + char *pos, int line) +{ + if (bss->last_osu == NULL) { + wpa_printf(MSG_ERROR, "Line %d: Unexpected OSU field", line); + return -1; + } + + os_free(bss->last_osu->osu_nai); + bss->last_osu->osu_nai = os_strdup(pos); + if (bss->last_osu->osu_nai == NULL) + return -1; + + return 0; +} + + +static int hs20_parse_osu_method_list(struct hostapd_bss_config *bss, char *pos, + int line) +{ + if (bss->last_osu == NULL) { + wpa_printf(MSG_ERROR, "Line %d: Unexpected OSU field", line); + return -1; + } + + if (hostapd_parse_intlist(&bss->last_osu->method_list, pos)) { + wpa_printf(MSG_ERROR, "Line %d: Invalid osu_method_list", line); + return -1; + } + + return 0; +} + + +static int hs20_parse_osu_icon(struct hostapd_bss_config *bss, char *pos, + int line) +{ + char **n; + struct hs20_osu_provider *p = bss->last_osu; + + if (p == NULL) { + wpa_printf(MSG_ERROR, "Line %d: Unexpected OSU field", line); + return -1; + } + + n = os_realloc_array(p->icons, p->icons_count + 1, sizeof(char *)); + if (n == NULL) + return -1; + p->icons = n; + p->icons[p->icons_count] = os_strdup(pos); + if (p->icons[p->icons_count] == NULL) + return -1; + p->icons_count++; + + return 0; +} + + +static int hs20_parse_osu_service_desc(struct hostapd_bss_config *bss, + char *pos, int line) +{ + if (bss->last_osu == NULL) { + wpa_printf(MSG_ERROR, "Line %d: Unexpected OSU field", line); + return -1; + } + + if (parse_lang_string(&bss->last_osu->service_desc, + &bss->last_osu->service_desc_count, pos)) { + wpa_printf(MSG_ERROR, "Line %d: Invalid osu_service_desc '%s'", + line, pos); + return -1; + } + + return 0; +} + #endif /* CONFIG_HS20 */ @@ -2790,6 +3000,12 @@ static int hostapd_config_fill(struct hostapd_config *conf, bss->hs20 = atoi(pos); } else if (os_strcmp(buf, "disable_dgaf") == 0) { bss->disable_dgaf = atoi(pos); + } else if (os_strcmp(buf, "osen") == 0) { + 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++; @@ -2831,6 +3047,39 @@ static int hostapd_config_fill(struct hostapd_config *conf, os_free(bss->hs20_operating_class); bss->hs20_operating_class = oper_class; bss->hs20_operating_class_len = oper_class_len; + } else if (os_strcmp(buf, "hs20_icon") == 0) { + if (hs20_parse_icon(bss, pos) < 0) { + wpa_printf(MSG_ERROR, "Line %d: Invalid " + "hs20_icon '%s'", line, pos); + errors++; + return errors; + } + } else if (os_strcmp(buf, "osu_ssid") == 0) { + if (hs20_parse_osu_ssid(bss, pos, line) < 0) + errors++; + } else if (os_strcmp(buf, "osu_server_uri") == 0) { + if (hs20_parse_osu_server_uri(bss, pos, line) < 0) + errors++; + } else if (os_strcmp(buf, "osu_friendly_name") == 0) { + if (hs20_parse_osu_friendly_name(bss, pos, line) < 0) + errors++; + } else if (os_strcmp(buf, "osu_nai") == 0) { + if (hs20_parse_osu_nai(bss, pos, line) < 0) + errors++; + } else if (os_strcmp(buf, "osu_method_list") == 0) { + if (hs20_parse_osu_method_list(bss, pos, line) < 0) + errors++; + } else if (os_strcmp(buf, "osu_icon") == 0) { + if (hs20_parse_osu_icon(bss, pos, line) < 0) + errors++; + } else if (os_strcmp(buf, "osu_service_desc") == 0) { + if (hs20_parse_osu_service_desc(bss, pos, line) < 0) + errors++; + } else if (os_strcmp(buf, "subscr_remediation_url") == 0) { + os_free(bss->subscr_remediation_url); + bss->subscr_remediation_url = os_strdup(pos); + } else if (os_strcmp(buf, "subscr_remediation_method") == 0) { + bss->subscr_remediation_method = atoi(pos); #endif /* CONFIG_HS20 */ #ifdef CONFIG_TESTING_OPTIONS #define PARSE_TEST_PROBABILITY(_val) \ @@ -2907,6 +3156,16 @@ static int hostapd_config_fill(struct hostapd_config *conf, "sae_groups value '%s'", line, pos); return 1; } + } else if (os_strcmp(buf, "local_pwr_constraint") == 0) { + int val = atoi(pos); + if (val < 0 || val > 255) { + wpa_printf(MSG_ERROR, "Line %d: Invalid local_pwr_constraint %d (expected 0..255)", + line, val); + return 1; + } + conf->local_pwr_constraint = val; + } else if (os_strcmp(buf, "spectrum_mgmt_required") == 0) { + conf->spectrum_mgmt_required = atoi(pos); } else { wpa_printf(MSG_ERROR, "Line %d: unknown configuration " "item '%s'", line, buf); diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c index 7f5de625..dbdc8c67 100644 --- a/hostapd/ctrl_iface.c +++ b/hostapd/ctrl_iface.c @@ -30,6 +30,7 @@ #include "ap/wps_hostapd.h" #include "ap/ctrl_iface_ap.h" #include "ap/ap_drv_ops.h" +#include "ap/hs20.h" #include "ap/wnm_ap.h" #include "ap/wpa_auth.h" #include "wps/wps_defs.h" @@ -617,6 +618,82 @@ static int hostapd_ctrl_iface_wps_get_status(struct hostapd_data *hapd, #endif /* CONFIG_WPS */ +#ifdef CONFIG_HS20 + +static int hostapd_ctrl_iface_hs20_wnm_notif(struct hostapd_data *hapd, + const char *cmd) +{ + u8 addr[ETH_ALEN]; + const char *url; + + if (hwaddr_aton(cmd, addr)) + return -1; + url = cmd + 17; + if (*url == '\0') { + url = NULL; + } else { + if (*url != ' ') + return -1; + url++; + if (*url == '\0') + url = NULL; + } + + 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; + + /* <STA MAC Addr> <Code(0/1)> <Re-auth-Delay(sec)> [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 */ + #ifdef CONFIG_INTERWORKING @@ -983,7 +1060,37 @@ static int hostapd_ctrl_iface_set(struct hostapd_data *hapd, char *cmd) hapd->ext_mgmt_frame_handling = atoi(value); #endif /* CONFIG_TESTING_OPTIONS */ } else { + struct sta_info *sta; + int vlan_id; + ret = hostapd_set_iface(hapd->iconf, hapd->conf, cmd, value); + if (ret) + return ret; + + if (os_strcasecmp(cmd, "deny_mac_file") == 0) { + for (sta = hapd->sta_list; sta; sta = sta->next) { + if (hostapd_maclist_found( + hapd->conf->deny_mac, + hapd->conf->num_deny_mac, sta->addr, + &vlan_id) && + (!vlan_id || vlan_id == sta->vlan_id)) + ap_sta_deauthenticate( + hapd, sta, + WLAN_REASON_UNSPECIFIED); + } + } else if (hapd->conf->macaddr_acl == DENY_UNLESS_ACCEPTED && + os_strcasecmp(cmd, "accept_mac_file") == 0) { + for (sta = hapd->sta_list; sta; sta = sta->next) { + if (!hostapd_maclist_found( + hapd->conf->accept_mac, + hapd->conf->num_accept_mac, + sta->addr, &vlan_id) || + (vlan_id && vlan_id != sta->vlan_id)) + ap_sta_deauthenticate( + hapd, sta, + WLAN_REASON_UNSPECIFIED); + } + } } return ret; @@ -1318,6 +1425,14 @@ static void hostapd_ctrl_iface_receive(int sock, void *eloop_ctx, if (hostapd_ctrl_iface_send_qos_map_conf(hapd, buf + 18)) reply_len = -1; #endif /* CONFIG_INTERWORKING */ +#ifdef CONFIG_HS20 + } 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) { if (hostapd_ctrl_iface_disassoc_imminent(hapd, buf + 18)) diff --git a/hostapd/eap_register.c b/hostapd/eap_register.c index 981e5394..8477c215 100644 --- a/hostapd/eap_register.c +++ b/hostapd/eap_register.c @@ -44,6 +44,13 @@ int eap_server_register_methods(void) ret = eap_server_unauth_tls_register(); #endif /* EAP_SERVER_TLS */ +#ifdef EAP_SERVER_TLS +#ifdef CONFIG_HS20 + if (ret == 0) + ret = eap_server_wfa_unauth_tls_register(); +#endif /* CONFIG_HS20 */ +#endif /* EAP_SERVER_TLS */ + #ifdef EAP_SERVER_MSCHAPV2 if (ret == 0) ret = eap_server_mschapv2_register(); diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index c503ce29..c745fe83 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -108,6 +108,20 @@ ssid=test # (default: 0 = disabled) #ieee80211h=1 +# Add Power Constraint element to Beacon and Probe Response frames +# This config option adds Power Constraint element when applicable and Country +# element is added. Power Constraint element is required by Transmit Power +# Control. This can be used only with ieee80211d=1. +# Valid values are 0..255. +#local_pwr_constraint=3 + +# Set Spectrum Management subfield in the Capability Information field. +# This config option forces the Spectrum Management bit to be set. When this +# option is not set, the value of the Spectrum Management bit depends on whether +# DFS or TPC is required by regulatory authorities. This can be used only with +# ieee80211d=1 and local_pwr_constraint configured. +#spectrum_mgmt_required=1 + # Operation mode (a = IEEE 802.11a, b = IEEE 802.11b, g = IEEE 802.11g, # ad = IEEE 802.11ad (60 GHz); a/g options are used with IEEE 802.11n, too, to # specify band) @@ -1568,6 +1582,21 @@ own_ip_addr=127.0.0.1 # forging such frames to other stations in the BSS. #disable_dgaf=1 +# OSU Server-Only Authenticated L2 Encryption Network +#osen=1 + +# ANQP Domain ID (0..65535) +# An identifier for a set of APs in an ESS that share the same common ANQP +# 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) @@ -1611,6 +1640,32 @@ own_ip_addr=127.0.0.1 # channels 36-48): #hs20_operating_class=5173 +# OSU icons +# <Icon Width>:<Icon Height>:<Language code>:<Icon Type>:<Name>:<file path> +#hs20_icon=32:32:eng:image/png:icon32:/tmp/icon32.png +#hs20_icon=64:64:eng:image/png:icon64:/tmp/icon64.png + +# OSU SSID (see ssid2 for format description) +# This is the SSID used for all OSU connections to all the listed OSU Providers. +#osu_ssid="example" + +# OSU Providers +# One or more sets of following parameter. Each OSU provider is started by the +# mandatory osu_server_uri item. The other parameters add information for the +# last added OSU provider. +# +#osu_server_uri=https://example.com/osu/ +#osu_friendly_name=eng:Example operator +#osu_friendly_name=fin:Esimerkkipalveluntarjoaja +#osu_nai=anonymous@example.com +#osu_method_list=1 0 +#osu_icon=icon32 +#osu_icon=icon64 +#osu_service_desc=eng:Example services +#osu_service_desc=fin:Esimerkkipalveluja +# +#osu_server_uri=... + ##### TESTING OPTIONS ######################################################### # # The options in this section are only available when the build configuration diff --git a/hostapd/hostapd.eap_user_sqlite b/hostapd/hostapd.eap_user_sqlite index f6883271..2c1f130f 100644 --- a/hostapd/hostapd.eap_user_sqlite +++ b/hostapd/hostapd.eap_user_sqlite @@ -2,6 +2,7 @@ CREATE TABLE users( identity TEXT PRIMARY KEY, methods TEXT, password TEXT, + remediation TEXT, phase2 INTEGER ); diff --git a/hostapd/hostapd_cli.c b/hostapd/hostapd_cli.c index eee85041..8caca4f6 100644 --- a/hostapd/hostapd_cli.c +++ b/hostapd/hostapd_cli.c @@ -743,6 +743,51 @@ static int hostapd_cli_cmd_send_qos_map_conf(struct wpa_ctrl *ctrl, } +static int hostapd_cli_cmd_hs20_wnm_notif(struct wpa_ctrl *ctrl, int argc, + char *argv[]) +{ + char buf[300]; + int res; + + if (argc < 2) { + printf("Invalid 'hs20_wnm_notif' command - two arguments (STA " + "addr and URL) are needed\n"); + return -1; + } + + res = os_snprintf(buf, sizeof(buf), "HS20_WNM_NOTIF %s %s", + argv[0], argv[1]); + if (res < 0 || res >= (int) sizeof(buf)) + return -1; + return wpa_ctrl_command(ctrl, buf); +} + + +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; @@ -941,6 +986,8 @@ static struct hostapd_cli_cmd hostapd_cli_commands[] = { { "set_qos_map_set", hostapd_cli_cmd_set_qos_map_set }, { "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.c b/src/ap/ap_config.c index 368b2020..b995892c 100644 --- a/src/ap/ap_config.c +++ b/src/ap/ap_config.c @@ -154,6 +154,8 @@ struct hostapd_config * hostapd_config_defaults(void) conf->rts_threshold = -1; /* use driver default: 2347 */ conf->fragm_threshold = -1; /* user driver default: 2346 */ conf->send_probe_response = 1; + /* Set to invalid value means do not add Power Constraint IE */ + conf->local_pwr_constraint = -1; conf->wmm_ac_params[0] = ac_be; conf->wmm_ac_params[1] = ac_bk; @@ -526,6 +528,25 @@ void hostapd_config_free_bss(struct hostapd_bss_config *conf) os_free(conf->hs20_wan_metrics); os_free(conf->hs20_connection_capability); os_free(conf->hs20_operating_class); + os_free(conf->hs20_icons); + if (conf->hs20_osu_providers) { + size_t i; + for (i = 0; i < conf->hs20_osu_providers_count; i++) { + struct hs20_osu_provider *p; + size_t j; + p = &conf->hs20_osu_providers[i]; + os_free(p->friendly_name); + os_free(p->server_uri); + os_free(p->method_list); + for (j = 0; j < p->icons_count; j++) + os_free(p->icons[j]); + os_free(p->icons); + os_free(p->osu_nai); + os_free(p->service_desc); + } + os_free(conf->hs20_osu_providers); + } + os_free(conf->subscr_remediation_url); #endif /* CONFIG_HS20 */ wpabuf_free(conf->vendor_elements); @@ -829,6 +850,18 @@ int hostapd_config_check(struct hostapd_config *conf, int full_config) return -1; } + if (full_config && conf->local_pwr_constraint != -1 && + !conf->ieee80211d) { + wpa_printf(MSG_ERROR, "Cannot add Power Constraint element without Country element"); + return -1; + } + + if (full_config && conf->spectrum_mgmt_required && + conf->local_pwr_constraint == -1) { + wpa_printf(MSG_ERROR, "Cannot set Spectrum Management bit without Country and Power Constraint elements"); + return -1; + } + for (i = 0; i < conf->num_bss; i++) { if (hostapd_config_check_bss(conf->bss[i], conf, full_config)) return -1; @@ -876,6 +909,11 @@ void hostapd_set_security_params(struct hostapd_bss_config *bss) bss->wpa_group = cipher; bss->wpa_pairwise = cipher; bss->rsn_pairwise = cipher; + } else if (bss->osen) { + bss->ssid.security_policy = SECURITY_OSEN; + bss->wpa_group = WPA_CIPHER_CCMP; + bss->wpa_pairwise = 0; + bss->rsn_pairwise = WPA_CIPHER_CCMP; } else { bss->ssid.security_policy = SECURITY_PLAINTEXT; bss->wpa_group = WPA_CIPHER_NONE; diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index 4631ca9e..e1e34e2d 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -45,7 +45,8 @@ typedef enum hostap_security_policy { SECURITY_STATIC_WEP = 1, SECURITY_IEEE_802_1X = 2, SECURITY_WPA_PSK = 3, - SECURITY_WPA = 4 + SECURITY_WPA = 4, + SECURITY_OSEN = 5 } secpolicy; struct hostapd_ssid { @@ -125,6 +126,7 @@ struct hostapd_eap_user { unsigned int wildcard_prefix:1; unsigned int password_hash:1; /* whether password is hashed with * nt_password_hash() */ + unsigned int remediation:1; int ttls_auth; /* EAP_TTLS_AUTH_* bitfield */ }; @@ -452,9 +454,11 @@ struct hostapd_bss_config { u8 qos_map_set[16 + 2 * 21]; unsigned int qos_map_set_len; + int osen; #ifdef CONFIG_HS20 int hs20; int disable_dgaf; + u16 anqp_domain_id; unsigned int hs20_oper_friendly_name_count; struct hostapd_lang_string *hs20_oper_friendly_name; u8 *hs20_wan_metrics; @@ -462,6 +466,32 @@ struct hostapd_bss_config { size_t hs20_connection_capability_len; u8 *hs20_operating_class; u8 hs20_operating_class_len; + struct hs20_icon { + u16 width; + u16 height; + char language[3]; + char type[256]; + char name[256]; + char file[256]; + } *hs20_icons; + size_t hs20_icons_count; + u8 osu_ssid[HOSTAPD_MAX_SSID_LEN]; + size_t osu_ssid_len; + struct hs20_osu_provider { + unsigned int friendly_name_count; + struct hostapd_lang_string *friendly_name; + char *server_uri; + int *method_list; + char **icons; + size_t icons_count; + char *osu_nai; + unsigned int service_desc_count; + struct hostapd_lang_string *service_desc; + } *hs20_osu_providers, *last_osu; + size_t hs20_osu_providers_count; + unsigned int hs20_deauth_req_timeout; + char *subscr_remediation_url; + u8 subscr_remediation_method; #endif /* CONFIG_HS20 */ u8 wps_rf_bands; /* RF bands for WPS (WPS_RF_*) */ @@ -519,6 +549,16 @@ struct hostapd_config { int ieee80211h; /* DFS */ + /* + * Local power constraint is an octet encoded as an unsigned integer in + * units of decibels. Invalid value -1 indicates that Power Constraint + * element will not be added. + */ + int local_pwr_constraint; + + /* Control Spectrum Management bit */ + int spectrum_mgmt_required; + struct hostapd_tx_queue_params tx_queue[NUM_TX_QUEUES]; /* diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c index e998fc62..b8b260aa 100644 --- a/src/ap/ap_drv_ops.c +++ b/src/ap/ap_drv_ops.c @@ -170,6 +170,17 @@ int hostapd_build_ap_extra_ies(struct hostapd_data *hapd, goto fail; wpabuf_put_data(proberesp, buf, pos - buf); } + + pos = hostapd_eid_osen(hapd, buf); + if (pos != buf) { + if (wpabuf_resize(&beacon, pos - buf) != 0) + goto fail; + wpabuf_put_data(beacon, buf, pos - buf); + + if (wpabuf_resize(&proberesp, pos - buf) != 0) + goto fail; + wpabuf_put_data(proberesp, buf, pos - buf); + } #endif /* CONFIG_HS20 */ if (hapd->conf->vendor_elements) { diff --git a/src/ap/authsrv.c b/src/ap/authsrv.c index 7183abae..7691012f 100644 --- a/src/ap/authsrv.c +++ b/src/ap/authsrv.c @@ -80,6 +80,7 @@ static int hostapd_radius_get_eap_user(void *ctx, const u8 *identity, } user->force_version = eap_user->force_version; user->ttls_auth = eap_user->ttls_auth; + user->remediation = eap_user->remediation; return 0; } @@ -116,6 +117,10 @@ static int hostapd_setup_radius_srv(struct hostapd_data *hapd) #ifdef CONFIG_RADIUS_TEST srv.dump_msk_file = conf->dump_msk_file; #endif /* CONFIG_RADIUS_TEST */ +#ifdef CONFIG_HS20 + srv.subscr_remediation_url = conf->subscr_remediation_url; + srv.subscr_remediation_method = conf->subscr_remediation_method; +#endif /* CONFIG_HS20 */ hapd->radius_srv = radius_server_init(&srv); if (hapd->radius_srv == NULL) { diff --git a/src/ap/beacon.c b/src/ap/beacon.c index b1ebf6e3..e06ce778 100644 --- a/src/ap/beacon.c +++ b/src/ap/beacon.c @@ -27,6 +27,7 @@ #include "ap_drv_ops.h" #include "beacon.h" #include "hs20.h" +#include "dfs.h" #ifdef NEED_AP_MLME @@ -102,6 +103,70 @@ static u8 * hostapd_eid_erp_info(struct hostapd_data *hapd, u8 *eid) } +static u8 * hostapd_eid_pwr_constraint(struct hostapd_data *hapd, u8 *eid) +{ + u8 *pos = eid; + u8 local_pwr_constraint = 0; + int dfs; + + if (hapd->iface->current_mode == NULL || + hapd->iface->current_mode->mode != HOSTAPD_MODE_IEEE80211A) + return eid; + + /* + * There is no DFS support and power constraint was not directly + * requested by config option. + */ + if (!hapd->iconf->ieee80211h && + hapd->iconf->local_pwr_constraint == -1) + return eid; + + /* Check if DFS is required by regulatory. */ + dfs = hostapd_is_dfs_required(hapd->iface); + if (dfs < 0) { + wpa_printf(MSG_WARNING, "Failed to check if DFS is required; ret=%d", + dfs); + dfs = 0; + } + + if (dfs == 0 && hapd->iconf->local_pwr_constraint == -1) + return eid; + + /* + * ieee80211h (DFS) is enabled so Power Constraint element shall + * be added when running on DFS channel whenever local_pwr_constraint + * is configured or not. In order to meet regulations when TPC is not + * implemented using a transmit power that is below the legal maximum + * (including any mitigation factor) should help. In this case, + * indicate 3 dB below maximum allowed transmit power. + */ + if (hapd->iconf->local_pwr_constraint == -1) + local_pwr_constraint = 3; + + /* + * A STA that is not an AP shall use a transmit power less than or + * equal to the local maximum transmit power level for the channel. + * The local maximum transmit power can be calculated from the formula: + * local max TX pwr = max TX pwr - local pwr constraint + * Where max TX pwr is maximum transmit power level specified for + * channel in Country element and local pwr constraint is specified + * for channel in this Power Constraint element. + */ + + /* Element ID */ + *pos++ = WLAN_EID_PWR_CONSTRAINT; + /* Length */ + *pos++ = 1; + /* Local Power Constraint */ + if (local_pwr_constraint) + *pos++ = local_pwr_constraint; + else + *pos++ = hapd->iconf->local_pwr_constraint; + + return pos; +} + + static u8 * hostapd_eid_country_add(u8 *pos, u8 *end, int chan_spacing, struct hostapd_channel_data *start, struct hostapd_channel_data *prev) @@ -315,6 +380,9 @@ static u8 * hostapd_gen_probe_resp(struct hostapd_data *hapd, pos = hostapd_eid_country(hapd, pos, epos - pos); + /* Power Constraint element */ + pos = hostapd_eid_pwr_constraint(hapd, pos); + /* ERP Information element */ pos = hostapd_eid_erp_info(hapd, pos); @@ -374,6 +442,7 @@ static u8 * hostapd_gen_probe_resp(struct hostapd_data *hapd, #ifdef CONFIG_HS20 pos = hostapd_eid_hs20_indication(hapd, pos); + pos = hostapd_eid_osen(hapd, pos); #endif /* CONFIG_HS20 */ if (hapd->conf->vendor_elements) { @@ -721,6 +790,9 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd, tailpos = hostapd_eid_country(hapd, tailpos, tail + BEACON_TAIL_BUF_SIZE - tailpos); + /* Power Constraint element */ + tailpos = hostapd_eid_pwr_constraint(hapd, tailpos); + /* ERP Information element */ tailpos = hostapd_eid_erp_info(hapd, tailpos); @@ -783,6 +855,7 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd, #ifdef CONFIG_HS20 tailpos = hostapd_eid_hs20_indication(hapd, tailpos); + tailpos = hostapd_eid_osen(hapd, tailpos); #endif /* CONFIG_HS20 */ if (hapd->conf->vendor_elements) { @@ -854,6 +927,10 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd, params->ap_max_inactivity = hapd->conf->ap_max_inactivity; #ifdef CONFIG_HS20 params->disable_dgaf = hapd->conf->disable_dgaf; + if (hapd->conf->osen) { + params->privacy = 1; + params->osen = 1; + } #endif /* CONFIG_HS20 */ return 0; } diff --git a/src/ap/dfs.c b/src/ap/dfs.c index ec691db3..612f5348 100644 --- a/src/ap/dfs.c +++ b/src/ap/dfs.c @@ -78,6 +78,11 @@ static int dfs_is_chan_allowed(struct hostapd_channel_data *chan, int n_chans) * 42, 58, 106, 122, 138, 155 */ int allowed_80[] = { 36, 52, 100, 116, 132, 149 }; + /* + * VHT160 valid channels based on center frequency: + * 50, 114 + */ + int allowed_160[] = { 36, 100 }; int *allowed = allowed_40; unsigned int i, allowed_no = 0; @@ -90,6 +95,10 @@ static int dfs_is_chan_allowed(struct hostapd_channel_data *chan, int n_chans) allowed = allowed_80; allowed_no = ARRAY_SIZE(allowed_80); break; + case 8: + allowed = allowed_160; + allowed_no = ARRAY_SIZE(allowed_160); + break; default: wpa_printf(MSG_DEBUG, "Unknown width for %d channels", n_chans); break; @@ -294,8 +303,15 @@ static int dfs_check_chans_available(struct hostapd_iface *iface, mode = iface->current_mode; - for(i = 0; i < n_chans; i++) { + for (i = 0; i < n_chans; i++) { channel = &mode->channels[start_chan_idx + i]; + + if (channel->flag & HOSTAPD_CHAN_DISABLED) + break; + + if (!(channel->flag & HOSTAPD_CHAN_RADAR)) + continue; + if ((channel->flag & HOSTAPD_CHAN_DFS_MASK) != HOSTAPD_CHAN_DFS_AVAILABLE) break; @@ -316,7 +332,7 @@ static int dfs_check_chans_unavailable(struct hostapd_iface *iface, mode = iface->current_mode; - for(i = 0; i < n_chans; i++) { + for (i = 0; i < n_chans; i++) { channel = &mode->channels[start_chan_idx + i]; if (channel->flag & HOSTAPD_CHAN_DISABLED) res++; @@ -801,3 +817,23 @@ int hostapd_dfs_nop_finished(struct hostapd_iface *iface, int freq, cf1, cf2, HOSTAPD_CHAN_DFS_USABLE); return 0; } + + +int hostapd_is_dfs_required(struct hostapd_iface *iface) +{ + int n_chans, start_chan_idx; + + if (!iface->current_mode) + return -1; + + /* Get start (first) channel for current configuration */ + start_chan_idx = dfs_get_start_chan_idx(iface); + if (start_chan_idx == -1) + return -1; + + /* Get number of used channels, depend on width */ + n_chans = dfs_get_used_n_chans(iface); + + /* Check if any of configured channels require DFS */ + return dfs_check_chans_radar(iface, start_chan_idx, n_chans); +} diff --git a/src/ap/dfs.h b/src/ap/dfs.h index 859ff791..a619c55c 100644 --- a/src/ap/dfs.h +++ b/src/ap/dfs.h @@ -21,5 +21,6 @@ int hostapd_dfs_radar_detected(struct hostapd_iface *iface, int freq, int hostapd_dfs_nop_finished(struct hostapd_iface *iface, int freq, int ht_enabled, int chan_offset, int chan_width, int cf1, int cf2); +int hostapd_is_dfs_required(struct hostapd_iface *iface); #endif /* DFS_H */ diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c index 9af96468..6fb10566 100644 --- a/src/ap/drv_callbacks.c +++ b/src/ap/drv_callbacks.c @@ -78,6 +78,12 @@ int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr, ie = elems.wpa_ie - 2; ielen = elems.wpa_ie_len + 2; wpa_printf(MSG_DEBUG, "STA included WPA IE in (Re)AssocReq"); +#ifdef CONFIG_HS20 + } else if (elems.osen) { + ie = elems.osen - 2; + ielen = elems.osen_len + 2; + wpa_printf(MSG_DEBUG, "STA included OSEN IE in (Re)AssocReq"); +#endif /* CONFIG_HS20 */ } else { ie = NULL; ielen = 0; @@ -281,6 +287,29 @@ int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr, sta->flags |= WLAN_STA_MAYBE_WPS; wpabuf_free(wps); #endif /* CONFIG_WPS */ +#ifdef CONFIG_HS20 + } else if (hapd->conf->osen) { + if (elems.osen == NULL) { + hostapd_logger( + hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "No HS 2.0 OSEN element in association request"); + return WLAN_STATUS_INVALID_IE; + } + + wpa_printf(MSG_DEBUG, "HS 2.0: OSEN association"); + if (sta->wpa_sm == NULL) + sta->wpa_sm = wpa_auth_sta_init(hapd->wpa_auth, + sta->addr, NULL); + if (sta->wpa_sm == NULL) { + wpa_printf(MSG_WARNING, "Failed to initialize WPA " + "state machine"); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + if (wpa_validate_osen(hapd->wpa_auth, sta->wpa_sm, + elems.osen - 2, elems.osen_len + 2) < 0) + return WLAN_STATUS_INVALID_IE; +#endif /* CONFIG_HS20 */ } #ifdef CONFIG_WPS skip_wpa_check: diff --git a/src/ap/eap_user_db.c b/src/ap/eap_user_db.c index 79d50e51..371a73f2 100644 --- a/src/ap/eap_user_db.c +++ b/src/ap/eap_user_db.c @@ -89,6 +89,8 @@ static int get_user_cb(void *ctx, int argc, char *argv[], char *col[]) user->next = (void *) 1; } else if (os_strcmp(col[i], "methods") == 0 && argv[i]) { set_user_methods(user, argv[i]); + } else if (os_strcmp(col[i], "remediation") == 0 && argv[i]) { + user->remediation = strlen(argv[i]) > 0; } } @@ -173,8 +175,8 @@ eap_user_sqlite_get(struct hostapd_data *hapd, const u8 *identity, } os_snprintf(cmd, sizeof(cmd), - "SELECT password,methods FROM users WHERE " - "identity='%s' AND phase2=%d;", id_str, phase2); + "SELECT * FROM users WHERE identity='%s' AND phase2=%d;", + id_str, phase2); wpa_printf(MSG_DEBUG, "DB: %s", cmd); if (sqlite3_exec(db, cmd, get_user_cb, &hapd->tmp_eap_user, NULL) != SQLITE_OK) { diff --git a/src/ap/gas_serv.c b/src/ap/gas_serv.c index b5fb7dfb..fd1041ea 100644 --- a/src/ap/gas_serv.c +++ b/src/ap/gas_serv.c @@ -159,6 +159,10 @@ static void anqp_add_hs_capab_list(struct hostapd_data *hapd, wpabuf_put_u8(buf, HS20_STYPE_NAI_HOME_REALM_QUERY); if (hapd->conf->hs20_operating_class) wpabuf_put_u8(buf, HS20_STYPE_OPERATING_CLASS); + if (hapd->conf->hs20_osu_providers_count) + wpabuf_put_u8(buf, HS20_STYPE_OSU_PROVIDERS_LIST); + if (hapd->conf->hs20_icons_count) + wpabuf_put_u8(buf, HS20_STYPE_ICON_REQUEST); gas_anqp_set_element_len(buf, len); } #endif /* CONFIG_HS20 */ @@ -514,6 +518,169 @@ static void anqp_add_operating_class(struct hostapd_data *hapd, } } + +static void anqp_add_osu_provider(struct wpabuf *buf, + struct hostapd_bss_config *bss, + struct hs20_osu_provider *p) +{ + u8 *len, *len2, *count; + unsigned int i; + + len = wpabuf_put(buf, 2); /* OSU Provider Length to be filled */ + + /* OSU Friendly Name Duples */ + len2 = wpabuf_put(buf, 2); + for (i = 0; i < p->friendly_name_count; i++) { + struct hostapd_lang_string *s = &p->friendly_name[i]; + wpabuf_put_u8(buf, 3 + s->name_len); + wpabuf_put_data(buf, s->lang, 3); + wpabuf_put_data(buf, s->name, s->name_len); + } + WPA_PUT_LE16(len2, (u8 *) wpabuf_put(buf, 0) - len2 - 2); + + /* OSU Server URI */ + if (p->server_uri) { + wpabuf_put_u8(buf, os_strlen(p->server_uri)); + wpabuf_put_str(buf, p->server_uri); + } else + wpabuf_put_u8(buf, 0); + + /* OSU Method List */ + count = wpabuf_put(buf, 1); + for (i = 0; p->method_list[i] >= 0; i++) + wpabuf_put_u8(buf, p->method_list[i]); + *count = i; + + /* Icons Available */ + len2 = wpabuf_put(buf, 2); + for (i = 0; i < p->icons_count; i++) { + size_t j; + struct hs20_icon *icon = NULL; + + for (j = 0; j < bss->hs20_icons_count && !icon; j++) { + if (os_strcmp(p->icons[i], bss->hs20_icons[j].name) == + 0) + icon = &bss->hs20_icons[j]; + } + if (!icon) + continue; /* icon info not found */ + + wpabuf_put_le16(buf, icon->width); + wpabuf_put_le16(buf, icon->height); + wpabuf_put_data(buf, icon->language, 3); + wpabuf_put_u8(buf, os_strlen(icon->type)); + wpabuf_put_str(buf, icon->type); + wpabuf_put_u8(buf, os_strlen(icon->name)); + wpabuf_put_str(buf, icon->name); + } + WPA_PUT_LE16(len2, (u8 *) wpabuf_put(buf, 0) - len2 - 2); + + /* OSU_NAI */ + if (p->osu_nai) { + wpabuf_put_u8(buf, os_strlen(p->osu_nai)); + wpabuf_put_str(buf, p->osu_nai); + } else + wpabuf_put_u8(buf, 0); + + /* OSU Service Description Duples */ + len2 = wpabuf_put(buf, 2); + for (i = 0; i < p->service_desc_count; i++) { + struct hostapd_lang_string *s = &p->service_desc[i]; + wpabuf_put_u8(buf, 3 + s->name_len); + wpabuf_put_data(buf, s->lang, 3); + wpabuf_put_data(buf, s->name, s->name_len); + } + WPA_PUT_LE16(len2, (u8 *) wpabuf_put(buf, 0) - len2 - 2); + + WPA_PUT_LE16(len, (u8 *) wpabuf_put(buf, 0) - len - 2); +} + + +static void anqp_add_osu_providers_list(struct hostapd_data *hapd, + struct wpabuf *buf) +{ + if (hapd->conf->hs20_osu_providers_count) { + size_t i; + u8 *len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC); + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE); + wpabuf_put_u8(buf, HS20_STYPE_OSU_PROVIDERS_LIST); + wpabuf_put_u8(buf, 0); /* Reserved */ + + /* OSU SSID */ + wpabuf_put_u8(buf, hapd->conf->osu_ssid_len); + wpabuf_put_data(buf, hapd->conf->osu_ssid, + hapd->conf->osu_ssid_len); + + /* Number of OSU Providers */ + wpabuf_put_u8(buf, hapd->conf->hs20_osu_providers_count); + + for (i = 0; i < hapd->conf->hs20_osu_providers_count; i++) { + anqp_add_osu_provider( + buf, hapd->conf, + &hapd->conf->hs20_osu_providers[i]); + } + + gas_anqp_set_element_len(buf, len); + } +} + + +static void anqp_add_icon_binary_file(struct hostapd_data *hapd, + struct wpabuf *buf, + const u8 *name, size_t name_len) +{ + struct hs20_icon *icon; + size_t i; + u8 *len; + + wpa_hexdump_ascii(MSG_DEBUG, "HS 2.0: Requested Icon Filename", + name, name_len); + for (i = 0; i < hapd->conf->hs20_icons_count; i++) { + icon = &hapd->conf->hs20_icons[i]; + if (name_len == os_strlen(icon->name) && + os_memcmp(name, icon->name, name_len) == 0) + break; + } + + if (i < hapd->conf->hs20_icons_count) + icon = &hapd->conf->hs20_icons[i]; + else + icon = NULL; + + len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC); + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE); + wpabuf_put_u8(buf, HS20_STYPE_ICON_BINARY_FILE); + wpabuf_put_u8(buf, 0); /* Reserved */ + + if (icon) { + char *data; + size_t data_len; + + data = os_readfile(icon->file, &data_len); + if (data == NULL || data_len > 65535) { + wpabuf_put_u8(buf, 2); /* Download Status: + * Unspecified file error */ + wpabuf_put_u8(buf, 0); + wpabuf_put_le16(buf, 0); + } else { + wpabuf_put_u8(buf, 0); /* Download Status: Success */ + wpabuf_put_u8(buf, os_strlen(icon->type)); + wpabuf_put_str(buf, icon->type); + wpabuf_put_le16(buf, data_len); + wpabuf_put_data(buf, data, data_len); + } + os_free(data); + } else { + wpabuf_put_u8(buf, 1); /* Download Status: File not found */ + wpabuf_put_u8(buf, 0); + wpabuf_put_le16(buf, 0); + } + + gas_anqp_set_element_len(buf, len); +} + #endif /* CONFIG_HS20 */ @@ -521,11 +688,19 @@ static struct wpabuf * gas_serv_build_gas_resp_payload(struct hostapd_data *hapd, unsigned int request, struct gas_dialog_info *di, - const u8 *home_realm, size_t home_realm_len) + const u8 *home_realm, size_t home_realm_len, + const u8 *icon_name, size_t icon_name_len) { struct wpabuf *buf; + size_t len; + + len = 1400; + if (request & (ANQP_REQ_NAI_REALM | ANQP_REQ_NAI_HOME_REALM)) + len += 1000; + if (request & ANQP_REQ_ICON_REQUEST) + len += 65536; - buf = wpabuf_alloc(1400); + buf = wpabuf_alloc(len); if (buf == NULL) return NULL; @@ -559,6 +734,10 @@ gas_serv_build_gas_resp_payload(struct hostapd_data *hapd, anqp_add_connection_capability(hapd, buf); if (request & ANQP_REQ_OPERATING_CLASS) anqp_add_operating_class(hapd, buf); + if (request & ANQP_REQ_OSU_PROVIDERS_LIST) + anqp_add_osu_providers_list(hapd, buf); + if (request & ANQP_REQ_ICON_REQUEST) + anqp_add_icon_binary_file(hapd, buf, icon_name, icon_name_len); #endif /* CONFIG_HS20 */ return buf; @@ -581,6 +760,8 @@ struct anqp_query_info { unsigned int remote_request; const u8 *home_realm_query; size_t home_realm_query_len; + const u8 *icon_name; + size_t icon_name_len; u16 remote_delay; }; @@ -700,6 +881,10 @@ static void rx_anqp_hs_query_list(struct hostapd_data *hapd, u8 subtype, hapd->conf->hs20_operating_class != NULL, 0, 0, qi); break; + case HS20_STYPE_OSU_PROVIDERS_LIST: + set_anqp_req(ANQP_REQ_OSU_PROVIDERS_LIST, "OSU Providers list", + hapd->conf->hs20_osu_providers_count, 0, 0, qi); + break; default: wpa_printf(MSG_DEBUG, "ANQP: Unsupported HS 2.0 subtype %u", subtype); @@ -725,6 +910,23 @@ static void rx_anqp_hs_nai_home_realm(struct hostapd_data *hapd, } +static void rx_anqp_hs_icon_request(struct hostapd_data *hapd, + const u8 *pos, const u8 *end, + struct anqp_query_info *qi) +{ + qi->request |= ANQP_REQ_ICON_REQUEST; + qi->icon_name = pos; + qi->icon_name_len = end - pos; + if (hapd->conf->hs20_icons_count) { + wpa_printf(MSG_DEBUG, "ANQP: HS 2.0 Icon Request Query " + "(local)"); + } else { + wpa_printf(MSG_DEBUG, "ANQP: HS 2.0 Icon Request Query not " + "available"); + } +} + + static void rx_anqp_vendor_specific(struct hostapd_data *hapd, const u8 *pos, const u8 *end, struct anqp_query_info *qi) @@ -769,6 +971,9 @@ static void rx_anqp_vendor_specific(struct hostapd_data *hapd, case HS20_STYPE_NAI_HOME_REALM_QUERY: rx_anqp_hs_nai_home_realm(hapd, pos, end, qi); break; + case HS20_STYPE_ICON_REQUEST: + rx_anqp_hs_icon_request(hapd, pos, end, qi); + break; default: wpa_printf(MSG_DEBUG, "ANQP: Unsupported HS 2.0 query subtype " "%u", subtype); @@ -787,7 +992,8 @@ static void gas_serv_req_local_processing(struct hostapd_data *hapd, buf = gas_serv_build_gas_resp_payload(hapd, qi->request, NULL, qi->home_realm_query, - qi->home_realm_query_len); + qi->home_realm_query_len, + qi->icon_name, qi->icon_name_len); wpa_hexdump_buf(MSG_MSGDUMP, "ANQP: Locally generated ANQP responses", buf); if (!buf) @@ -954,7 +1160,7 @@ void gas_serv_tx_gas_response(struct hostapd_data *hapd, const u8 *dst, if (dialog->sd_resp == NULL) { buf = gas_serv_build_gas_resp_payload(hapd, dialog->all_requested, - dialog, NULL, 0); + dialog, NULL, 0, NULL, 0); wpa_hexdump_buf(MSG_MSGDUMP, "ANQP: Generated ANQP responses", buf); if (!buf) @@ -1087,7 +1293,7 @@ static void gas_serv_rx_gas_comeback_req(struct hostapd_data *hapd, buf = gas_serv_build_gas_resp_payload(hapd, dialog->all_requested, - dialog, NULL, 0); + dialog, NULL, 0, NULL, 0); wpa_hexdump_buf(MSG_MSGDUMP, "ANQP: Generated ANQP responses", buf); if (!buf) diff --git a/src/ap/gas_serv.h b/src/ap/gas_serv.h index 74739fef..7e392b36 100644 --- a/src/ap/gas_serv.h +++ b/src/ap/gas_serv.h @@ -1,6 +1,6 @@ /* * Generic advertisement service (GAS) server - * Copyright (c) 2011-2012, Qualcomm Atheros, Inc. + * Copyright (c) 2011-2013, Qualcomm Atheros, Inc. * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -37,6 +37,10 @@ (0x10000 << HS20_STYPE_NAI_HOME_REALM_QUERY) #define ANQP_REQ_OPERATING_CLASS \ (0x10000 << HS20_STYPE_OPERATING_CLASS) +#define ANQP_REQ_OSU_PROVIDERS_LIST \ + (0x10000 << HS20_STYPE_OSU_PROVIDERS_LIST) +#define ANQP_REQ_ICON_REQUEST \ + (0x10000 << HS20_STYPE_ICON_REQUEST) /* To account for latencies between hostapd and external ANQP processor */ #define GAS_SERV_COMEBACK_DELAY_FUDGE 10 diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c index 98148da4..75baec0e 100644 --- a/src/ap/hostapd.c +++ b/src/ap/hostapd.c @@ -87,7 +87,7 @@ static void hostapd_reload_bss(struct hostapd_data *hapd) else hostapd_set_drv_ieee8021x(hapd, hapd->conf->iface, 0); - if (hapd->conf->wpa && hapd->wpa_auth == NULL) { + if ((hapd->conf->wpa || hapd->conf->osen) && hapd->wpa_auth == NULL) { hostapd_setup_wpa(hapd); if (hapd->wpa_auth) wpa_init_keys(hapd->wpa_auth); @@ -802,7 +802,7 @@ static int hostapd_setup_bss(struct hostapd_data *hapd, int first) return -1; } - if (hapd->conf->wpa && hostapd_setup_wpa(hapd)) + if ((hapd->conf->wpa || hapd->conf->osen) && hostapd_setup_wpa(hapd)) return -1; if (accounting_init(hapd)) { diff --git a/src/ap/hs20.c b/src/ap/hs20.c index 45d518bc..d7909fad 100644 --- a/src/ap/hs20.c +++ b/src/ap/hs20.c @@ -1,7 +1,7 @@ /* * Hotspot 2.0 AP ANQP processing * Copyright (c) 2009, Atheros Communications, Inc. - * Copyright (c) 2011-2012, Qualcomm Atheros, Inc. + * Copyright (c) 2011-2013, Qualcomm Atheros, Inc. * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -13,19 +13,165 @@ #include "common/ieee802_11_defs.h" #include "hostapd.h" #include "ap_config.h" +#include "ap_drv_ops.h" #include "hs20.h" u8 * hostapd_eid_hs20_indication(struct hostapd_data *hapd, u8 *eid) { + u8 conf; if (!hapd->conf->hs20) return eid; *eid++ = WLAN_EID_VENDOR_SPECIFIC; - *eid++ = 5; + *eid++ = 7; WPA_PUT_BE24(eid, OUI_WFA); eid += 3; *eid++ = HS20_INDICATION_OUI_TYPE; - /* Hotspot Configuration: DGAF Enabled */ - *eid++ = hapd->conf->disable_dgaf ? 0x01 : 0x00; + conf = HS20_VERSION; /* Release Number */ + conf |= HS20_ANQP_DOMAIN_ID_PRESENT; + if (hapd->conf->disable_dgaf) + conf |= HS20_DGAF_DISABLED; + *eid++ = conf; + WPA_PUT_LE16(eid, hapd->conf->anqp_domain_id); + eid += 2; + return eid; } + + +u8 * hostapd_eid_osen(struct hostapd_data *hapd, u8 *eid) +{ + u8 *len; + u16 capab; + + if (!hapd->conf->osen) + return eid; + + *eid++ = WLAN_EID_VENDOR_SPECIFIC; + len = eid++; /* to be filled */ + WPA_PUT_BE24(eid, OUI_WFA); + eid += 3; + *eid++ = HS20_OSEN_OUI_TYPE; + + /* Group Data Cipher Suite */ + RSN_SELECTOR_PUT(eid, RSN_CIPHER_SUITE_NO_GROUP_ADDRESSED); + eid += RSN_SELECTOR_LEN; + + /* Pairwise Cipher Suite Count and List */ + WPA_PUT_LE16(eid, 1); + eid += 2; + RSN_SELECTOR_PUT(eid, RSN_CIPHER_SUITE_CCMP); + eid += RSN_SELECTOR_LEN; + + /* AKM Suite Count and List */ + WPA_PUT_LE16(eid, 1); + eid += 2; + RSN_SELECTOR_PUT(eid, RSN_AUTH_KEY_MGMT_OSEN); + eid += RSN_SELECTOR_LEN; + + /* RSN Capabilities */ + capab = 0; + if (hapd->conf->wmm_enabled) { + /* 4 PTKSA replay counters when using WMM */ + capab |= (RSN_NUM_REPLAY_COUNTERS_16 << 2); + } +#ifdef CONFIG_IEEE80211W + if (hapd->conf->ieee80211w != NO_MGMT_FRAME_PROTECTION) { + capab |= WPA_CAPABILITY_MFPC; + if (hapd->conf->ieee80211w == MGMT_FRAME_PROTECTION_REQUIRED) + capab |= WPA_CAPABILITY_MFPR; + } +#endif /* CONFIG_IEEE80211W */ + WPA_PUT_LE16(eid, capab); + eid += 2; + + *len = eid - len - 1; + + return eid; +} + + +int hs20_send_wnm_notification(struct hostapd_data *hapd, const u8 *addr, + u8 osu_method, const char *url) +{ + struct wpabuf *buf; + size_t len = 0; + 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 */ + + if (url) { + len = 1 + os_strlen(url); + if (5 + len > 255) { + wpa_printf(MSG_INFO, "HS 2.0: Too long URL for " + "WNM-Notification: '%s'", url); + return -1; + } + } + + buf = wpabuf_alloc(4 + 7 + len); + 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 */ + + /* Subscription Remediation subelement */ + wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC); + wpabuf_put_u8(buf, 5 + len); + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, HS20_WNM_SUB_REM_NEEDED); + if (url) { + wpabuf_put_u8(buf, len - 1); + wpabuf_put_data(buf, url, len - 1); + wpabuf_put_u8(buf, osu_method); + } else { + /* Server URL and Server Method fields not included */ + wpabuf_put_u8(buf, 0); + } + + ret = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, addr, + wpabuf_head(buf), wpabuf_len(buf)); + + wpabuf_free(buf); + + 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 98698ce2..152439f4 100644 --- a/src/ap/hs20.h +++ b/src/ap/hs20.h @@ -1,6 +1,6 @@ /* * Hotspot 2.0 AP ANQP processing - * Copyright (c) 2011-2012, Qualcomm Atheros, Inc. + * Copyright (c) 2011-2013, Qualcomm Atheros, Inc. * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -12,5 +12,11 @@ struct hostapd_data; 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/hw_features.c b/src/ap/hw_features.c index 4e663795..7d36790c 100644 --- a/src/ap/hw_features.c +++ b/src/ap/hw_features.c @@ -943,6 +943,15 @@ int hostapd_select_hw_mode(struct hostapd_iface *iface) if (iface->num_hw_features < 1) return -1; + if ((iface->conf->hw_mode == HOSTAPD_MODE_IEEE80211G || + iface->conf->ieee80211n || iface->conf->ieee80211ac) && + iface->conf->channel == 14) { + wpa_printf(MSG_INFO, "Disable OFDM/HT/VHT on channel 14"); + iface->conf->hw_mode = HOSTAPD_MODE_IEEE80211B; + iface->conf->ieee80211n = 0; + iface->conf->ieee80211ac = 0; + } + iface->current_mode = NULL; for (i = 0; i < iface->num_hw_features; i++) { struct hostapd_hw_modes *mode = &iface->hw_features[i]; diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c index 9251ac3a..3e704e56 100644 --- a/src/ap/ieee802_11.c +++ b/src/ap/ieee802_11.c @@ -38,6 +38,7 @@ #include "ap_drv_ops.h" #include "wnm_ap.h" #include "ieee802_11.h" +#include "dfs.h" u8 * hostapd_eid_supp_rates(struct hostapd_data *hapd, u8 *eid) @@ -137,6 +138,15 @@ u16 hostapd_own_capab_info(struct hostapd_data *hapd, struct sta_info *sta, { int capab = WLAN_CAPABILITY_ESS; int privacy; + int dfs; + + /* Check if any of configured channels require DFS */ + dfs = hostapd_is_dfs_required(hapd->iface); + if (dfs < 0) { + wpa_printf(MSG_WARNING, "Failed to check if DFS is required; ret=%d", + dfs); + dfs = 0; + } if (hapd->iface->num_sta_no_short_preamble == 0 && hapd->iconf->preamble == SHORT_PREAMBLE) @@ -152,6 +162,11 @@ u16 hostapd_own_capab_info(struct hostapd_data *hapd, struct sta_info *sta, if (hapd->conf->wpa) privacy = 1; +#ifdef CONFIG_HS20 + if (hapd->conf->osen) + privacy = 1; +#endif /* CONFIG_HS20 */ + if (sta) { int policy, def_klen; if (probe && sta->ssid_probe) { @@ -174,6 +189,17 @@ u16 hostapd_own_capab_info(struct hostapd_data *hapd, struct sta_info *sta, hapd->iface->num_sta_no_short_slot_time == 0) capab |= WLAN_CAPABILITY_SHORT_SLOT_TIME; + /* + * Currently, Spectrum Management capability bit is set when directly + * requested in configuration by spectrum_mgmt_required or when AP is + * running on DFS channel. + * TODO: Also consider driver support for TPC to set Spectrum Mgmt bit + */ + if (hapd->iface->current_mode && + hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211A && + (hapd->iconf->spectrum_mgmt_required || dfs)) + capab |= WLAN_CAPABILITY_SPECTRUM_MGMT; + return capab; } @@ -1068,6 +1094,29 @@ static u16 check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta, return WLAN_STATUS_CIPHER_REJECTED_PER_POLICY; } #endif /* CONFIG_IEEE80211N */ +#ifdef CONFIG_HS20 + } else if (hapd->conf->osen) { + if (elems.osen == NULL) { + hostapd_logger( + hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "No HS 2.0 OSEN element in association request"); + return WLAN_STATUS_INVALID_IE; + } + + wpa_printf(MSG_DEBUG, "HS 2.0: OSEN association"); + if (sta->wpa_sm == NULL) + sta->wpa_sm = wpa_auth_sta_init(hapd->wpa_auth, + sta->addr, NULL); + if (sta->wpa_sm == NULL) { + wpa_printf(MSG_WARNING, "Failed to initialize WPA " + "state machine"); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + if (wpa_validate_osen(hapd->wpa_auth, sta->wpa_sm, + elems.osen - 2, elems.osen_len + 2) < 0) + return WLAN_STATUS_INVALID_IE; +#endif /* CONFIG_HS20 */ } else wpa_auth_sta_no_wpa(sta->wpa_sm); @@ -1903,7 +1952,7 @@ static void handle_assoc_cb(struct hostapd_data *hapd, new_assoc = 0; sta->flags |= WLAN_STA_ASSOC; sta->flags &= ~WLAN_STA_WNM_SLEEP_MODE; - if ((!hapd->conf->ieee802_1x && !hapd->conf->wpa) || + if ((!hapd->conf->ieee802_1x && !hapd->conf->wpa && !hapd->conf->osen) || sta->auth_alg == WLAN_AUTH_FT) { /* * Open, static WEP, or FT protocol; no separate authorization diff --git a/src/ap/ieee802_11_shared.c b/src/ap/ieee802_11_shared.c index eadaa4d1..b78fd010 100644 --- a/src/ap/ieee802_11_shared.c +++ b/src/ap/ieee802_11_shared.c @@ -199,6 +199,10 @@ static void hostapd_ext_capab_byte(struct hostapd_data *hapd, u8 *pos, int idx) } break; case 5: /* Bits 40-47 */ +#ifdef CONFIG_HS20 + if (hapd->conf->hs20) + *pos |= 0x40; /* Bit 46 - WNM-Notification */ +#endif /* CONFIG_HS20 */ break; case 6: /* Bits 48-55 */ if (hapd->conf->ssid.utf8_ssid) @@ -225,6 +229,10 @@ u8 * hostapd_eid_ext_capab(struct hostapd_data *hapd, u8 *eid) if (len < 4) len = 4; #endif /* CONFIG_WNM */ +#ifdef CONFIG_HS20 + if (hapd->conf->hs20 && len < 6) + len = 6; +#endif /* CONFIG_HS20 */ if (len < hapd->iface->extended_capa_len) len = hapd->iface->extended_capa_len; if (len == 0) diff --git a/src/ap/ieee802_1x.c b/src/ap/ieee802_1x.c index 21f815af..b12c9d6c 100644 --- a/src/ap/ieee802_1x.c +++ b/src/ap/ieee802_1x.c @@ -30,11 +30,13 @@ #include "ap_config.h" #include "ap_drv_ops.h" #include "wps_hostapd.h" +#include "hs20.h" #include "ieee802_1x.h" static void ieee802_1x_finished(struct hostapd_data *hapd, - struct sta_info *sta, int success); + struct sta_info *sta, int success, + int remediation); static void ieee802_1x_send(struct hostapd_data *hapd, struct sta_info *sta, @@ -521,6 +523,41 @@ static void ieee802_1x_encapsulate_radius(struct hostapd_data *hapd, } } +#ifdef CONFIG_HS20 + if (hapd->conf->hs20) { + u8 ver = 1; /* Release 2 */ + if (!radius_msg_add_wfa( + msg, RADIUS_VENDOR_ATTR_WFA_HS20_AP_VERSION, + &ver, 1)) { + wpa_printf(MSG_ERROR, "Could not add HS 2.0 AP " + "version"); + goto fail; + } + + if (sta->hs20_ie && wpabuf_len(sta->hs20_ie) > 0) { + const u8 *pos; + u8 buf[3]; + u16 id; + pos = wpabuf_head_u8(sta->hs20_ie); + buf[0] = (*pos) >> 4; + if (((*pos) & HS20_PPS_MO_ID_PRESENT) && + wpabuf_len(sta->hs20_ie) >= 3) + id = WPA_GET_LE16(pos + 1); + else + id = 0; + WPA_PUT_BE16(buf + 1, id); + if (!radius_msg_add_wfa( + msg, + RADIUS_VENDOR_ATTR_WFA_HS20_STA_VERSION, + buf, sizeof(buf))) { + wpa_printf(MSG_ERROR, "Could not add HS 2.0 " + "STA version"); + goto fail; + } + } + } +#endif /* CONFIG_HS20 */ + if (radius_client_send(hapd->radius, msg, RADIUS_AUTH, sta->addr) < 0) goto fail; @@ -650,7 +687,7 @@ void ieee802_1x_receive(struct hostapd_data *hapd, const u8 *sa, const u8 *buf, struct rsn_pmksa_cache_entry *pmksa; int key_mgmt; - if (!hapd->conf->ieee802_1x && !hapd->conf->wpa && + if (!hapd->conf->ieee802_1x && !hapd->conf->wpa && !hapd->conf->osen && !hapd->conf->wps_state) return; @@ -701,7 +738,7 @@ void ieee802_1x_receive(struct hostapd_data *hapd, const u8 *sa, const u8 *buf, return; } - if (!hapd->conf->ieee802_1x && + if (!hapd->conf->ieee802_1x && !hapd->conf->osen && !(sta->flags & (WLAN_STA_WPS | WLAN_STA_MAYBE_WPS))) { wpa_printf(MSG_DEBUG, "IEEE 802.1X: Ignore EAPOL message - " "802.1X not enabled and WPS not used"); @@ -721,7 +758,7 @@ void ieee802_1x_receive(struct hostapd_data *hapd, const u8 *sa, const u8 *buf, return; #ifdef CONFIG_WPS - if (!hapd->conf->ieee802_1x) { + if (!hapd->conf->ieee802_1x && hapd->conf->wps_state) { u32 wflags = sta->flags & (WLAN_STA_WPS | WLAN_STA_WPS2 | WLAN_STA_MAYBE_WPS); @@ -839,7 +876,7 @@ void ieee802_1x_new_station(struct hostapd_data *hapd, struct sta_info *sta) } #endif /* CONFIG_WPS */ - if (!force_1x && !hapd->conf->ieee802_1x) { + if (!force_1x && !hapd->conf->ieee802_1x && !hapd->conf->osen) { wpa_printf(MSG_DEBUG, "IEEE 802.1X: Ignore STA - " "802.1X not enabled or forced for WPS"); /* @@ -877,7 +914,8 @@ void ieee802_1x_new_station(struct hostapd_data *hapd, struct sta_info *sta) #ifdef CONFIG_WPS sta->eapol_sm->flags &= ~EAPOL_SM_WAIT_START; - if (!hapd->conf->ieee802_1x && !(sta->flags & WLAN_STA_WPS2)) { + if (!hapd->conf->ieee802_1x && hapd->conf->wps_state && + !(sta->flags & WLAN_STA_WPS2)) { /* * Delay EAPOL frame transmission until a possible WPS STA * initiates the handshake with EAPOL-Start. Only allow the @@ -1203,6 +1241,147 @@ static void ieee802_1x_update_sta_cui(struct hostapd_data *hapd, } +#ifdef CONFIG_HS20 + +static void ieee802_1x_hs20_sub_rem(struct sta_info *sta, u8 *pos, size_t len) +{ + sta->remediation = 1; + os_free(sta->remediation_url); + if (len > 2) { + sta->remediation_url = os_malloc(len); + if (!sta->remediation_url) + return; + sta->remediation_method = pos[0]; + os_memcpy(sta->remediation_url, pos + 1, len - 1); + sta->remediation_url[len - 1] = '\0'; + wpa_printf(MSG_DEBUG, "HS 2.0: Subscription remediation needed " + "for " MACSTR " - server method %u URL %s", + MAC2STR(sta->addr), sta->remediation_method, + sta->remediation_url); + } else { + sta->remediation_url = NULL; + wpa_printf(MSG_DEBUG, "HS 2.0: Subscription remediation needed " + "for " MACSTR, MAC2STR(sta->addr)); + } + /* 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); +} + + +static void ieee802_1x_hs20_session_info(struct hostapd_data *hapd, + struct sta_info *sta, u8 *pos, + size_t len, int session_timeout) +{ + unsigned int swt; + int warning_time, beacon_int; + + if (len < 1) + return; /* Malformed information */ + os_free(sta->hs20_session_info_url); + sta->hs20_session_info_url = os_malloc(len); + if (sta->hs20_session_info_url == NULL) + return; + swt = pos[0]; + os_memcpy(sta->hs20_session_info_url, pos + 1, len - 1); + sta->hs20_session_info_url[len - 1] = '\0'; + wpa_printf(MSG_DEBUG, "HS 2.0: Session Information URL='%s' SWT=%u " + "(session_timeout=%d)", + sta->hs20_session_info_url, swt, session_timeout); + if (session_timeout < 0) { + wpa_printf(MSG_DEBUG, "HS 2.0: No Session-Timeout set - ignore session info URL"); + return; + } + if (swt == 255) + swt = 1; /* Use one minute as the AP selected value */ + + if ((unsigned int) session_timeout < swt * 60) + warning_time = 0; + else + warning_time = session_timeout - swt * 60; + + beacon_int = hapd->iconf->beacon_int; + if (beacon_int < 1) + beacon_int = 100; /* best guess */ + sta->hs20_disassoc_timer = swt * 60 * 1000 / beacon_int * 125 / 128; + if (sta->hs20_disassoc_timer > 65535) + sta->hs20_disassoc_timer = 65535; + + ap_sta_session_warning_timeout(hapd, sta, warning_time); +} + +#endif /* CONFIG_HS20 */ + + +static void ieee802_1x_check_hs20(struct hostapd_data *hapd, + struct sta_info *sta, + struct radius_msg *msg, + int session_timeout) +{ +#ifdef CONFIG_HS20 + u8 *buf, *pos, *end, type, sublen; + size_t len; + + 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) + break; + if (len < 6) + continue; + pos = buf; + end = buf + len; + if (WPA_GET_BE32(pos) != RADIUS_VENDOR_ID_WFA) + continue; + pos += 4; + + type = *pos++; + sublen = *pos++; + if (sublen < 2) + continue; /* invalid length */ + sublen -= 2; /* skip header */ + if (pos + sublen > end) + continue; /* invalid WFA VSA */ + + switch (type) { + 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; + case RADIUS_VENDOR_ATTR_WFA_HS20_SESSION_INFO_URL: + ieee802_1x_hs20_session_info(hapd, sta, pos, sublen, + session_timeout); + break; + } + } +#endif /* CONFIG_HS20 */ +} + + struct sta_id_search { u8 identifier; struct eapol_state_machine *sm; @@ -1361,7 +1540,11 @@ ieee802_1x_receive_auth(struct radius_msg *msg, struct radius_msg *req, ieee802_1x_store_radius_class(hapd, sta, msg); ieee802_1x_update_sta_identity(hapd, sta, msg); ieee802_1x_update_sta_cui(hapd, sta, msg); - if (sm->eap_if->eapKeyAvailable && + ieee802_1x_check_hs20(hapd, sta, msg, + session_timeout_set ? + (int) session_timeout : -1); + 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) { @@ -1564,14 +1747,14 @@ static void ieee802_1x_aaa_send(void *ctx, void *sta_ctx, static void _ieee802_1x_finished(void *ctx, void *sta_ctx, int success, - int preauth) + int preauth, int remediation) { struct hostapd_data *hapd = ctx; struct sta_info *sta = sta_ctx; if (preauth) rsn_preauth_finished(hapd, sta, success); else - ieee802_1x_finished(hapd, sta, success); + ieee802_1x_finished(hapd, sta, success, remediation); } @@ -1605,6 +1788,7 @@ static int ieee802_1x_get_eap_user(void *ctx, const u8 *identity, } user->force_version = eap_user->force_version; user->ttls_auth = eap_user->ttls_auth; + user->remediation = eap_user->remediation; return 0; } @@ -2108,15 +2292,49 @@ int ieee802_1x_get_mib_sta(struct hostapd_data *hapd, struct sta_info *sta, static void ieee802_1x_finished(struct hostapd_data *hapd, - struct sta_info *sta, int success) + struct sta_info *sta, int success, + int remediation) { const u8 *key; size_t len; /* TODO: get PMKLifetime from WPA parameters */ static const int dot11RSNAConfigPMKLifetime = 43200; +#ifdef CONFIG_HS20 + if (remediation && !sta->remediation) { + sta->remediation = 1; + os_free(sta->remediation_url); + sta->remediation_url = + os_strdup(hapd->conf->subscr_remediation_url); + sta->remediation_method = 1; /* SOAP-XML SPP */ + } + + if (success) { + if (sta->remediation) { + wpa_printf(MSG_DEBUG, "HS 2.0: Send WNM-Notification " + "to " MACSTR " to indicate Subscription " + "Remediation", + MAC2STR(sta->addr)); + hs20_send_wnm_notification(hapd, sta->addr, + sta->remediation_method, + sta->remediation_url); + 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 && + 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 24e764d6..f7af0889 100644 --- a/src/ap/sta_info.c +++ b/src/ap/sta_info.c @@ -30,11 +30,13 @@ #include "p2p_hostapd.h" #include "ap_drv_ops.h" #include "gas_serv.h" +#include "wnm_ap.h" #include "sta_info.h" static void ap_sta_remove_in_other_bss(struct hostapd_data *hapd, struct sta_info *sta); static void ap_handle_session_timer(void *eloop_ctx, void *timeout_ctx); +static void ap_handle_session_warning_timer(void *eloop_ctx, void *timeout_ctx); static void ap_sta_deauth_cb_timeout(void *eloop_ctx, void *timeout_ctx); static void ap_sta_disassoc_cb_timeout(void *eloop_ctx, void *timeout_ctx); #ifdef CONFIG_IEEE80211W @@ -224,6 +226,7 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta) __func__, MAC2STR(sta->addr)); eloop_cancel_timeout(ap_handle_timer, hapd, sta); eloop_cancel_timeout(ap_handle_session_timer, hapd, sta); + eloop_cancel_timeout(ap_handle_session_warning_timer, hapd, sta); eloop_cancel_timeout(ap_sta_deauth_cb_timeout, hapd, sta); eloop_cancel_timeout(ap_sta_disassoc_cb_timeout, hapd, sta); @@ -265,6 +268,9 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta) hostapd_free_psk_list(sta->psk); os_free(sta->identity); os_free(sta->radius_cui); + os_free(sta->remediation_url); + wpabuf_free(sta->hs20_deauth_req); + os_free(sta->hs20_session_info_url); #ifdef CONFIG_SAE sae_clear_data(sta->sae); @@ -520,6 +526,32 @@ void ap_sta_no_session_timeout(struct hostapd_data *hapd, struct sta_info *sta) } +static void ap_handle_session_warning_timer(void *eloop_ctx, void *timeout_ctx) +{ +#ifdef CONFIG_WNM + struct hostapd_data *hapd = eloop_ctx; + struct sta_info *sta = timeout_ctx; + + wpa_printf(MSG_DEBUG, "WNM: Session warning time reached for " MACSTR, + MAC2STR(sta->addr)); + if (sta->hs20_session_info_url == NULL) + return; + + wnm_send_ess_disassoc_imminent(hapd, sta, sta->hs20_session_info_url, + sta->hs20_disassoc_timer); +#endif /* CONFIG_WNM */ +} + + +void ap_sta_session_warning_timeout(struct hostapd_data *hapd, + struct sta_info *sta, int warning_time) +{ + eloop_cancel_timeout(ap_handle_session_warning_timer, hapd, sta); + eloop_register_timeout(warning_time, 0, ap_handle_session_warning_timer, + hapd, sta); +} + + struct sta_info * ap_sta_add(struct hostapd_data *hapd, const u8 *addr) { struct sta_info *sta; diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h index 240b9263..c0bab6f2 100644 --- a/src/ap/sta_info.h +++ b/src/ap/sta_info.h @@ -57,6 +57,8 @@ struct sta_info { unsigned int ht_20mhz_set:1; 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]; @@ -125,6 +127,11 @@ struct sta_info { struct wpabuf *wps_ie; /* WPS IE from (Re)Association Request */ struct wpabuf *p2p_ie; /* P2P IE from (Re)Association Request */ 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; + char *hs20_session_info_url; + int hs20_disassoc_timer; struct os_reltime connected_time; @@ -168,6 +175,8 @@ void ap_sta_session_timeout(struct hostapd_data *hapd, struct sta_info *sta, u32 session_timeout); void ap_sta_no_session_timeout(struct hostapd_data *hapd, struct sta_info *sta); +void ap_sta_session_warning_timeout(struct hostapd_data *hapd, + struct sta_info *sta, int warning_time); struct sta_info * ap_sta_add(struct hostapd_data *hapd, const u8 *addr); void ap_sta_disassociate(struct hostapd_data *hapd, struct sta_info *sta, u16 reason); diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c index 707a63f0..cc64ff18 100644 --- a/src/ap/wpa_auth.c +++ b/src/ap/wpa_auth.c @@ -211,6 +211,8 @@ static int wpa_use_aes_cmac(struct wpa_state_machine *sm) if (wpa_key_mgmt_sha256(sm->wpa_key_mgmt)) ret = 1; #endif /* CONFIG_IEEE80211W */ + if (sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) + ret = 1; return ret; } @@ -878,6 +880,7 @@ void wpa_receive(struct wpa_authenticator *wpa_auth, if (sm->pairwise == WPA_CIPHER_CCMP || sm->pairwise == WPA_CIPHER_GCMP) { if (wpa_use_aes_cmac(sm) && + sm->wpa_key_mgmt != WPA_KEY_MGMT_OSEN && ver != WPA_KEY_INFO_TYPE_AES_128_CMAC) { wpa_auth_logger(wpa_auth, sm->addr, LOGGER_WARNING, @@ -1001,6 +1004,9 @@ continue_processing: if (kde.rsn_ie) { eapol_key_ie = kde.rsn_ie; eapol_key_ie_len = kde.rsn_ie_len; + } else if (kde.osen) { + eapol_key_ie = kde.osen; + eapol_key_ie_len = kde.osen_len; } else { eapol_key_ie = kde.wpa_ie; eapol_key_ie_len = kde.wpa_ie_len; @@ -1286,6 +1292,8 @@ void __wpa_send_eapol(struct wpa_authenticator *wpa_auth, if (force_version) version = force_version; + else if (sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) + version = WPA_KEY_INFO_TYPE_AKM_DEFINED; else if (wpa_use_aes_cmac(sm)) version = WPA_KEY_INFO_TYPE_AES_128_CMAC; else if (sm->pairwise != WPA_CIPHER_TKIP) @@ -1308,6 +1316,7 @@ void __wpa_send_eapol(struct wpa_authenticator *wpa_auth, key_data_len = kde_len; if ((version == WPA_KEY_INFO_TYPE_HMAC_SHA1_AES || + sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN || version == WPA_KEY_INFO_TYPE_AES_128_CMAC) && encr) { pad_len = key_data_len % 8; if (pad_len) @@ -1376,6 +1385,7 @@ void __wpa_send_eapol(struct wpa_authenticator *wpa_auth, wpa_hexdump_key(MSG_DEBUG, "Plaintext EAPOL-Key Key Data", buf, key_data_len); if (version == WPA_KEY_INFO_TYPE_HMAC_SHA1_AES || + sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN || version == WPA_KEY_INFO_TYPE_AES_128_CMAC) { if (aes_wrap(sm->PTK.kek, (key_data_len - 8) / 8, buf, (u8 *) (key + 1))) { @@ -1774,7 +1784,8 @@ SM_STATE(WPA_PTK, PTKSTART) * one possible PSK for this STA. */ if (sm->wpa == WPA_VERSION_WPA2 && - wpa_key_mgmt_wpa_ieee8021x(sm->wpa_key_mgmt)) { + wpa_key_mgmt_wpa_ieee8021x(sm->wpa_key_mgmt) && + sm->wpa_key_mgmt != WPA_KEY_MGMT_OSEN) { pmkid = buf; pmkid_len = 2 + RSN_SELECTOR_LEN + PMKID_LEN; pmkid[0] = WLAN_EID_VENDOR_SPECIFIC; diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h index bc3dec45..d99db691 100644 --- a/src/ap/wpa_auth.h +++ b/src/ap/wpa_auth.h @@ -232,6 +232,9 @@ int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth, struct wpa_state_machine *sm, const u8 *wpa_ie, size_t wpa_ie_len, const u8 *mdie, size_t mdie_len); +int wpa_validate_osen(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, + const u8 *osen_ie, size_t osen_ie_len); int wpa_auth_uses_mfp(struct wpa_state_machine *sm); struct wpa_state_machine * wpa_auth_sta_init(struct wpa_authenticator *wpa_auth, const u8 *addr, diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c index 5af14950..da5fea7c 100644 --- a/src/ap/wpa_auth_glue.c +++ b/src/ap/wpa_auth_glue.c @@ -73,6 +73,19 @@ static void hostapd_wpa_auth_conf(struct hostapd_bss_config *conf, #endif /* CONFIG_IEEE80211R */ #ifdef CONFIG_HS20 wconf->disable_gtk = conf->disable_dgaf; + if (conf->osen) { + wconf->disable_gtk = 1; + wconf->wpa = WPA_PROTO_OSEN; + wconf->wpa_key_mgmt = WPA_KEY_MGMT_OSEN; + wconf->wpa_pairwise = 0; + wconf->wpa_group = WPA_CIPHER_CCMP; + wconf->rsn_pairwise = WPA_CIPHER_CCMP; + wconf->rsn_preauth = 0; + wconf->disable_pmksa_caching = 1; +#ifdef CONFIG_IEEE80211W + wconf->ieee80211w = 1; +#endif /* CONFIG_IEEE80211W */ + } #endif /* CONFIG_HS20 */ #ifdef CONFIG_TESTING_OPTIONS wconf->corrupt_gtk_rekey_mic_probability = diff --git a/src/ap/wpa_auth_ie.c b/src/ap/wpa_auth_ie.c index 274f4d62..7a497516 100644 --- a/src/ap/wpa_auth_ie.c +++ b/src/ap/wpa_auth_ie.c @@ -295,6 +295,55 @@ int wpa_write_rsn_ie(struct wpa_auth_config *conf, u8 *buf, size_t len, } +static u8 * wpa_write_osen(struct wpa_auth_config *conf, u8 *eid) +{ + u8 *len; + u16 capab; + + *eid++ = WLAN_EID_VENDOR_SPECIFIC; + len = eid++; /* to be filled */ + WPA_PUT_BE24(eid, OUI_WFA); + eid += 3; + *eid++ = HS20_OSEN_OUI_TYPE; + + /* Group Data Cipher Suite */ + RSN_SELECTOR_PUT(eid, RSN_CIPHER_SUITE_NO_GROUP_ADDRESSED); + eid += RSN_SELECTOR_LEN; + + /* Pairwise Cipher Suite Count and List */ + WPA_PUT_LE16(eid, 1); + eid += 2; + RSN_SELECTOR_PUT(eid, RSN_CIPHER_SUITE_CCMP); + eid += RSN_SELECTOR_LEN; + + /* AKM Suite Count and List */ + WPA_PUT_LE16(eid, 1); + eid += 2; + RSN_SELECTOR_PUT(eid, RSN_AUTH_KEY_MGMT_OSEN); + eid += RSN_SELECTOR_LEN; + + /* RSN Capabilities */ + capab = 0; + if (conf->wmm_enabled) { + /* 4 PTKSA replay counters when using WMM */ + capab |= (RSN_NUM_REPLAY_COUNTERS_16 << 2); + } +#ifdef CONFIG_IEEE80211W + if (conf->ieee80211w != NO_MGMT_FRAME_PROTECTION) { + capab |= WPA_CAPABILITY_MFPC; + if (conf->ieee80211w == MGMT_FRAME_PROTECTION_REQUIRED) + capab |= WPA_CAPABILITY_MFPR; + } +#endif /* CONFIG_IEEE80211W */ + WPA_PUT_LE16(eid, capab); + eid += 2; + + *len = eid - len - 1; + + return eid; +} + + int wpa_auth_gen_wpa_ie(struct wpa_authenticator *wpa_auth) { u8 *pos, buf[128]; @@ -302,6 +351,9 @@ int wpa_auth_gen_wpa_ie(struct wpa_authenticator *wpa_auth) pos = buf; + if (wpa_auth->conf.wpa == WPA_PROTO_OSEN) { + pos = wpa_write_osen(&wpa_auth->conf, pos); + } if (wpa_auth->conf.wpa & WPA_PROTO_RSN) { res = wpa_write_rsn_ie(&wpa_auth->conf, pos, buf + sizeof(buf) - pos, NULL); @@ -626,6 +678,36 @@ int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth, } +#ifdef CONFIG_HS20 +int wpa_validate_osen(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, + const u8 *osen_ie, size_t osen_ie_len) +{ + if (wpa_auth == NULL || sm == NULL) + return -1; + + /* TODO: parse OSEN element */ + sm->wpa_key_mgmt = WPA_KEY_MGMT_OSEN; + sm->mgmt_frame_prot = 1; + sm->pairwise = WPA_CIPHER_CCMP; + sm->wpa = WPA_VERSION_WPA2; + + if (sm->wpa_ie == NULL || sm->wpa_ie_len < osen_ie_len) { + os_free(sm->wpa_ie); + sm->wpa_ie = os_malloc(osen_ie_len); + if (sm->wpa_ie == NULL) + return -1; + } + + os_memcpy(sm->wpa_ie, osen_ie, osen_ie_len); + sm->wpa_ie_len = osen_ie_len; + + return 0; +} + +#endif /* CONFIG_HS20 */ + + /** * wpa_parse_generic - Parse EAPOL-Key Key Data Generic IEs * @pos: Pointer to the IE header @@ -648,6 +730,12 @@ static int wpa_parse_generic(const u8 *pos, const u8 *end, return 0; } + if (pos[1] >= 4 && WPA_GET_BE32(pos + 2) == OSEN_IE_VENDOR_TYPE) { + ie->osen = pos; + ie->osen_len = pos[1] + 2; + return 0; + } + if (pos + 1 + RSN_SELECTOR_LEN < end && pos[1] >= RSN_SELECTOR_LEN + PMKID_LEN && RSN_SELECTOR_GET(pos + 2) == RSN_KEY_DATA_PMKID) { diff --git a/src/ap/wpa_auth_ie.h b/src/ap/wpa_auth_ie.h index f9458825..d2067ba3 100644 --- a/src/ap/wpa_auth_ie.h +++ b/src/ap/wpa_auth_ie.h @@ -43,6 +43,9 @@ struct wpa_eapol_ie_parse { const u8 *ip_addr_req; const u8 *ip_addr_alloc; #endif /* CONFIG_P2P */ + + const u8 *osen; + size_t osen_len; }; int wpa_parse_kde_ies(const u8 *buf, size_t len, diff --git a/src/common/defs.h b/src/common/defs.h index 4811e8e9..d4091e31 100644 --- a/src/common/defs.h +++ b/src/common/defs.h @@ -48,12 +48,14 @@ typedef enum { FALSE = 0, TRUE = 1 } Boolean; #define WPA_KEY_MGMT_WAPI_PSK BIT(12) #define WPA_KEY_MGMT_WAPI_CERT BIT(13) #define WPA_KEY_MGMT_CCKM BIT(14) +#define WPA_KEY_MGMT_OSEN BIT(15) static inline int wpa_key_mgmt_wpa_ieee8021x(int akm) { return !!(akm & (WPA_KEY_MGMT_IEEE8021X | WPA_KEY_MGMT_FT_IEEE8021X | WPA_KEY_MGMT_CCKM | + WPA_KEY_MGMT_OSEN | WPA_KEY_MGMT_IEEE8021X_SHA256)); } @@ -82,7 +84,8 @@ static inline int wpa_key_mgmt_sae(int akm) static inline int wpa_key_mgmt_sha256(int akm) { return !!(akm & (WPA_KEY_MGMT_PSK_SHA256 | - WPA_KEY_MGMT_IEEE8021X_SHA256)); + WPA_KEY_MGMT_IEEE8021X_SHA256 | + WPA_KEY_MGMT_OSEN)); } static inline int wpa_key_mgmt_wpa(int akm) @@ -106,6 +109,7 @@ static inline int wpa_key_mgmt_cckm(int akm) #define WPA_PROTO_WPA BIT(0) #define WPA_PROTO_RSN BIT(1) #define WPA_PROTO_WAPI BIT(2) +#define WPA_PROTO_OSEN BIT(3) #define WPA_AUTH_ALG_OPEN BIT(0) #define WPA_AUTH_ALG_SHARED BIT(1) diff --git a/src/common/ieee802_11_common.c b/src/common/ieee802_11_common.c index 50bdc014..cdee6bc3 100644 --- a/src/common/ieee802_11_common.c +++ b/src/common/ieee802_11_common.c @@ -108,6 +108,11 @@ static int ieee802_11_parse_vendor_specific(const u8 *pos, size_t elen, elems->hs20 = pos; elems->hs20_len = elen; break; + case HS20_OSEN_OUI_TYPE: + /* Hotspot 2.0 OSEN */ + elems->osen = pos; + elems->osen_len = elen; + break; default: wpa_printf(MSG_MSGDUMP, "Unknown WFA " "information element ignored " diff --git a/src/common/ieee802_11_common.h b/src/common/ieee802_11_common.h index 4fb2e842..9b8bbd1c 100644 --- a/src/common/ieee802_11_common.h +++ b/src/common/ieee802_11_common.h @@ -41,6 +41,7 @@ struct ieee802_11_elems { const u8 *ext_capab; const u8 *bss_max_idle_period; const u8 *ssid_list; + const u8 *osen; u8 ssid_len; u8 supp_rates_len; @@ -69,6 +70,7 @@ struct ieee802_11_elems { u8 hs20_len; u8 ext_capab_len; u8 ssid_list_len; + u8 osen_len; }; typedef enum { ParseOK = 0, ParseUnknown = 1, ParseFailed = -1 } ParseRes; diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h index 0e39caf5..520e55d9 100644 --- a/src/common/ieee802_11_defs.h +++ b/src/common/ieee802_11_defs.h @@ -786,6 +786,7 @@ struct ieee80211_vht_operation { #define WFD_IE_VENDOR_TYPE 0x506f9a0a #define WFD_OUI_TYPE 10 #define HS20_IE_VENDOR_TYPE 0x506f9a10 +#define OSEN_IE_VENDOR_TYPE 0x506f9a12 #define WMM_OUI_TYPE 2 #define WMM_OUI_SUBTYPE_INFORMATION_ELEMENT 0 @@ -901,6 +902,7 @@ enum { #define HS20_INDICATION_OUI_TYPE 16 #define HS20_ANQP_OUI_TYPE 17 +#define HS20_OSEN_OUI_TYPE 18 #define HS20_STYPE_QUERY_LIST 1 #define HS20_STYPE_CAPABILITY_LIST 2 #define HS20_STYPE_OPERATOR_FRIENDLY_NAME 3 @@ -908,6 +910,21 @@ enum { #define HS20_STYPE_CONNECTION_CAPABILITY 5 #define HS20_STYPE_NAI_HOME_REALM_QUERY 6 #define HS20_STYPE_OPERATING_CLASS 7 +#define HS20_STYPE_OSU_PROVIDERS_LIST 8 +#define HS20_STYPE_ICON_REQUEST 10 +#define HS20_STYPE_ICON_BINARY_FILE 11 + +#define HS20_DGAF_DISABLED 0x01 +#define HS20_PPS_MO_ID_PRESENT 0x02 +#define HS20_ANQP_DOMAIN_ID_PRESENT 0x04 +#define HS20_VERSION 0x10 /* Release 2 */ + +/* WNM-Notification WFA vendors specific subtypes */ +#define HS20_WNM_SUB_REM_NEEDED 0 +#define HS20_WNM_DEAUTH_IMMINENT_NOTICE 1 + +#define HS20_DEAUTH_REASON_CODE_BSS 0 +#define HS20_DEAUTH_REASON_CODE_ESS 1 /* Wi-Fi Direct (P2P) */ diff --git a/src/common/wpa_common.c b/src/common/wpa_common.c index 37b265d0..c9d0ccb7 100644 --- a/src/common/wpa_common.c +++ b/src/common/wpa_common.c @@ -56,6 +56,11 @@ int wpa_eapol_key_mic(const u8 *key, int ver, const u8 *buf, size_t len, case WPA_KEY_INFO_TYPE_AES_128_CMAC: return omac1_aes_128(key, buf, len, mic); #endif /* CONFIG_IEEE80211R || CONFIG_IEEE80211W */ +#ifdef CONFIG_HS20 + case WPA_KEY_INFO_TYPE_AKM_DEFINED: + /* FIX: This should be based on negotiated AKM */ + return omac1_aes_128(key, buf, len, mic); +#endif /* CONFIG_HS20 */ default: return -1; } diff --git a/src/common/wpa_common.h b/src/common/wpa_common.h index dcc035c7..5684ef3f 100644 --- a/src/common/wpa_common.h +++ b/src/common/wpa_common.h @@ -67,6 +67,7 @@ WPA_CIPHER_GTK_NOT_USED) #define RSN_AUTH_KEY_MGMT_FT_802_1X_SUITE_B_384 \ RSN_SELECTOR(0x00, 0x0f, 0xac, 13) #define RSN_AUTH_KEY_MGMT_CCKM RSN_SELECTOR(0x00, 0x40, 0x96, 0x00) +#define RSN_AUTH_KEY_MGMT_OSEN RSN_SELECTOR(0x50, 0x6f, 0x9a, 0x01) #define RSN_CIPHER_SUITE_NONE RSN_SELECTOR(0x00, 0x0f, 0xac, 0) #define RSN_CIPHER_SUITE_WEP40 RSN_SELECTOR(0x00, 0x0f, 0xac, 1) @@ -157,6 +158,7 @@ RSN_SELECTOR(0x00, 0x0f, 0xac, 13) /* IEEE 802.11, 8.5.2 EAPOL-Key frames */ #define WPA_KEY_INFO_TYPE_MASK ((u16) (BIT(0) | BIT(1) | BIT(2))) +#define WPA_KEY_INFO_TYPE_AKM_DEFINED 0 #define WPA_KEY_INFO_TYPE_HMAC_MD5_RC4 BIT(0) #define WPA_KEY_INFO_TYPE_HMAC_SHA1_AES BIT(1) #define WPA_KEY_INFO_TYPE_AES_128_CMAC 3 diff --git a/src/common/wpa_ctrl.c b/src/common/wpa_ctrl.c index f4af94aa..5820a136 100644 --- a/src/common/wpa_ctrl.c +++ b/src/common/wpa_ctrl.c @@ -25,6 +25,10 @@ #include "private/android_filesystem_config.h" #endif /* ANDROID */ +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 +#include <net/if.h> +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ + #include "wpa_ctrl.h" #include "common.h" @@ -46,8 +50,13 @@ struct wpa_ctrl { #ifdef CONFIG_CTRL_IFACE_UDP int s; +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + struct sockaddr_in6 local; + struct sockaddr_in6 dest; +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ struct sockaddr_in local; struct sockaddr_in dest; +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ char *cookie; char *remote_ifname; char *remote_ip; @@ -279,19 +288,33 @@ struct wpa_ctrl * wpa_ctrl_open(const char *ctrl_path) return NULL; os_memset(ctrl, 0, sizeof(*ctrl)); +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + ctrl->s = socket(PF_INET6, SOCK_DGRAM, 0); +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ ctrl->s = socket(PF_INET, SOCK_DGRAM, 0); +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ if (ctrl->s < 0) { perror("socket"); os_free(ctrl); return NULL; } +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + ctrl->local.sin6_family = AF_INET6; +#ifdef CONFIG_CTRL_IFACE_UDP_REMOTE + ctrl->local.sin6_addr = in6addr_any; +#else /* CONFIG_CTRL_IFACE_UDP_REMOTE */ + inet_pton(AF_INET6, "::1", &ctrl->local.sin6_addr); +#endif /* CONFIG_CTRL_IFACE_UDP_REMOTE */ +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ ctrl->local.sin_family = AF_INET; #ifdef CONFIG_CTRL_IFACE_UDP_REMOTE ctrl->local.sin_addr.s_addr = INADDR_ANY; #else /* CONFIG_CTRL_IFACE_UDP_REMOTE */ ctrl->local.sin_addr.s_addr = htonl((127 << 24) | 1); #endif /* CONFIG_CTRL_IFACE_UDP_REMOTE */ +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ + if (bind(ctrl->s, (struct sockaddr *) &ctrl->local, sizeof(ctrl->local)) < 0) { close(ctrl->s); @@ -299,14 +322,24 @@ struct wpa_ctrl * wpa_ctrl_open(const char *ctrl_path) return NULL; } +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + ctrl->dest.sin6_family = AF_INET6; + inet_pton(AF_INET6, "::1", &ctrl->dest.sin6_addr); + ctrl->dest.sin6_port = htons(WPA_CTRL_IFACE_PORT); +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ ctrl->dest.sin_family = AF_INET; ctrl->dest.sin_addr.s_addr = htonl((127 << 24) | 1); ctrl->dest.sin_port = htons(WPA_CTRL_IFACE_PORT); +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ #ifdef CONFIG_CTRL_IFACE_UDP_REMOTE if (ctrl_path) { char *port, *name; int port_id; +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + char *scope; + int scope_id = 0; +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ name = os_strdup(ctrl_path); if (name == NULL) { @@ -314,7 +347,11 @@ struct wpa_ctrl * wpa_ctrl_open(const char *ctrl_path) os_free(ctrl); return NULL; } +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + port = os_strchr(name, ','); +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ port = os_strchr(name, ':'); +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ if (port) { port_id = atoi(&port[1]); @@ -322,7 +359,16 @@ struct wpa_ctrl * wpa_ctrl_open(const char *ctrl_path) } else port_id = WPA_CTRL_IFACE_PORT; +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + scope = os_strchr(name, '%'); + if (scope) { + scope_id = if_nametoindex(&scope[1]); + scope[0] = '\0'; + } + h = gethostbyname2(name, AF_INET6); +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ h = gethostbyname(name); +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ ctrl->remote_ip = os_strdup(name); os_free(name); if (h == NULL) { @@ -332,16 +378,33 @@ struct wpa_ctrl * wpa_ctrl_open(const char *ctrl_path) os_free(ctrl); return NULL; } +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + ctrl->dest.sin6_scope_id = scope_id; + ctrl->dest.sin6_port = htons(port_id); + os_memcpy(&ctrl->dest.sin6_addr, h->h_addr, h->h_length); +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ ctrl->dest.sin_port = htons(port_id); - os_memcpy(h->h_addr, (char *) &ctrl->dest.sin_addr.s_addr, - h->h_length); + os_memcpy(&ctrl->dest.sin_addr.s_addr, h->h_addr, h->h_length); +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ } else ctrl->remote_ip = os_strdup("localhost"); #endif /* CONFIG_CTRL_IFACE_UDP_REMOTE */ if (connect(ctrl->s, (struct sockaddr *) &ctrl->dest, sizeof(ctrl->dest)) < 0) { - perror("connect"); +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + char addr[INET6_ADDRSTRLEN]; + wpa_printf(MSG_ERROR, "connect(%s:%d) failed: %s", + inet_ntop(AF_INET6, &ctrl->dest.sin6_addr, addr, + sizeof(ctrl->dest)), + ntohs(ctrl->dest.sin6_port), + strerror(errno)); +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ + wpa_printf(MSG_ERROR, "connect(%s:%d) failed: %s", + inet_ntoa(ctrl->dest.sin_addr), + ntohs(ctrl->dest.sin_port), + strerror(errno)); +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ close(ctrl->s); os_free(ctrl->remote_ip); os_free(ctrl); diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h index 759cee47..6d172283 100644 --- a/src/common/wpa_ctrl.h +++ b/src/common/wpa_ctrl.h @@ -159,8 +159,10 @@ extern "C" { #define P2P_EVENT_REMOVE_AND_REFORM_GROUP "P2P-REMOVE-AND-REFORM-GROUP " #define INTERWORKING_AP "INTERWORKING-AP " +#define INTERWORKING_BLACKLISTED "INTERWORKING-BLACKLISTED " #define INTERWORKING_NO_MATCH "INTERWORKING-NO-MATCH " #define INTERWORKING_ALREADY_CONNECTED "INTERWORKING-ALREADY-CONNECTED " +#define INTERWORKING_SELECTED "INTERWORKING-SELECTED " #define GAS_RESPONSE_INFO "GAS-RESPONSE-INFO " /* parameters: <addr> <dialog_token> <freq> */ @@ -168,6 +170,9 @@ extern "C" { /* parameters: <addr> <dialog_token> <freq> <status_code> <result> */ #define GAS_QUERY_DONE "GAS-QUERY-DONE " +#define HS20_SUBSCRIPTION_REMEDIATION "HS20-SUBSCRIPTION-REMEDIATION " +#define HS20_DEAUTH_IMMINENT_NOTICE "HS20-DEAUTH-IMMINENT-NOTICE " + #define EXT_RADIO_WORK_START "EXT-RADIO-WORK-START " #define EXT_RADIO_WORK_TIMEOUT "EXT-RADIO-WORK-TIMEOUT " diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 632ae3a7..d2aad246 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -847,6 +847,11 @@ struct wpa_driver_ap_params { * disable_dgaf - Whether group-addressed frames are disabled */ int disable_dgaf; + + /** + * osen - Whether OSEN security is enabled + */ + int osen; }; /** @@ -875,6 +880,7 @@ struct wpa_driver_capa { #define WPA_DRIVER_CAPA_ENC_BIP_GMAC_128 0x00000200 #define WPA_DRIVER_CAPA_ENC_BIP_GMAC_256 0x00000400 #define WPA_DRIVER_CAPA_ENC_BIP_CMAC_256 0x00000800 +#define WPA_DRIVER_CAPA_ENC_GTK_NOT_USED 0x00001000 unsigned int enc; #define WPA_DRIVER_AUTH_OPEN 0x00000001 diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index 32a371d1..42578b61 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -3525,6 +3525,9 @@ static void wiphy_info_cipher_suites(struct wiphy_info_data *info, case WLAN_CIPHER_SUITE_BIP_CMAC_256: info->capa->enc |= WPA_DRIVER_CAPA_ENC_BIP_CMAC_256; break; + case WLAN_CIPHER_SUITE_NO_GROUP_ADDR: + info->capa->enc |= WPA_DRIVER_CAPA_ENC_GTK_NOT_USED; + break; } } } @@ -4373,6 +4376,12 @@ static int nl80211_mgmt_subscribe_non_ap(struct i802_bss *bss) if (nl80211_register_action_frame(bss, (u8 *) "\x0a\x11", 2) < 0) ret = -1; +#ifdef CONFIG_HS20 + /* WNM-Notification */ + if (nl80211_register_action_frame(bss, (u8 *) "\x0a\x1a", 2) < 0) + return -1; +#endif /* CONFIG_HS20 */ + nl80211_mgmt_handle_register_eloop(bss); return ret; @@ -5511,6 +5520,8 @@ static u32 wpa_cipher_to_cipher_suite(unsigned int cipher) return WLAN_CIPHER_SUITE_WEP104; case WPA_CIPHER_WEP40: return WLAN_CIPHER_SUITE_WEP40; + case WPA_CIPHER_GTK_NOT_USED: + return WLAN_CIPHER_SUITE_NO_GROUP_ADDR; } return 0; @@ -6642,7 +6653,7 @@ static int nl80211_get_reg(struct nl_msg *msg, void *arg) nla_for_each_nested(nl_rule, tb_msg[NL80211_ATTR_REG_RULES], rem_rule) { - u32 start, end, max_eirp = 0, max_bw = 0; + u32 start, end, max_eirp = 0, max_bw = 0, flags = 0; nla_parse(tb_rule, NL80211_FREQUENCY_ATTR_MAX, nla_data(nl_rule), nla_len(nl_rule), reg_policy); if (tb_rule[NL80211_ATTR_FREQ_RANGE_START] == NULL || @@ -6654,9 +6665,20 @@ static int nl80211_get_reg(struct nl_msg *msg, void *arg) max_eirp = nla_get_u32(tb_rule[NL80211_ATTR_POWER_RULE_MAX_EIRP]) / 100; if (tb_rule[NL80211_ATTR_FREQ_RANGE_MAX_BW]) max_bw = nla_get_u32(tb_rule[NL80211_ATTR_FREQ_RANGE_MAX_BW]) / 1000; - - wpa_printf(MSG_DEBUG, "nl80211: %u-%u @ %u MHz %u mBm", - start, end, max_bw, max_eirp); + if (tb_rule[NL80211_ATTR_REG_RULE_FLAGS]) + flags = nla_get_u32(tb_rule[NL80211_ATTR_REG_RULE_FLAGS]); + + wpa_printf(MSG_DEBUG, "nl80211: %u-%u @ %u MHz %u mBm%s%s%s%s%s%s%s%s", + start, end, max_bw, max_eirp, + flags & NL80211_RRF_NO_OFDM ? " (no OFDM)" : "", + flags & NL80211_RRF_NO_CCK ? " (no CCK)" : "", + flags & NL80211_RRF_NO_INDOOR ? " (no indoor)" : "", + flags & NL80211_RRF_NO_OUTDOOR ? " (no outdoor)" : + "", + flags & NL80211_RRF_DFS ? " (DFS)" : "", + flags & NL80211_RRF_PTP_ONLY ? " (PTP only)" : "", + flags & NL80211_RRF_PTMP_ONLY ? " (PTMP only)" : "", + flags & NL80211_RRF_NO_IR ? " (no IR)" : ""); if (max_bw >= 40) nl80211_reg_rule_ht40(start, end, results); if (tb_rule[NL80211_ATTR_POWER_RULE_MAX_EIRP]) @@ -8459,7 +8481,14 @@ static int nl80211_connect_common(struct wpa_driver_nl80211_data *drv, NLA_PUT_U32(msg, NL80211_ATTR_CIPHER_SUITES_PAIRWISE, cipher); } - if (params->group_suite != WPA_CIPHER_NONE) { + if (params->group_suite == WPA_CIPHER_GTK_NOT_USED && + !(drv->capa.enc & WPA_DRIVER_CAPA_ENC_GTK_NOT_USED)) { + /* + * This is likely to work even though many drivers do not + * advertise support for operations without GTK. + */ + wpa_printf(MSG_DEBUG, " * skip group cipher configuration for GTK_NOT_USED due to missing driver support advertisement"); + } else if (params->group_suite != WPA_CIPHER_NONE) { u32 cipher = wpa_cipher_to_cipher_suite(params->group_suite); wpa_printf(MSG_DEBUG, " * group=0x%x", cipher); NLA_PUT_U32(msg, NL80211_ATTR_CIPHER_SUITE_GROUP, cipher); diff --git a/src/eap_common/eap_defs.h b/src/eap_common/eap_defs.h index f5890bec..4f14a01e 100644 --- a/src/eap_common/eap_defs.h +++ b/src/eap_common/eap_defs.h @@ -72,13 +72,16 @@ typedef enum { enum { EAP_VENDOR_IETF = 0, EAP_VENDOR_MICROSOFT = 0x000137 /* Microsoft */, - EAP_VENDOR_WFA = 0x00372A /* Wi-Fi Alliance */, - EAP_VENDOR_HOSTAP = 39068 /* hostapd/wpa_supplicant project */ + EAP_VENDOR_WFA = 0x00372A /* Wi-Fi Alliance (moved to WBA) */, + EAP_VENDOR_HOSTAP = 39068 /* hostapd/wpa_supplicant project */, + EAP_VENDOR_WFA_NEW = 40808 /* Wi-Fi Alliance */ }; #define EAP_VENDOR_UNAUTH_TLS EAP_VENDOR_HOSTAP #define EAP_VENDOR_TYPE_UNAUTH_TLS 1 +#define EAP_VENDOR_WFA_UNAUTH_TLS 13 + #define EAP_MSK_LEN 64 #define EAP_EMSK_LEN 64 diff --git a/src/eap_peer/eap_aka.c b/src/eap_peer/eap_aka.c index d3cbaca6..fee1b7b7 100644 --- a/src/eap_peer/eap_aka.c +++ b/src/eap_peer/eap_aka.c @@ -316,7 +316,7 @@ static int eap_aka_umts_auth(struct eap_sm *sm, struct eap_aka_data *data) #else /* CONFIG_USIM_HARDCODED */ - wpa_printf(MSG_DEBUG, "EAP-AKA: No UMTS authentication algorith " + wpa_printf(MSG_DEBUG, "EAP-AKA: No UMTS authentication algorithm " "enabled"); return -1; diff --git a/src/eap_peer/eap_methods.h b/src/eap_peer/eap_methods.h index a465fd23..e35c919a 100644 --- a/src/eap_peer/eap_methods.h +++ b/src/eap_peer/eap_methods.h @@ -86,6 +86,7 @@ static inline int eap_peer_method_unload(struct eap_method *method) int eap_peer_md5_register(void); int eap_peer_tls_register(void); int eap_peer_unauth_tls_register(void); +int eap_peer_wfa_unauth_tls_register(void); int eap_peer_mschapv2_register(void); int eap_peer_peap_register(void); int eap_peer_ttls_register(void); diff --git a/src/eap_peer/eap_tls.c b/src/eap_peer/eap_tls.c index d2066cd8..bb9f3f26 100644 --- a/src/eap_peer/eap_tls.c +++ b/src/eap_peer/eap_tls.c @@ -98,6 +98,33 @@ static void * eap_unauth_tls_init(struct eap_sm *sm) #endif /* EAP_UNAUTH_TLS */ +#ifdef CONFIG_HS20 +static void * eap_wfa_unauth_tls_init(struct eap_sm *sm) +{ + struct eap_tls_data *data; + struct eap_peer_config *config = eap_get_config(sm); + + data = os_zalloc(sizeof(*data)); + if (data == NULL) + return NULL; + + data->ssl_ctx = sm->init_phase2 && sm->ssl_ctx2 ? sm->ssl_ctx2 : + sm->ssl_ctx; + + if (eap_peer_tls_ssl_init(sm, &data->ssl, config, + EAP_WFA_UNAUTH_TLS_TYPE)) { + wpa_printf(MSG_INFO, "EAP-TLS: Failed to initialize SSL."); + eap_tls_deinit(sm, data); + return NULL; + } + + data->eap_type = EAP_WFA_UNAUTH_TLS_TYPE; + + return data; +} +#endif /* CONFIG_HS20 */ + + static void eap_tls_deinit(struct eap_sm *sm, void *priv) { struct eap_tls_data *data = priv; @@ -382,3 +409,35 @@ int eap_peer_unauth_tls_register(void) return ret; } #endif /* EAP_UNAUTH_TLS */ + + +#ifdef CONFIG_HS20 +int eap_peer_wfa_unauth_tls_register(void) +{ + struct eap_method *eap; + int ret; + + eap = eap_peer_method_alloc(EAP_PEER_METHOD_INTERFACE_VERSION, + EAP_VENDOR_WFA_NEW, + EAP_VENDOR_WFA_UNAUTH_TLS, + "WFA-UNAUTH-TLS"); + if (eap == NULL) + return -1; + + eap->init = eap_wfa_unauth_tls_init; + eap->deinit = eap_tls_deinit; + eap->process = eap_tls_process; + eap->isKeyAvailable = eap_tls_isKeyAvailable; + eap->getKey = eap_tls_getKey; + eap->get_status = eap_tls_get_status; + eap->has_reauth_data = eap_tls_has_reauth_data; + eap->deinit_for_reauth = eap_tls_deinit_for_reauth; + eap->init_for_reauth = eap_tls_init_for_reauth; + eap->get_emsk = eap_tls_get_emsk; + + ret = eap_peer_method_register(eap); + if (ret) + eap_peer_method_free(eap); + return ret; +} +#endif /* CONFIG_HS20 */ diff --git a/src/eap_peer/eap_tls_common.c b/src/eap_peer/eap_tls_common.c index b3a99b62..fe9bfe00 100644 --- a/src/eap_peer/eap_tls_common.c +++ b/src/eap_peer/eap_tls_common.c @@ -23,6 +23,10 @@ static struct wpabuf * eap_tls_msg_alloc(EapType type, size_t payload_len, return eap_msg_alloc(EAP_VENDOR_UNAUTH_TLS, EAP_VENDOR_TYPE_UNAUTH_TLS, payload_len, code, identifier); + if (type == EAP_WFA_UNAUTH_TLS_TYPE) + return eap_msg_alloc(EAP_VENDOR_WFA_NEW, + EAP_VENDOR_WFA_UNAUTH_TLS, payload_len, + code, identifier); return eap_msg_alloc(EAP_VENDOR_IETF, type, payload_len, code, identifier); } @@ -846,6 +850,10 @@ const u8 * eap_peer_tls_process_init(struct eap_sm *sm, pos = eap_hdr_validate(EAP_VENDOR_UNAUTH_TLS, EAP_VENDOR_TYPE_UNAUTH_TLS, reqData, &left); + else if (eap_type == EAP_WFA_UNAUTH_TLS_TYPE) + pos = eap_hdr_validate(EAP_VENDOR_WFA_NEW, + EAP_VENDOR_WFA_UNAUTH_TLS, reqData, + &left); else pos = eap_hdr_validate(EAP_VENDOR_IETF, eap_type, reqData, &left); diff --git a/src/eap_peer/eap_tls_common.h b/src/eap_peer/eap_tls_common.h index 1a5e0f89..390c2165 100644 --- a/src/eap_peer/eap_tls_common.h +++ b/src/eap_peer/eap_tls_common.h @@ -87,6 +87,7 @@ struct eap_ssl_data { /* dummy type used as a flag for UNAUTH-TLS */ #define EAP_UNAUTH_TLS_TYPE 255 +#define EAP_WFA_UNAUTH_TLS_TYPE 254 int eap_peer_tls_ssl_init(struct eap_sm *sm, struct eap_ssl_data *data, diff --git a/src/eap_server/eap.h b/src/eap_server/eap.h index 36b230b4..197b232f 100644 --- a/src/eap_server/eap.h +++ b/src/eap_server/eap.h @@ -32,6 +32,7 @@ struct eap_user { * nt_password_hash() */ int phase2; int force_version; + unsigned int remediation:1; int ttls_auth; /* bitfield of * EAP_TTLS_AUTH_{PAP,CHAP,MSCHAP,MSCHAPV2} */ }; diff --git a/src/eap_server/eap_methods.h b/src/eap_server/eap_methods.h index 429cb72b..0baa3279 100644 --- a/src/eap_server/eap_methods.h +++ b/src/eap_server/eap_methods.h @@ -27,6 +27,7 @@ int eap_server_identity_register(void); int eap_server_md5_register(void); int eap_server_tls_register(void); int eap_server_unauth_tls_register(void); +int eap_server_wfa_unauth_tls_register(void); int eap_server_mschapv2_register(void); int eap_server_peap_register(void); int eap_server_tlv_register(void); diff --git a/src/eap_server/eap_server_tls.c b/src/eap_server/eap_server_tls.c index 447f47cf..6bed62f8 100644 --- a/src/eap_server/eap_server_tls.c +++ b/src/eap_server/eap_server_tls.c @@ -94,6 +94,28 @@ static void * eap_unauth_tls_init(struct eap_sm *sm) #endif /* EAP_SERVER_UNAUTH_TLS */ +#ifdef CONFIG_HS20 +static void * eap_wfa_unauth_tls_init(struct eap_sm *sm) +{ + struct eap_tls_data *data; + + data = os_zalloc(sizeof(*data)); + if (data == NULL) + return NULL; + data->state = START; + + if (eap_server_tls_ssl_init(sm, &data->ssl, 0)) { + wpa_printf(MSG_INFO, "EAP-TLS: Failed to initialize SSL."); + eap_tls_reset(sm, data); + return NULL; + } + + data->eap_type = EAP_WFA_UNAUTH_TLS_TYPE; + return data; +} +#endif /* CONFIG_HS20 */ + + static void eap_tls_reset(struct eap_sm *sm, void *priv) { struct eap_tls_data *data = priv; @@ -178,6 +200,10 @@ static Boolean eap_tls_check(struct eap_sm *sm, void *priv, pos = eap_hdr_validate(EAP_VENDOR_UNAUTH_TLS, EAP_VENDOR_TYPE_UNAUTH_TLS, respData, &len); + else if (data->eap_type == EAP_WFA_UNAUTH_TLS_TYPE) + pos = eap_hdr_validate(EAP_VENDOR_WFA_NEW, + EAP_VENDOR_WFA_UNAUTH_TLS, respData, + &len); else pos = eap_hdr_validate(EAP_VENDOR_IETF, data->eap_type, respData, &len); @@ -340,3 +366,34 @@ int eap_server_unauth_tls_register(void) return ret; } #endif /* EAP_SERVER_UNAUTH_TLS */ + + +#ifdef CONFIG_HS20 +int eap_server_wfa_unauth_tls_register(void) +{ + struct eap_method *eap; + int ret; + + eap = eap_server_method_alloc(EAP_SERVER_METHOD_INTERFACE_VERSION, + EAP_VENDOR_WFA_NEW, + EAP_VENDOR_WFA_UNAUTH_TLS, + "WFA-UNAUTH-TLS"); + if (eap == NULL) + return -1; + + eap->init = eap_wfa_unauth_tls_init; + eap->reset = eap_tls_reset; + eap->buildReq = eap_tls_buildReq; + eap->check = eap_tls_check; + eap->process = eap_tls_process; + eap->isDone = eap_tls_isDone; + eap->getKey = eap_tls_getKey; + eap->isSuccess = eap_tls_isSuccess; + eap->get_emsk = eap_tls_get_emsk; + + ret = eap_server_method_register(eap); + if (ret) + eap_server_method_free(eap); + return ret; +} +#endif /* CONFIG_HS20 */ diff --git a/src/eap_server/eap_server_tls_common.c b/src/eap_server/eap_server_tls_common.c index 526e1bcc..de5ab0dd 100644 --- a/src/eap_server/eap_server_tls_common.c +++ b/src/eap_server/eap_server_tls_common.c @@ -25,6 +25,10 @@ struct wpabuf * eap_tls_msg_alloc(EapType type, size_t payload_len, return eap_msg_alloc(EAP_VENDOR_UNAUTH_TLS, EAP_VENDOR_TYPE_UNAUTH_TLS, payload_len, code, identifier); + else if (type == EAP_WFA_UNAUTH_TLS_TYPE) + return eap_msg_alloc(EAP_VENDOR_WFA_NEW, + EAP_VENDOR_WFA_UNAUTH_TLS, payload_len, + code, identifier); return eap_msg_alloc(EAP_VENDOR_IETF, type, payload_len, code, identifier); } @@ -393,6 +397,10 @@ int eap_server_tls_process(struct eap_sm *sm, struct eap_ssl_data *data, pos = eap_hdr_validate(EAP_VENDOR_UNAUTH_TLS, EAP_VENDOR_TYPE_UNAUTH_TLS, respData, &left); + else if (eap_type == EAP_WFA_UNAUTH_TLS_TYPE) + pos = eap_hdr_validate(EAP_VENDOR_WFA_NEW, + EAP_VENDOR_WFA_UNAUTH_TLS, respData, + &left); else pos = eap_hdr_validate(EAP_VENDOR_IETF, eap_type, respData, &left); diff --git a/src/eap_server/eap_tls_common.h b/src/eap_server/eap_tls_common.h index 11f58275..91449afd 100644 --- a/src/eap_server/eap_tls_common.h +++ b/src/eap_server/eap_tls_common.h @@ -64,6 +64,7 @@ struct eap_ssl_data { /* dummy type used as a flag for UNAUTH-TLS */ #define EAP_UNAUTH_TLS_TYPE 255 +#define EAP_WFA_UNAUTH_TLS_TYPE 254 struct wpabuf * eap_tls_msg_alloc(EapType type, size_t payload_len, diff --git a/src/eapol_auth/eapol_auth_sm.c b/src/eapol_auth/eapol_auth_sm.c index a2577814..525bdeef 100644 --- a/src/eapol_auth/eapol_auth_sm.c +++ b/src/eapol_auth/eapol_auth_sm.c @@ -219,7 +219,8 @@ SM_STATE(AUTH_PAE, DISCONNECTED) sm->eapolLogoff = FALSE; if (!from_initialize) { sm->eapol->cb.finished(sm->eapol->conf.ctx, sm->sta, 0, - sm->flags & EAPOL_SM_PREAUTH); + sm->flags & EAPOL_SM_PREAUTH, + sm->remediation); } } @@ -276,7 +277,7 @@ SM_STATE(AUTH_PAE, HELD) 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->flags & EAPOL_SM_PREAUTH, sm->remediation); } @@ -302,7 +303,7 @@ SM_STATE(AUTH_PAE, AUTHENTICATED) 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->flags & EAPOL_SM_PREAUTH, sm->remediation); } @@ -1001,8 +1002,13 @@ static int eapol_sm_get_eap_user(void *ctx, const u8 *identity, struct eap_user *user) { struct eapol_state_machine *sm = ctx; - return sm->eapol->cb.get_eap_user(sm->eapol->conf.ctx, identity, - identity_len, phase2, user); + 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; } diff --git a/src/eapol_auth/eapol_auth_sm.h b/src/eapol_auth/eapol_auth_sm.h index f0ff4644..320a0adb 100644 --- a/src/eapol_auth/eapol_auth_sm.h +++ b/src/eapol_auth/eapol_auth_sm.h @@ -60,7 +60,8 @@ struct eapol_auth_cb { size_t datalen); void (*aaa_send)(void *ctx, void *sta_ctx, const u8 *data, size_t datalen); - void (*finished)(void *ctx, void *sta_ctx, int success, int preauth); + void (*finished)(void *ctx, void *sta_ctx, int success, int preauth, + int remediation); int (*get_eap_user)(void *ctx, const u8 *identity, size_t identity_len, int phase2, struct eap_user *user); int (*sta_entry_alive)(void *ctx, const u8 *addr); diff --git a/src/eapol_auth/eapol_auth_sm_i.h b/src/eapol_auth/eapol_auth_sm_i.h index d7f893a1..25baddba 100644 --- a/src/eapol_auth/eapol_auth_sm_i.h +++ b/src/eapol_auth/eapol_auth_sm_i.h @@ -173,6 +173,8 @@ struct eapol_state_machine { struct eapol_authenticator *eapol; void *sta; /* station context pointer to use in callbacks */ + + int remediation; }; #endif /* EAPOL_AUTH_SM_I_H */ diff --git a/src/radius/radius.c b/src/radius/radius.c index 1070fc70..370b517f 100644 --- a/src/radius/radius.c +++ b/src/radius/radius.c @@ -1220,6 +1220,33 @@ int radius_msg_add_mppe_keys(struct radius_msg *msg, } +int radius_msg_add_wfa(struct radius_msg *msg, u8 subtype, const u8 *data, + size_t len) +{ + struct radius_attr_hdr *attr; + u8 *buf, *pos; + size_t alen; + + alen = 4 + 2 + len; + buf = os_malloc(alen); + if (buf == NULL) + return 0; + pos = buf; + WPA_PUT_BE32(pos, RADIUS_VENDOR_ID_WFA); + pos += 4; + *pos++ = subtype; + *pos++ = 2 + len; + os_memcpy(pos, data, len); + attr = radius_msg_add_attr(msg, RADIUS_ATTR_VENDOR_SPECIFIC, + buf, alen); + os_free(buf); + if (attr == NULL) + return 0; + + return 1; +} + + /* Add User-Password attribute to a RADIUS message and encrypt it as specified * in RFC 2865, Chap. 5.2 */ struct radius_attr_hdr * diff --git a/src/radius/radius.h b/src/radius/radius.h index ad65b04b..d8bf21eb 100644 --- a/src/radius/radius.h +++ b/src/radius/radius.h @@ -163,6 +163,18 @@ enum { RADIUS_VENDOR_ATTR_MS_MPPE_SEND_KEY = 16, RADIUS_VENDOR_ATTR_MS_MPPE_RECV_KEY = 17 }; + +/* Hotspot 2.0 - WFA Vendor-specific RADIUS Attributes */ +#define RADIUS_VENDOR_ID_WFA 40808 + +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, + RADIUS_VENDOR_ATTR_WFA_HS20_SESSION_INFO_URL = 5, +}; + #ifdef _MSC_VER #pragma pack(pop) #endif /* _MSC_VER */ @@ -237,6 +249,8 @@ int radius_msg_add_mppe_keys(struct radius_msg *msg, const u8 *secret, size_t secret_len, const u8 *send_key, size_t send_key_len, const u8 *recv_key, size_t recv_key_len); +int radius_msg_add_wfa(struct radius_msg *msg, u8 subtype, const u8 *data, + size_t len); struct radius_attr_hdr * radius_msg_add_attr_user_password(struct radius_msg *msg, const u8 *data, size_t data_len, diff --git a/src/radius/radius_server.c b/src/radius/radius_server.c index 2904b2f7..5074b602 100644 --- a/src/radius/radius_server.c +++ b/src/radius/radius_server.c @@ -77,6 +77,8 @@ struct radius_session { u8 last_identifier; struct radius_msg *last_reply; u8 last_authenticator[16]; + + unsigned int remediation:1; }; /** @@ -307,6 +309,9 @@ struct radius_server_data { #ifdef CONFIG_RADIUS_TEST char *dump_msk_file; #endif /* CONFIG_RADIUS_TEST */ + + char *subscr_remediation_url; + u8 subscr_remediation_method; }; @@ -622,6 +627,34 @@ radius_server_encapsulate_eap(struct radius_server_data *data, } } +#ifdef CONFIG_HS20 + if (code == RADIUS_CODE_ACCESS_ACCEPT && sess->remediation && + data->subscr_remediation_url) { + u8 *buf; + size_t url_len = os_strlen(data->subscr_remediation_url); + buf = os_malloc(1 + url_len); + if (buf == NULL) { + radius_msg_free(msg); + return NULL; + } + buf[0] = data->subscr_remediation_method; + os_memcpy(&buf[1], data->subscr_remediation_url, url_len); + if (!radius_msg_add_wfa( + msg, RADIUS_VENDOR_ATTR_WFA_HS20_SUBSCR_REMEDIATION, + buf, 1 + url_len)) { + RADIUS_DEBUG("Failed to add WFA-HS20-SubscrRem"); + } + os_free(buf); + } else if (code == RADIUS_CODE_ACCESS_ACCEPT && sess->remediation) { + u8 buf[1]; + if (!radius_msg_add_wfa( + msg, RADIUS_VENDOR_ATTR_WFA_HS20_SUBSCR_REMEDIATION, + buf, 0)) { + RADIUS_DEBUG("Failed to add WFA-HS20-SubscrRem"); + } + } +#endif /* CONFIG_HS20 */ + if (radius_msg_copy_attr(msg, request, RADIUS_ATTR_PROXY_STATE) < 0) { RADIUS_DEBUG("Failed to copy Proxy-State attribute(s)"); radius_msg_free(msg); @@ -1444,6 +1477,11 @@ radius_server_init(struct radius_server_conf *conf) } } + if (conf->subscr_remediation_url) { + data->subscr_remediation_url = + os_strdup(conf->subscr_remediation_url); + } + #ifdef CONFIG_RADIUS_TEST if (conf->dump_msk_file) data->dump_msk_file = os_strdup(conf->dump_msk_file); @@ -1530,6 +1568,7 @@ void radius_server_deinit(struct radius_server_data *data) #ifdef CONFIG_RADIUS_TEST os_free(data->dump_msk_file); #endif /* CONFIG_RADIUS_TEST */ + os_free(data->subscr_remediation_url); os_free(data); } @@ -1682,9 +1721,13 @@ static int radius_server_get_eap_user(void *ctx, const u8 *identity, { struct radius_session *sess = ctx; struct radius_server_data *data = sess->server; + int ret; - return data->get_eap_user(data->conf_ctx, identity, identity_len, - phase2, user); + ret = data->get_eap_user(data->conf_ctx, identity, identity_len, + phase2, user); + if (ret == 0 && user) + sess->remediation = user->remediation; + return ret; } diff --git a/src/radius/radius_server.h b/src/radius/radius_server.h index 78f5fc23..e85d0090 100644 --- a/src/radius/radius_server.h +++ b/src/radius/radius_server.h @@ -209,6 +209,9 @@ struct radius_server_conf { #ifdef CONFIG_RADIUS_TEST const char *dump_msk_file; #endif /* CONFIG_RADIUS_TEST */ + + char *subscr_remediation_url; + u8 subscr_remediation_method; }; diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c index 4474c3b1..ba50263d 100644 --- a/src/rsn_supp/wpa.c +++ b/src/rsn_supp/wpa.c @@ -89,7 +89,10 @@ void wpa_sm_key_request(struct wpa_sm *sm, int error, int pairwise) int key_info, ver; u8 bssid[ETH_ALEN], *rbuf; - if (wpa_key_mgmt_ft(sm->key_mgmt) || wpa_key_mgmt_sha256(sm->key_mgmt)) + if (sm->key_mgmt == WPA_KEY_MGMT_OSEN) + ver = WPA_KEY_INFO_TYPE_AKM_DEFINED; + else if (wpa_key_mgmt_ft(sm->key_mgmt) || + wpa_key_mgmt_sha256(sm->key_mgmt)) ver = WPA_KEY_INFO_TYPE_AES_128_CMAC; else if (sm->pairwise_cipher != WPA_CIPHER_TKIP) ver = WPA_KEY_INFO_TYPE_HMAC_SHA1_AES; @@ -107,7 +110,8 @@ void wpa_sm_key_request(struct wpa_sm *sm, int error, int pairwise) if (rbuf == NULL) return; - reply->type = sm->proto == WPA_PROTO_RSN ? + reply->type = (sm->proto == WPA_PROTO_RSN || + sm->proto == WPA_PROTO_OSEN) ? EAPOL_KEY_TYPE_RSN : EAPOL_KEY_TYPE_WPA; key_info = WPA_KEY_INFO_REQUEST | ver; if (sm->ptk_set) @@ -231,7 +235,8 @@ static int wpa_supplicant_get_pmk(struct wpa_sm *sm, } if (abort_cached && wpa_key_mgmt_wpa_ieee8021x(sm->key_mgmt) && - !wpa_key_mgmt_ft(sm->key_mgmt)) { + !wpa_key_mgmt_ft(sm->key_mgmt) && sm->key_mgmt != WPA_KEY_MGMT_OSEN) + { /* Send EAPOL-Start to trigger full EAP authentication. */ u8 *buf; size_t buflen; @@ -325,11 +330,12 @@ int wpa_supplicant_send_2_of_4(struct wpa_sm *sm, const unsigned char *dst, return -1; } - reply->type = sm->proto == WPA_PROTO_RSN ? + reply->type = (sm->proto == WPA_PROTO_RSN || + sm->proto == WPA_PROTO_OSEN) ? EAPOL_KEY_TYPE_RSN : EAPOL_KEY_TYPE_WPA; WPA_PUT_BE16(reply->key_info, ver | WPA_KEY_INFO_KEY_TYPE | WPA_KEY_INFO_MIC); - if (sm->proto == WPA_PROTO_RSN) + if (sm->proto == WPA_PROTO_RSN || sm->proto == WPA_PROTO_OSEN) WPA_PUT_BE16(reply->key_length, 0); else os_memcpy(reply->key_length, key->key_length, 2); @@ -394,7 +400,7 @@ static void wpa_supplicant_process_1_of_4(struct wpa_sm *sm, os_memset(&ie, 0, sizeof(ie)); - if (sm->proto == WPA_PROTO_RSN) { + if (sm->proto == WPA_PROTO_RSN || sm->proto == WPA_PROTO_OSEN) { /* RSN: msg 1/4 should contain PMKID for the selected PMK */ const u8 *_buf = (const u8 *) (key + 1); size_t len = WPA_GET_BE16(key->key_data_length); @@ -561,7 +567,7 @@ static int wpa_supplicant_install_ptk(struct wpa_sm *sm, keylen = wpa_cipher_key_len(sm->pairwise_cipher); rsclen = wpa_cipher_rsc_len(sm->pairwise_cipher); - if (sm->proto == WPA_PROTO_RSN) { + if (sm->proto == WPA_PROTO_RSN || sm->proto == WPA_PROTO_OSEN) { key_rsc = null_rsc; } else { key_rsc = key->key_rsc; @@ -1033,12 +1039,13 @@ int wpa_supplicant_send_4_of_4(struct wpa_sm *sm, const unsigned char *dst, if (rbuf == NULL) return -1; - reply->type = sm->proto == WPA_PROTO_RSN ? + reply->type = (sm->proto == WPA_PROTO_RSN || + sm->proto == WPA_PROTO_OSEN) ? EAPOL_KEY_TYPE_RSN : EAPOL_KEY_TYPE_WPA; key_info &= WPA_KEY_INFO_SECURE; key_info |= ver | WPA_KEY_INFO_KEY_TYPE | WPA_KEY_INFO_MIC; WPA_PUT_BE16(reply->key_info, key_info); - if (sm->proto == WPA_PROTO_RSN) + if (sm->proto == WPA_PROTO_RSN || sm->proto == WPA_PROTO_OSEN) WPA_PUT_BE16(reply->key_length, 0); else os_memcpy(reply->key_length, key->key_length, 2); @@ -1320,12 +1327,13 @@ static int wpa_supplicant_send_2_of_2(struct wpa_sm *sm, if (rbuf == NULL) return -1; - reply->type = sm->proto == WPA_PROTO_RSN ? + reply->type = (sm->proto == WPA_PROTO_RSN || + sm->proto == WPA_PROTO_OSEN) ? EAPOL_KEY_TYPE_RSN : EAPOL_KEY_TYPE_WPA; key_info &= WPA_KEY_INFO_KEY_INDEX_MASK; key_info |= ver | WPA_KEY_INFO_MIC | WPA_KEY_INFO_SECURE; WPA_PUT_BE16(reply->key_info, key_info); - if (sm->proto == WPA_PROTO_RSN) + if (sm->proto == WPA_PROTO_RSN || sm->proto == WPA_PROTO_OSEN) WPA_PUT_BE16(reply->key_length, 0); else os_memcpy(reply->key_length, key->key_length, 2); @@ -1360,7 +1368,7 @@ static void wpa_supplicant_process_1_of_2(struct wpa_sm *sm, key_info = WPA_GET_BE16(key->key_info); keydatalen = WPA_GET_BE16(key->key_data_length); - if (sm->proto == WPA_PROTO_RSN) { + if (sm->proto == WPA_PROTO_RSN || sm->proto == WPA_PROTO_OSEN) { ret = wpa_supplicant_process_1_of_2_rsn(sm, (const u8 *) (key + 1), keydatalen, key_info, @@ -1480,7 +1488,8 @@ static int wpa_supplicant_decrypt_key_data(struct wpa_sm *sm, return -1; } } else if (ver == WPA_KEY_INFO_TYPE_HMAC_SHA1_AES || - ver == WPA_KEY_INFO_TYPE_AES_128_CMAC) { + ver == WPA_KEY_INFO_TYPE_AES_128_CMAC || + sm->key_mgmt == WPA_KEY_MGMT_OSEN) { u8 *buf; if (keydatalen % 8) { wpa_msg(sm->ctx->msg_ctx, MSG_WARNING, @@ -1662,13 +1671,22 @@ int wpa_sm_rx_eapol(struct wpa_sm *sm, const u8 *src_addr, #if defined(CONFIG_IEEE80211R) || defined(CONFIG_IEEE80211W) ver != WPA_KEY_INFO_TYPE_AES_128_CMAC && #endif /* CONFIG_IEEE80211R || CONFIG_IEEE80211W */ - ver != WPA_KEY_INFO_TYPE_HMAC_SHA1_AES) { + ver != WPA_KEY_INFO_TYPE_HMAC_SHA1_AES && + sm->key_mgmt != WPA_KEY_MGMT_OSEN) { wpa_msg(sm->ctx->msg_ctx, MSG_INFO, "WPA: Unsupported EAPOL-Key descriptor version %d", ver); goto out; } + if (sm->key_mgmt == WPA_KEY_MGMT_OSEN && + ver != WPA_KEY_INFO_TYPE_AKM_DEFINED) { + wpa_msg(sm->ctx->msg_ctx, MSG_INFO, + "OSEN: Unsupported EAPOL-Key descriptor version %d", + ver); + goto out; + } + #ifdef CONFIG_IEEE80211R if (wpa_key_mgmt_ft(sm->key_mgmt)) { /* IEEE 802.11r uses a new key_info type (AES-128-CMAC). */ @@ -1681,7 +1699,8 @@ int wpa_sm_rx_eapol(struct wpa_sm *sm, const u8 *src_addr, #endif /* CONFIG_IEEE80211R */ #ifdef CONFIG_IEEE80211W if (wpa_key_mgmt_sha256(sm->key_mgmt)) { - if (ver != WPA_KEY_INFO_TYPE_AES_128_CMAC) { + if (ver != WPA_KEY_INFO_TYPE_AES_128_CMAC && + sm->key_mgmt != WPA_KEY_MGMT_OSEN) { wpa_msg(sm->ctx->msg_ctx, MSG_INFO, "WPA: AP did not use the " "negotiated AES-128-CMAC"); @@ -1797,7 +1816,7 @@ int wpa_sm_rx_eapol(struct wpa_sm *sm, const u8 *src_addr, } extra_len = WPA_GET_BE16(key->key_data_length); - if (sm->proto == WPA_PROTO_RSN && + if ((sm->proto == WPA_PROTO_RSN || sm->proto == WPA_PROTO_OSEN) && (key_info & WPA_KEY_INFO_ENCR_KEY_DATA)) { if (wpa_supplicant_decrypt_key_data(sm, key, ver)) goto out; @@ -1851,7 +1870,8 @@ static u32 wpa_key_mgmt_suite(struct wpa_sm *sm) { switch (sm->key_mgmt) { case WPA_KEY_MGMT_IEEE8021X: - return (sm->proto == WPA_PROTO_RSN ? + return ((sm->proto == WPA_PROTO_RSN || + sm->proto == WPA_PROTO_OSEN) ? RSN_AUTH_KEY_MGMT_UNSPEC_802_1X : WPA_AUTH_KEY_MGMT_UNSPEC_802_1X); case WPA_KEY_MGMT_PSK: diff --git a/src/rsn_supp/wpa_ie.c b/src/rsn_supp/wpa_ie.c index e58bdc47..9c111839 100644 --- a/src/rsn_supp/wpa_ie.c +++ b/src/rsn_supp/wpa_ie.c @@ -222,6 +222,64 @@ static int wpa_gen_wpa_ie_rsn(u8 *rsn_ie, size_t rsn_ie_len, } +#ifdef CONFIG_HS20 +static int wpa_gen_wpa_ie_osen(u8 *wpa_ie, size_t wpa_ie_len, + int pairwise_cipher, int group_cipher, + int key_mgmt) +{ + u8 *pos, *len; + u32 suite; + + if (wpa_ie_len < 2 + 4 + RSN_SELECTOR_LEN + + 2 + RSN_SELECTOR_LEN + 2 + RSN_SELECTOR_LEN) + return -1; + + pos = wpa_ie; + *pos++ = WLAN_EID_VENDOR_SPECIFIC; + len = pos++; /* to be filled */ + WPA_PUT_BE24(pos, OUI_WFA); + pos += 3; + *pos++ = HS20_OSEN_OUI_TYPE; + + /* Group Data Cipher Suite */ + suite = wpa_cipher_to_suite(WPA_PROTO_RSN, group_cipher); + if (suite == 0) { + wpa_printf(MSG_WARNING, "Invalid group cipher (%d).", + group_cipher); + return -1; + } + RSN_SELECTOR_PUT(pos, suite); + pos += RSN_SELECTOR_LEN; + + /* Pairwise Cipher Suite Count and List */ + WPA_PUT_LE16(pos, 1); + pos += 2; + suite = wpa_cipher_to_suite(WPA_PROTO_RSN, pairwise_cipher); + if (suite == 0 || + (!wpa_cipher_valid_pairwise(pairwise_cipher) && + pairwise_cipher != WPA_CIPHER_NONE)) { + wpa_printf(MSG_WARNING, "Invalid pairwise cipher (%d).", + pairwise_cipher); + return -1; + } + RSN_SELECTOR_PUT(pos, suite); + pos += RSN_SELECTOR_LEN; + + /* AKM Suite Count and List */ + WPA_PUT_LE16(pos, 1); + pos += 2; + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_OSEN); + pos += RSN_SELECTOR_LEN; + + *len = pos - len - 1; + + WPA_ASSERT((size_t) (pos - wpa_ie) <= wpa_ie_len); + + return pos - wpa_ie; +} +#endif /* CONFIG_HS20 */ + + /** * wpa_gen_wpa_ie - Generate WPA/RSN IE based on current security policy * @sm: Pointer to WPA state machine data from wpa_sm_init() @@ -237,6 +295,13 @@ int wpa_gen_wpa_ie(struct wpa_sm *sm, u8 *wpa_ie, size_t wpa_ie_len) sm->group_cipher, sm->key_mgmt, sm->mgmt_group_cipher, sm); +#ifdef CONFIG_HS20 + else if (sm->proto == WPA_PROTO_OSEN) + return wpa_gen_wpa_ie_osen(wpa_ie, wpa_ie_len, + sm->pairwise_cipher, + sm->group_cipher, + sm->key_mgmt); +#endif /* CONFIG_HS20 */ else return wpa_gen_wpa_ie_wpa(wpa_ie, wpa_ie_len, sm->pairwise_cipher, diff --git a/src/utils/os.h b/src/utils/os.h index 2e2350a7..d63ac294 100644 --- a/src/utils/os.h +++ b/src/utils/os.h @@ -549,6 +549,21 @@ static inline void * os_realloc_array(void *ptr, size_t nmemb, size_t size) return os_realloc(ptr, nmemb * size); } +/** + * os_remove_in_array - Remove a member from an array by index + * @ptr: Pointer to the array + * @nmemb: Current member count of the array + * @size: The size per member of the array + * @idx: Index of the member to be removed + */ +static inline void os_remove_in_array(void *ptr, size_t nmemb, size_t size, + size_t idx) +{ + if (idx < nmemb - 1) + os_memmove(((unsigned char *) ptr) + idx * size, + ((unsigned char *) ptr) + (idx + 1) * size, + (nmemb - idx - 1) * size); +} /** * os_strlcpy - Copy a string with size bound and NUL-termination diff --git a/src/wps/wps_upnp_web.c b/src/wps/wps_upnp_web.c index 11386d88..2a3b6360 100644 --- a/src/wps/wps_upnp_web.c +++ b/src/wps/wps_upnp_web.c @@ -324,8 +324,6 @@ static void web_connection_parse_get(struct upnp_wps_device_sm *sm, * It is not required that filenames be case insensitive but it is * allowed and cannot hurt here. */ - if (filename == NULL) - filename = "(null)"; /* just in case */ if (os_strcasecmp(filename, UPNP_WPS_DEVICE_XML_FILE) == 0) { wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for device XML"); req = GET_DEVICE_XML_FILE; @@ -1173,7 +1171,6 @@ static void web_connection_parse_unsubscribe(struct upnp_wps_device_sm *sm, ..... } #endif - /* SID is only for renewal */ match = "SID:"; match_len = os_strlen(match); if (os_strncasecmp(h, match, match_len) == 0) { @@ -1196,6 +1193,20 @@ static void web_connection_parse_unsubscribe(struct upnp_wps_device_sm *sm, got_uuid = 1; continue; } + + match = "NT:"; + match_len = os_strlen(match); + if (os_strncasecmp(h, match, match_len) == 0) { + ret = HTTP_BAD_REQUEST; + goto send_msg; + } + + match = "CALLBACK:"; + match_len = os_strlen(match); + if (os_strncasecmp(h, match, match_len) == 0) { + ret = HTTP_BAD_REQUEST; + goto send_msg; + } } if (got_uuid) { @@ -1209,6 +1220,10 @@ static void web_connection_parse_unsubscribe(struct upnp_wps_device_sm *sm, sa->domain_and_port : "-null-"); dl_list_del(&s->list); subscription_destroy(s); + } else { + wpa_printf(MSG_INFO, "WPS UPnP: Could not find matching subscription to unsubscribe"); + ret = HTTP_PRECONDITION_FAILED; + goto send_msg; } } else { wpa_printf(MSG_INFO, "WPS UPnP: Unsubscribe fails (not " diff --git a/wpa_supplicant/Android.mk b/wpa_supplicant/Android.mk index 2880b2da..c745cb20 100644 --- a/wpa_supplicant/Android.mk +++ b/wpa_supplicant/Android.mk @@ -280,6 +280,7 @@ ifdef CONFIG_HS20 OBJS += hs20_supplicant.c L_CFLAGS += -DCONFIG_HS20 CONFIG_INTERWORKING=y +NEED_AES_OMAC1=y endif ifdef CONFIG_INTERWORKING diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile index 7b556e84..2b8cb93e 100644 --- a/wpa_supplicant/Makefile +++ b/wpa_supplicant/Makefile @@ -286,6 +286,7 @@ ifdef CONFIG_HS20 OBJS += hs20_supplicant.o CFLAGS += -DCONFIG_HS20 CONFIG_INTERWORKING=y +NEED_AES_OMAC1=y endif ifdef CONFIG_INTERWORKING @@ -1267,6 +1268,11 @@ endif ifeq ($(CONFIG_CTRL_IFACE), udp) CFLAGS += -DCONFIG_CTRL_IFACE_UDP endif +ifeq ($(CONFIG_CTRL_IFACE), udp6) +CONFIG_CTRL_IFACE=udp +CFLAGS += -DCONFIG_CTRL_IFACE_UDP +CFLAGS += -DCONFIG_CTRL_IFACE_UDP_IPV6 +endif ifeq ($(CONFIG_CTRL_IFACE), named_pipe) CFLAGS += -DCONFIG_CTRL_IFACE_NAMED_PIPE endif @@ -1275,6 +1281,12 @@ CONFIG_CTRL_IFACE=udp CFLAGS += -DCONFIG_CTRL_IFACE_UDP CFLAGS += -DCONFIG_CTRL_IFACE_UDP_REMOTE endif +ifeq ($(CONFIG_CTRL_IFACE), udp6-remote) +CONFIG_CTRL_IFACE=udp +CFLAGS += -DCONFIG_CTRL_IFACE_UDP +CFLAGS += -DCONFIG_CTRL_IFACE_UDP_REMOTE +CFLAGS += -DCONFIG_CTRL_IFACE_UDP_IPV6 +endif OBJS += ctrl_iface.o ctrl_iface_$(CONFIG_CTRL_IFACE).o endif diff --git a/wpa_supplicant/README-HS20 b/wpa_supplicant/README-HS20 index ad29ef77..b6f06735 100644 --- a/wpa_supplicant/README-HS20 +++ b/wpa_supplicant/README-HS20 @@ -213,6 +213,63 @@ Credentials can be pre-configured for automatic network selection: # matching with the network. Multiple entries can be used to specify more # than one SSID. # +# roaming_partner: Roaming partner information +# This optional field can be used to configure preferences between roaming +# partners. The field is a string in following format: +# <FQDN>,<0/1 exact match>,<priority>,<* or country code> +# (non-exact match means any subdomain matches the entry; priority is in +# 0..255 range with 0 being the highest priority) +# +# update_identifier: PPS MO ID +# (Hotspot 2.0 PerProviderSubscription/UpdateIdentifier) +# +# provisioning_sp: FQDN of the SP that provisioned the credential +# This optional field can be used to keep track of the SP that provisioned +# the credential to find the PPS MO (./Wi-Fi/<provisioning_sp>). +# +# sp_priority: Credential priority within a provisioning SP +# This is the priority of the credential among all credentials +# provisionined by the same SP (i.e., for entries that have identical +# provisioning_sp value). The range of this priority is 0-255 with 0 +# being the highest and 255 the lower priority. +# +# Minimum backhaul threshold (PPS/<X+>/Policy/MinBackhauldThreshold/*) +# These fields can be used to specify minimum download/upload backhaul +# bandwidth that is preferred for the credential. This constraint is +# ignored if the AP does not advertise WAN Metrics information or if the +# limit would prevent any connection. Values are in kilobits per second. +# min_dl_bandwidth_home +# min_ul_bandwidth_home +# min_dl_bandwidth_roaming +# min_ul_bandwidth_roaming +# +# max_bss_load: Maximum BSS Load Channel Utilization (1..255) +# (PPS/<X+>/Policy/MaximumBSSLoadValue) +# This value is used as the maximum channel utilization for network +# selection purposes for home networks. If the AP does not advertise +# BSS Load or if the limit would prevent any connection, this constraint +# will be ignored. +# +# req_conn_capab: Required connection capability +# (PPS/<X+>/Policy/RequiredProtoPortTuple) +# This value is used to configure set of required protocol/port pairs that +# a roaming network shall support (include explicitly in Connection +# Capability ANQP element). This constraint is ignored if the AP does not +# advertise Connection Capability or if this constraint would prevent any +# network connection. This policy is not used in home networks. +# Format: <protocol>[:<comma-separated list of ports] +# Multiple entries can be used to list multiple requirements. +# For example, number of common TCP protocols: +# req_conn_capab=6,22,80,443 +# For example, IPSec/IKE: +# req_conn_capab=17:500 +# req_conn_capab=50 +# +# ocsp: Whether to use/require OCSP to check server certificate +# 0 = do not use OCSP stapling (TLS certificate status extension) +# 1 = try to use OCSP stapling, but not require response +# 2 = require valid OCSP stapling response +# # for example: # #cred={ diff --git a/wpa_supplicant/bss.c b/wpa_supplicant/bss.c index 9ea69033..482fc64f 100644 --- a/wpa_supplicant/bss.c +++ b/wpa_supplicant/bss.c @@ -98,6 +98,7 @@ static struct wpa_bss_anqp * wpa_bss_anqp_clone(struct wpa_bss_anqp *anqp) ANQP_DUP(hs20_wan_metrics); ANQP_DUP(hs20_connection_capability); ANQP_DUP(hs20_operating_class); + ANQP_DUP(hs20_osu_providers_list); #endif /* CONFIG_HS20 */ #undef ANQP_DUP @@ -166,6 +167,7 @@ static void wpa_bss_anqp_free(struct wpa_bss_anqp *anqp) wpabuf_free(anqp->hs20_wan_metrics); wpabuf_free(anqp->hs20_connection_capability); wpabuf_free(anqp->hs20_operating_class); + wpabuf_free(anqp->hs20_osu_providers_list); #endif /* CONFIG_HS20 */ os_free(anqp); diff --git a/wpa_supplicant/bss.h b/wpa_supplicant/bss.h index 4deeb5f5..30df7cac 100644 --- a/wpa_supplicant/bss.h +++ b/wpa_supplicant/bss.h @@ -39,6 +39,7 @@ struct wpa_bss_anqp { struct wpabuf *hs20_wan_metrics; struct wpabuf *hs20_connection_capability; struct wpabuf *hs20_operating_class; + struct wpabuf *hs20_osu_providers_list; #endif /* CONFIG_HS20 */ }; diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c index 2dd70545..da9580e9 100644 --- a/wpa_supplicant/config.c +++ b/wpa_supplicant/config.c @@ -405,6 +405,8 @@ static int wpa_config_parse_proto(const struct parse_data *data, else if (os_strcmp(start, "RSN") == 0 || os_strcmp(start, "WPA2") == 0) val |= WPA_PROTO_RSN; + else if (os_strcmp(start, "OSEN") == 0) + val |= WPA_PROTO_OSEN; else { wpa_printf(MSG_ERROR, "Line %d: invalid proto '%s'", line, start); @@ -516,6 +518,10 @@ static int wpa_config_parse_key_mgmt(const struct parse_data *data, else if (os_strcmp(start, "FT-SAE") == 0) val |= WPA_KEY_MGMT_FT_SAE; #endif /* CONFIG_SAE */ +#ifdef CONFIG_HS20 + else if (os_strcmp(start, "OSEN") == 0) + val |= WPA_KEY_MGMT_OSEN; +#endif /* CONFIG_HS20 */ else { wpa_printf(MSG_ERROR, "Line %d: invalid key_mgmt '%s'", line, start); @@ -1923,6 +1929,12 @@ void wpa_config_free_cred(struct wpa_cred *cred) os_free(cred->phase1); os_free(cred->phase2); os_free(cred->excluded_ssid); + os_free(cred->roaming_partner); + os_free(cred->provisioning_sp); + for (i = 0; i < cred->num_req_conn_capab; i++) + os_free(cred->req_conn_capab_port[i]); + os_free(cred->req_conn_capab_port); + os_free(cred->req_conn_capab_proto); os_free(cred); } @@ -1998,6 +2010,7 @@ void wpa_config_free(struct wpa_config *config) os_free(config->ext_password_backend); os_free(config->sae_groups); wpabuf_free(config->ap_vendor_elements); + os_free(config->osu_dir); os_free(config); } @@ -2393,6 +2406,69 @@ void wpa_config_update_psk(struct wpa_ssid *ssid) } +static int wpa_config_set_cred_req_conn_capab(struct wpa_cred *cred, + const char *value) +{ + u8 *proto; + int **port; + int *ports, *nports; + const char *pos; + unsigned int num_ports; + + proto = os_realloc_array(cred->req_conn_capab_proto, + cred->num_req_conn_capab + 1, sizeof(u8)); + if (proto == NULL) + return -1; + cred->req_conn_capab_proto = proto; + + port = os_realloc_array(cred->req_conn_capab_port, + cred->num_req_conn_capab + 1, sizeof(int *)); + if (port == NULL) + return -1; + cred->req_conn_capab_port = port; + + proto[cred->num_req_conn_capab] = atoi(value); + + pos = os_strchr(value, ':'); + if (pos == NULL) { + port[cred->num_req_conn_capab] = NULL; + cred->num_req_conn_capab++; + return 0; + } + pos++; + + ports = NULL; + num_ports = 0; + + while (*pos) { + nports = os_realloc_array(ports, num_ports + 1, sizeof(int)); + if (nports == NULL) { + os_free(ports); + return -1; + } + ports = nports; + ports[num_ports++] = atoi(pos); + + pos = os_strchr(pos, ','); + if (pos == NULL) + break; + pos++; + } + + nports = os_realloc_array(ports, num_ports + 1, sizeof(int)); + if (nports == NULL) { + os_free(ports); + return -1; + } + ports = nports; + ports[num_ports] = -1; + + port[cred->num_req_conn_capab] = ports; + cred->num_req_conn_capab++; + return 0; +} + + int wpa_config_set_cred(struct wpa_cred *cred, const char *var, const char *value, int line) { @@ -2409,6 +2485,14 @@ int wpa_config_set_cred(struct wpa_cred *cred, const char *var, return 0; } + if (os_strcmp(var, "sp_priority") == 0) { + int prio = atoi(value); + if (prio < 0 || prio > 255) + return -1; + cred->sp_priority = prio; + return 0; + } + if (os_strcmp(var, "pcsc") == 0) { cred->pcsc = atoi(value); return 0; @@ -2439,6 +2523,44 @@ int wpa_config_set_cred(struct wpa_cred *cred, const char *var, return 0; } + if (os_strcmp(var, "update_identifier") == 0) { + cred->update_identifier = atoi(value); + return 0; + } + + if (os_strcmp(var, "min_dl_bandwidth_home") == 0) { + cred->min_dl_bandwidth_home = atoi(value); + return 0; + } + + if (os_strcmp(var, "min_ul_bandwidth_home") == 0) { + cred->min_ul_bandwidth_home = atoi(value); + return 0; + } + + if (os_strcmp(var, "min_dl_bandwidth_roaming") == 0) { + cred->min_dl_bandwidth_roaming = atoi(value); + return 0; + } + + if (os_strcmp(var, "min_ul_bandwidth_roaming") == 0) { + cred->min_ul_bandwidth_roaming = atoi(value); + return 0; + } + + if (os_strcmp(var, "max_bss_load") == 0) { + cred->max_bss_load = atoi(value); + return 0; + } + + if (os_strcmp(var, "req_conn_capab") == 0) + return wpa_config_set_cred_req_conn_capab(cred, value); + + if (os_strcmp(var, "ocsp") == 0) { + cred->ocsp = atoi(value); + return 0; + } + val = wpa_config_parse_string(value, &len); if (val == NULL) { wpa_printf(MSG_ERROR, "Line %d: invalid field '%s' string " @@ -2590,6 +2712,69 @@ int wpa_config_set_cred(struct wpa_cred *cred, const char *var, return 0; } + if (os_strcmp(var, "roaming_partner") == 0) { + struct roaming_partner *p; + char *pos; + + p = os_realloc_array(cred->roaming_partner, + cred->num_roaming_partner + 1, + sizeof(struct roaming_partner)); + if (p == NULL) { + os_free(val); + return -1; + } + cred->roaming_partner = p; + + p = &cred->roaming_partner[cred->num_roaming_partner]; + + pos = os_strchr(val, ','); + if (pos == NULL) { + os_free(val); + return -1; + } + *pos++ = '\0'; + if (pos - val - 1 >= (int) sizeof(p->fqdn)) { + os_free(val); + return -1; + } + os_memcpy(p->fqdn, val, pos - val); + + p->exact_match = atoi(pos); + + pos = os_strchr(pos, ','); + if (pos == NULL) { + os_free(val); + return -1; + } + *pos++ = '\0'; + + p->priority = atoi(pos); + + pos = os_strchr(pos, ','); + if (pos == NULL) { + os_free(val); + return -1; + } + *pos++ = '\0'; + + if (os_strlen(pos) >= sizeof(p->country)) { + os_free(val); + return -1; + } + os_memcpy(p->country, pos, os_strlen(pos) + 1); + + cred->num_roaming_partner++; + os_free(val); + + return 0; + } + + if (os_strcmp(var, "provisioning_sp") == 0) { + os_free(cred->provisioning_sp); + cred->provisioning_sp = val; + return 0; + } + if (line) { wpa_printf(MSG_ERROR, "Line %d: unknown cred field '%s'.", line, var); @@ -3401,6 +3586,7 @@ static const struct global_parse_data global_fields[] = { { INT(scan_cur_freq), 0 }, { INT(sched_scan_interval), 0 }, { INT(tdls_external_control), 0}, + { STR(osu_dir), 0 }, }; #undef FUNC diff --git a/wpa_supplicant/config.h b/wpa_supplicant/config.h index e7bdaa5a..de439706 100644 --- a/wpa_supplicant/config.h +++ b/wpa_supplicant/config.h @@ -236,6 +236,58 @@ struct wpa_cred { size_t ssid_len; } *excluded_ssid; size_t num_excluded_ssid; + + struct roaming_partner { + char fqdn[128]; + int exact_match; + u8 priority; + char country[3]; + } *roaming_partner; + size_t num_roaming_partner; + + int update_identifier; + + /** + * provisioning_sp - FQDN of the SP that provisioned the credential + */ + char *provisioning_sp; + + /** + * sp_priority - Credential priority within a provisioning SP + * + * This is the priority of the credential among all credentials + * provisionined by the same SP (i.e., for entries that have identical + * provisioning_sp value). The range of this priority is 0-255 with 0 + * being the highest and 255 the lower priority. + */ + int sp_priority; + + unsigned int min_dl_bandwidth_home; + unsigned int min_ul_bandwidth_home; + unsigned int min_dl_bandwidth_roaming; + unsigned int min_ul_bandwidth_roaming; + + /** + * max_bss_load - Maximum BSS Load Channel Utilization (1..255) + * This value is used as the maximum channel utilization for network + * selection purposes for home networks. If the AP does not advertise + * BSS Load or if the limit would prevent any connection, this + * constraint will be ignored. + */ + unsigned int max_bss_load; + + unsigned int num_req_conn_capab; + u8 *req_conn_capab_proto; + int **req_conn_capab_port; + + /** + * ocsp - Whether to use/require OCSP to check server certificate + * + * 0 = do not use OCSP stapling (TLS certificate status extension) + * 1 = try to use OCSP stapling, but not require response + * 2 = require valid OCSP stapling response + */ + int ocsp; }; @@ -953,6 +1005,15 @@ struct wpa_config { u8 ip_addr_mask[4]; u8 ip_addr_start[4]; u8 ip_addr_end[4]; + + /** + * osu_dir - OSU provider information directory + * + * If set, allow FETCH_OSU control interface command to be used to fetch + * OSU provider information into all APs and store the results in this + * directory. + */ + char *osu_dir; }; diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c index 6312a77b..850a6cf9 100644 --- a/wpa_supplicant/config_file.c +++ b/wpa_supplicant/config_file.c @@ -796,6 +796,41 @@ static void wpa_config_write_cred(FILE *f, struct wpa_cred *cred) fprintf(f, "\n"); } } + if (cred->roaming_partner) { + for (i = 0; i < cred->num_roaming_partner; i++) { + struct roaming_partner *p = &cred->roaming_partner[i]; + fprintf(f, "\troaming_partner=\"%s,%d,%u,%s\"\n", + p->fqdn, p->exact_match, p->priority, + p->country); + } + } + if (cred->update_identifier) + fprintf(f, "\tupdate_identifier=%d\n", cred->update_identifier); + + if (cred->provisioning_sp) + fprintf(f, "\tprovisioning_sp=%s\n", cred->provisioning_sp); + if (cred->sp_priority) + fprintf(f, "\tsp_priority=%d\n", cred->sp_priority); + + if (cred->min_dl_bandwidth_home) + fprintf(f, "\tmin_dl_bandwidth_home=%u\n", + cred->min_dl_bandwidth_home); + if (cred->min_ul_bandwidth_home) + fprintf(f, "\tmin_ul_bandwidth_home=%u\n", + cred->min_ul_bandwidth_home); + if (cred->min_dl_bandwidth_roaming) + fprintf(f, "\tmin_dl_bandwidth_roaming=%u\n", + cred->min_dl_bandwidth_roaming); + if (cred->min_ul_bandwidth_roaming) + fprintf(f, "\tmin_ul_bandwidth_roaming=%u\n", + cred->min_ul_bandwidth_roaming); + + if (cred->max_bss_load) + fprintf(f, "\tmax_bss_load=%u\n", + cred->max_bss_load); + + if (cred->ocsp) + fprintf(f, "\tocsp=%d\n", cred->ocsp); } diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index e95b55bf..9f5d4f40 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -1554,6 +1554,9 @@ static int wpa_supplicant_ctrl_iface_status(struct wpa_supplicant *wpa_s, { char *pos, *end, tmp[30]; int res, verbose, wps, ret; +#ifdef CONFIG_HS20 + const u8 *hs20; +#endif /* CONFIG_HS20 */ if (os_strcmp(params, "-DRIVER") == 0) return wpa_drv_status(wpa_s, buf, buflen); @@ -1692,10 +1695,16 @@ static int wpa_supplicant_ctrl_iface_status(struct wpa_supplicant *wpa_s, #ifdef CONFIG_HS20 if (wpa_s->current_bss && - wpa_bss_get_vendor_ie(wpa_s->current_bss, HS20_IE_VENDOR_TYPE) && + (hs20 = wpa_bss_get_vendor_ie(wpa_s->current_bss, + HS20_IE_VENDOR_TYPE)) && wpa_s->wpa_proto == WPA_PROTO_RSN && wpa_key_mgmt_wpa_ieee8021x(wpa_s->key_mgmt)) { - ret = os_snprintf(pos, end - pos, "hs20=1\n"); + int release = 1; + if (hs20[1] >= 5) { + u8 rel_num = (hs20[6] & 0xf0) >> 4; + release = rel_num + 1; + } + ret = os_snprintf(pos, end - pos, "hs20=%d\n", release); if (ret < 0 || ret >= end - pos) return pos - buf; pos += ret; @@ -1711,15 +1720,38 @@ static int wpa_supplicant_ctrl_iface_status(struct wpa_supplicant *wpa_s, if (wpa_s->current_ssid->parent_cred != cred) continue; - for (i = 0; cred->domain && i < cred->num_domain; i++) { + if (cred->provisioning_sp) { ret = os_snprintf(pos, end - pos, - "home_sp=%s\n", - cred->domain[i]); + "provisioning_sp=%s\n", + cred->provisioning_sp); if (ret < 0 || ret >= end - pos) return pos - buf; pos += ret; } + if (!cred->domain) + goto no_domain; + + i = 0; + if (wpa_s->current_bss && wpa_s->current_bss->anqp) { + struct wpabuf *names = + wpa_s->current_bss->anqp->domain_name; + for (i = 0; names && i < cred->num_domain; i++) + { + if (domain_name_list_contains( + names, cred->domain[i], 1)) + break; + } + if (i == cred->num_domain) + i = 0; /* show first entry by default */ + } + ret = os_snprintf(pos, end - pos, "home_sp=%s\n", + cred->domain[i]); + if (ret < 0 || ret >= end - pos) + return pos - buf; + pos += ret; + + no_domain: if (wpa_s->current_bss == NULL || wpa_s->current_bss->anqp == NULL) res = -1; @@ -2686,7 +2718,8 @@ static int wpa_supplicant_ctrl_iface_remove_cred(struct wpa_supplicant *wpa_s, int id; struct wpa_cred *cred, *prev; - /* cmd: "<cred id>", "all", or "sp_fqdn=<FQDN>" */ + /* cmd: "<cred id>", "all", "sp_fqdn=<FQDN>", or + * "provisioning_sp=<FQDN> */ if (os_strcmp(cmd, "all") == 0) { wpa_printf(MSG_DEBUG, "CTRL_IFACE: REMOVE_CRED all"); cred = wpa_s->conf->cred; @@ -2719,6 +2752,20 @@ static int wpa_supplicant_ctrl_iface_remove_cred(struct wpa_supplicant *wpa_s, return 0; } + if (os_strncmp(cmd, "provisioning_sp=", 16) == 0) { + wpa_printf(MSG_DEBUG, "CTRL_IFACE: REMOVE_CRED provisioning SP FQDN '%s'", + cmd + 16); + cred = wpa_s->conf->cred; + while (cred) { + prev = cred; + cred = cred->next; + if (prev->provisioning_sp && + os_strcmp(prev->provisioning_sp, cmd + 16) == 0) + wpas_ctrl_remove_cred(wpa_s, prev); + } + return 0; + } + id = atoi(cmd); wpa_printf(MSG_DEBUG, "CTRL_IFACE: REMOVE_CRED id=%d", id); @@ -3518,6 +3565,10 @@ static int print_bss_info(struct wpa_supplicant *wpa_s, struct wpa_bss *bss, anqp->hs20_wan_metrics); pos = anqp_add_hex(pos, end, "hs20_connection_capability", anqp->hs20_connection_capability); + pos = anqp_add_hex(pos, end, "hs20_operating_class", + anqp->hs20_operating_class); + pos = anqp_add_hex(pos, end, "hs20_osu_providers_list", + anqp->hs20_osu_providers_list); #endif /* CONFIG_HS20 */ } #endif /* CONFIG_INTERWORKING */ @@ -5169,6 +5220,26 @@ static int hs20_get_nai_home_realm_list(struct wpa_supplicant *wpa_s, return ret; } + +static int hs20_icon_request(struct wpa_supplicant *wpa_s, char *cmd) +{ + u8 dst_addr[ETH_ALEN]; + int used; + char *icon; + + used = hwaddr_aton2(cmd, dst_addr); + if (used < 0) + return -1; + + while (cmd[used] == ' ') + used++; + icon = &cmd[used]; + + wpa_s->fetch_osu_icon_in_progress = 0; + return hs20_anqp_send_req(wpa_s, dst_addr, BIT(HS20_STYPE_ICON_REQUEST), + (u8 *) icon, os_strlen(icon)); +} + #endif /* CONFIG_HS20 */ @@ -5454,7 +5525,6 @@ static void wpa_supplicant_ctrl_iface_flush(struct wpa_supplicant *wpa_s) wpa_config_flush_blobs(wpa_s->conf); wpa_s->conf->auto_interworking = 0; wpa_s->conf->okc = 0; - wpa_s->conf->pmf = 0; wpa_sm_set_param(wpa_s->wpa, RSNA_PMK_LIFETIME, 43200); wpa_sm_set_param(wpa_s->wpa, RSNA_PMK_REAUTH_THRESHOLD, 70); @@ -5462,6 +5532,12 @@ static void wpa_supplicant_ctrl_iface_flush(struct wpa_supplicant *wpa_s) eapol_sm_notify_logoff(wpa_s->eapol, FALSE); radio_remove_works(wpa_s, NULL, 1); + + wpa_s->next_ssid = NULL; + +#ifdef CONFIG_INTERWORKING + hs20_cancel_fetch_osu(wpa_s); +#endif /* CONFIG_INTERWORKING */ } @@ -6093,6 +6169,14 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s, } else if (os_strncmp(buf, "HS20_GET_NAI_HOME_REALM_LIST ", 29) == 0) { if (hs20_get_nai_home_realm_list(wpa_s, buf + 29) < 0) reply_len = -1; + } else if (os_strncmp(buf, "HS20_ICON_REQUEST ", 18) == 0) { + if (hs20_icon_request(wpa_s, buf + 18) < 0) + reply_len = -1; + } else if (os_strcmp(buf, "FETCH_OSU") == 0) { + if (hs20_fetch_osu(wpa_s) < 0) + reply_len = -1; + } else if (os_strcmp(buf, "CANCEL_FETCH_OSU") == 0) { + hs20_cancel_fetch_osu(wpa_s); #endif /* CONFIG_HS20 */ } else if (os_strncmp(buf, WPA_CTRL_RSP, os_strlen(WPA_CTRL_RSP)) == 0) { diff --git a/wpa_supplicant/ctrl_iface.h b/wpa_supplicant/ctrl_iface.h index b0dec53f..d54cc076 100644 --- a/wpa_supplicant/ctrl_iface.h +++ b/wpa_supplicant/ctrl_iface.h @@ -32,7 +32,7 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s, char *buf, size_t *resp_len); /** - * wpa_supplicant_ctrl_iface_process - Process global ctrl_iface command + * wpa_supplicant_global_ctrl_iface_process - Process global ctrl_iface command * @global: Pointer to global data from wpa_supplicant_init() * @buf: Received command buffer (nul terminated string) * @resp_len: Variable to be set to the response length diff --git a/wpa_supplicant/ctrl_iface_udp.c b/wpa_supplicant/ctrl_iface_udp.c index 8c09ba13..9d0674de 100644 --- a/wpa_supplicant/ctrl_iface_udp.c +++ b/wpa_supplicant/ctrl_iface_udp.c @@ -30,7 +30,11 @@ */ struct wpa_ctrl_dst { struct wpa_ctrl_dst *next; +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + struct sockaddr_in6 addr; +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ struct sockaddr_in addr; +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ socklen_t addrlen; int debug_level; int errors; @@ -51,38 +55,68 @@ static void wpa_supplicant_ctrl_iface_send(struct ctrl_iface_priv *priv, static int wpa_supplicant_ctrl_iface_attach(struct ctrl_iface_priv *priv, +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + struct sockaddr_in6 *from, +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ struct sockaddr_in *from, +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ socklen_t fromlen) { struct wpa_ctrl_dst *dst; +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + char addr[INET6_ADDRSTRLEN]; +#endif /* CONFIG_UDP_IPV6 */ dst = os_zalloc(sizeof(*dst)); if (dst == NULL) return -1; - os_memcpy(&dst->addr, from, sizeof(struct sockaddr_in)); + os_memcpy(&dst->addr, from, sizeof(*from)); dst->addrlen = fromlen; dst->debug_level = MSG_INFO; dst->next = priv->ctrl_dst; priv->ctrl_dst = dst; +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + wpa_printf(MSG_DEBUG, "CTRL_IFACE monitor attached %s:%d", + inet_ntop(AF_INET6, &from->sin6_addr, addr, sizeof(*from)), + ntohs(from->sin6_port)); +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ wpa_printf(MSG_DEBUG, "CTRL_IFACE monitor attached %s:%d", inet_ntoa(from->sin_addr), ntohs(from->sin_port)); +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ return 0; } static int wpa_supplicant_ctrl_iface_detach(struct ctrl_iface_priv *priv, +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + struct sockaddr_in6 *from, +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ struct sockaddr_in *from, +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ socklen_t fromlen) { struct wpa_ctrl_dst *dst, *prev = NULL; +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + char addr[INET6_ADDRSTRLEN]; +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ dst = priv->ctrl_dst; while (dst) { +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + if (from->sin6_port == dst->addr.sin6_port && + !os_memcmp(&from->sin6_addr, &dst->addr.sin6_addr, + sizeof(from->sin6_addr))) { + wpa_printf(MSG_DEBUG, "CTRL_IFACE monitor detached %s:%d", + inet_ntop(AF_INET6, &from->sin6_addr, addr, + sizeof(*from)), + ntohs(from->sin6_port)); +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ if (from->sin_addr.s_addr == dst->addr.sin_addr.s_addr && from->sin_port == dst->addr.sin_port) { wpa_printf(MSG_DEBUG, "CTRL_IFACE monitor detached " "%s:%d", inet_ntoa(from->sin_addr), ntohs(from->sin_port)); +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ if (prev == NULL) priv->ctrl_dst = dst->next; else @@ -98,21 +132,38 @@ static int wpa_supplicant_ctrl_iface_detach(struct ctrl_iface_priv *priv, static int wpa_supplicant_ctrl_iface_level(struct ctrl_iface_priv *priv, +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + struct sockaddr_in6 *from, +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ struct sockaddr_in *from, +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ socklen_t fromlen, char *level) { struct wpa_ctrl_dst *dst; +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + char addr[INET6_ADDRSTRLEN]; +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ wpa_printf(MSG_DEBUG, "CTRL_IFACE LEVEL %s", level); dst = priv->ctrl_dst; while (dst) { +#if CONFIG_CTRL_IFACE_UDP_IPV6 + if (from->sin6_port == dst->addr.sin6_port && + !os_memcmp(&from->sin6_addr, &dst->addr.sin6_addr, + sizeof(from->sin6_addr))) { + wpa_printf(MSG_DEBUG, "CTRL_IFACE changed monitor level %s:%d", + inet_ntop(AF_INET6, &from->sin6_addr, addr, + sizeof(*from)), + ntohs(from->sin6_port)); +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ if (from->sin_addr.s_addr == dst->addr.sin_addr.s_addr && from->sin_port == dst->addr.sin_port) { wpa_printf(MSG_DEBUG, "CTRL_IFACE changed monitor " "level %s:%d", inet_ntoa(from->sin_addr), ntohs(from->sin_port)); +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ dst->debug_level = atoi(level); return 0; } @@ -150,7 +201,14 @@ static void wpa_supplicant_ctrl_iface_receive(int sock, void *eloop_ctx, struct ctrl_iface_priv *priv = sock_ctx; char buf[256], *pos; int res; +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + struct sockaddr_in6 from; +#ifndef CONFIG_CTRL_IFACE_UDP_REMOTE + char addr[INET6_ADDRSTRLEN]; +#endif /* CONFIG_CTRL_IFACE_UDP_REMOTE */ +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ struct sockaddr_in from; +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ socklen_t fromlen = sizeof(from); char *reply = NULL; size_t reply_len = 0; @@ -165,6 +223,13 @@ static void wpa_supplicant_ctrl_iface_receive(int sock, void *eloop_ctx, } #ifndef CONFIG_CTRL_IFACE_UDP_REMOTE +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + inet_ntop(AF_INET6, &from.sin6_addr, addr, sizeof(from)); + if (os_strcmp(addr, "::1")) { + wpa_printf(MSG_DEBUG, "CTRL: Drop packet from unexpected source %s", + addr); + } +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ if (from.sin_addr.s_addr != htonl((127 << 24) | 1)) { /* * The OS networking stack is expected to drop this kind of @@ -176,6 +241,7 @@ static void wpa_supplicant_ctrl_iface_receive(int sock, void *eloop_ctx, "source %s", inet_ntoa(from.sin_addr)); return; } +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ #endif /* CONFIG_CTRL_IFACE_UDP_REMOTE */ buf[res] = '\0'; @@ -269,8 +335,14 @@ struct ctrl_iface_priv * wpa_supplicant_ctrl_iface_init(struct wpa_supplicant *wpa_s) { struct ctrl_iface_priv *priv; - struct sockaddr_in addr; int port = WPA_CTRL_IFACE_PORT; +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + struct sockaddr_in6 addr; + int domain = PF_INET6; +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ + struct sockaddr_in addr; + int domain = PF_INET; +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ priv = os_zalloc(sizeof(*priv)); if (priv == NULL) @@ -282,21 +354,34 @@ wpa_supplicant_ctrl_iface_init(struct wpa_supplicant *wpa_s) if (wpa_s->conf->ctrl_interface == NULL) return priv; - priv->sock = socket(PF_INET, SOCK_DGRAM, 0); + priv->sock = socket(domain, SOCK_DGRAM, 0); if (priv->sock < 0) { perror("socket(PF_INET)"); goto fail; } os_memset(&addr, 0, sizeof(addr)); +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + addr.sin6_family = AF_INET6; +#ifdef CONFIG_CTRL_IFACE_UDP_REMOTE + addr.sin6_addr = in6addr_any; +#else /* CONFIG_CTRL_IFACE_UDP_REMOTE */ + inet_pton(AF_INET6, "::1", &addr.sin6_addr); +#endif /* CONFIG_CTRL_IFACE_UDP_REMOTE */ +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ addr.sin_family = AF_INET; #ifdef CONFIG_CTRL_IFACE_UDP_REMOTE addr.sin_addr.s_addr = INADDR_ANY; #else /* CONFIG_CTRL_IFACE_UDP_REMOTE */ addr.sin_addr.s_addr = htonl((127 << 24) | 1); #endif /* CONFIG_CTRL_IFACE_UDP_REMOTE */ +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ try_again: +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + addr.sin6_port = htons(port); +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ addr.sin_port = htons(port); +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ if (bind(priv->sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { port--; if ((WPA_CTRL_IFACE_PORT - port) < WPA_CTRL_IFACE_PORT_LIMIT) @@ -362,6 +447,9 @@ static void wpa_supplicant_ctrl_iface_send(struct ctrl_iface_priv *priv, int idx; char *sbuf; int llen; +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + char addr[INET6_ADDRSTRLEN]; +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ dst = priv->ctrl_dst; if (priv->sock < 0 || dst == NULL) @@ -381,9 +469,16 @@ static void wpa_supplicant_ctrl_iface_send(struct ctrl_iface_priv *priv, while (dst) { next = dst->next; if (level >= dst->debug_level) { +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + wpa_printf(MSG_DEBUG, "CTRL_IFACE monitor send %s:%d", + inet_ntop(AF_INET6, &dst->addr.sin6_addr, + addr, sizeof(dst->addr)), + ntohs(dst->addr.sin6_port)); +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ wpa_printf(MSG_DEBUG, "CTRL_IFACE monitor send %s:%d", inet_ntoa(dst->addr.sin_addr), ntohs(dst->addr.sin_port)); +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ if (sendto(priv->sock, sbuf, llen + len, 0, (struct sockaddr *) &dst->addr, sizeof(dst->addr)) < 0) { diff --git a/wpa_supplicant/defconfig b/wpa_supplicant/defconfig index 6684782c..91eea355 100644 --- a/wpa_supplicant/defconfig +++ b/wpa_supplicant/defconfig @@ -192,8 +192,10 @@ CONFIG_SMARTCARD=y # Select control interface backend for external programs, e.g, wpa_cli: # unix = UNIX domain sockets (default for Linux/*BSD) # udp = UDP sockets using localhost (127.0.0.1) +# udp6 = UDP IPv6 sockets using localhost (::1) # named_pipe = Windows Named Pipe (default for Windows) # udp-remote = UDP sockets with remote access (only for tests systems/purpose) +# udp6-remote = UDP IPv6 sockets with remote access (only for tests purpose) # y = use default (backwards compatibility) # If this option is commented out, control interface is not included in the # build. diff --git a/wpa_supplicant/eap_register.c b/wpa_supplicant/eap_register.c index 6cd2fc50..ece57166 100644 --- a/wpa_supplicant/eap_register.c +++ b/wpa_supplicant/eap_register.c @@ -40,6 +40,13 @@ int eap_register_methods(void) ret = eap_peer_unauth_tls_register(); #endif /* EAP_UNAUTH_TLS */ +#ifdef EAP_TLS +#ifdef CONFIG_HS20 + if (ret == 0) + ret = eap_peer_wfa_unauth_tls_register(); +#endif /* CONFIG_HS20 */ +#endif /* EAP_TLS */ + #ifdef EAP_MSCHAPv2 if (ret == 0) ret = eap_peer_mschapv2_register(); diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c index c5e65efb..c0f55eea 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -398,6 +398,9 @@ static int wpa_supplicant_match_privacy(struct wpa_bss *bss, if (wpa_key_mgmt_wpa(ssid->key_mgmt)) privacy = 1; + if (ssid->key_mgmt & WPA_KEY_MGMT_OSEN) + privacy = 1; + if (bss->caps & IEEE80211_CAP_PRIVACY) return privacy; return !privacy; @@ -539,6 +542,12 @@ static int wpa_supplicant_ssid_bss_match(struct wpa_supplicant *wpa_s, return 0; } + if ((ssid->key_mgmt & WPA_KEY_MGMT_OSEN) && + wpa_bss_get_vendor_ie(bss, OSEN_IE_VENDOR_TYPE)) { + wpa_dbg(wpa_s, MSG_DEBUG, " allow in OSEN"); + return 1; + } + if (!wpa_key_mgmt_wpa(ssid->key_mgmt)) { wpa_dbg(wpa_s, MSG_DEBUG, " allow in non-WPA/WPA2"); return 1; @@ -728,13 +737,15 @@ static int bss_is_ess(struct wpa_bss *bss) static struct wpa_ssid * wpa_scan_res_match(struct wpa_supplicant *wpa_s, int i, struct wpa_bss *bss, - struct wpa_ssid *group) + struct wpa_ssid *group, + int only_first_ssid) { u8 wpa_ie_len, rsn_ie_len; int wpa; struct wpa_blacklist *e; const u8 *ie; struct wpa_ssid *ssid; + int osen; ie = wpa_bss_get_vendor_ie(bss, WPA_IE_VENDOR_TYPE); wpa_ie_len = ie ? ie[1] : 0; @@ -742,14 +753,18 @@ static struct wpa_ssid * wpa_scan_res_match(struct wpa_supplicant *wpa_s, ie = wpa_bss_get_ie(bss, WLAN_EID_RSN); rsn_ie_len = ie ? ie[1] : 0; + ie = wpa_bss_get_vendor_ie(bss, OSEN_IE_VENDOR_TYPE); + osen = ie != NULL; + wpa_dbg(wpa_s, MSG_DEBUG, "%d: " MACSTR " ssid='%s' " - "wpa_ie_len=%u rsn_ie_len=%u caps=0x%x level=%d%s%s", + "wpa_ie_len=%u rsn_ie_len=%u caps=0x%x level=%d%s%s%s", i, MAC2STR(bss->bssid), wpa_ssid_txt(bss->ssid, bss->ssid_len), wpa_ie_len, rsn_ie_len, bss->caps, bss->level, wpa_bss_get_vendor_ie(bss, WPS_IE_VENDOR_TYPE) ? " wps" : "", (wpa_bss_get_vendor_ie(bss, P2P_IE_VENDOR_TYPE) || wpa_bss_get_vendor_ie_beacon(bss, P2P_IE_VENDOR_TYPE)) ? - " p2p" : ""); + " p2p" : "", + osen ? " osen=1" : ""); e = wpa_blacklist_get(wpa_s, bss->bssid); if (e) { @@ -789,7 +804,7 @@ static struct wpa_ssid * wpa_scan_res_match(struct wpa_supplicant *wpa_s, wpa = wpa_ie_len > 0 || rsn_ie_len > 0; - for (ssid = group; ssid; ssid = ssid->pnext) { + for (ssid = group; ssid; ssid = only_first_ssid ? NULL : ssid->pnext) { int check_ssid = wpa ? 1 : (ssid->ssid_len != 0); int res; @@ -847,7 +862,7 @@ static struct wpa_ssid * wpa_scan_res_match(struct wpa_supplicant *wpa_s, if (!wpa_supplicant_ssid_bss_match(wpa_s, ssid, bss)) continue; - if (!wpa && + if (!osen && !wpa && !(ssid->key_mgmt & WPA_KEY_MGMT_NONE) && !(ssid->key_mgmt & WPA_KEY_MGMT_WPS) && !(ssid->key_mgmt & WPA_KEY_MGMT_IEEE8021X_NO_WPA)) { @@ -862,6 +877,12 @@ static struct wpa_ssid * wpa_scan_res_match(struct wpa_supplicant *wpa_s, continue; } + if ((ssid->key_mgmt & WPA_KEY_MGMT_OSEN) && !osen) { + wpa_dbg(wpa_s, MSG_DEBUG, " skip - non-OSEN network " + "not allowed"); + continue; + } + if (!wpa_supplicant_match_privacy(bss, ssid)) { wpa_dbg(wpa_s, MSG_DEBUG, " skip - privacy " "mismatch"); @@ -938,16 +959,22 @@ static struct wpa_ssid * wpa_scan_res_match(struct wpa_supplicant *wpa_s, static struct wpa_bss * wpa_supplicant_select_bss(struct wpa_supplicant *wpa_s, struct wpa_ssid *group, - struct wpa_ssid **selected_ssid) + struct wpa_ssid **selected_ssid, + int only_first_ssid) { unsigned int i; - wpa_dbg(wpa_s, MSG_DEBUG, "Selecting BSS from priority group %d", - group->priority); + if (only_first_ssid) + wpa_dbg(wpa_s, MSG_DEBUG, "Try to find BSS matching pre-selected network id=%d", + group->id); + else + wpa_dbg(wpa_s, MSG_DEBUG, "Selecting BSS from priority group %d", + group->priority); for (i = 0; i < wpa_s->last_scan_res_used; i++) { struct wpa_bss *bss = wpa_s->last_scan_res[i]; - *selected_ssid = wpa_scan_res_match(wpa_s, i, bss, group); + *selected_ssid = wpa_scan_res_match(wpa_s, i, bss, group, + only_first_ssid); if (!*selected_ssid) continue; wpa_dbg(wpa_s, MSG_DEBUG, " selected BSS " MACSTR @@ -972,10 +999,27 @@ struct wpa_bss * wpa_supplicant_pick_network(struct wpa_supplicant *wpa_s, return NULL; /* no scan results from last update */ while (selected == NULL) { + if (wpa_s->next_ssid) { + struct wpa_ssid *ssid; + + /* check that next_ssid is still valid */ + for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next) + if (ssid == wpa_s->next_ssid) + break; + wpa_s->next_ssid = NULL; + + if (ssid) { + selected = wpa_supplicant_select_bss( + wpa_s, ssid, selected_ssid, 1); + if (selected) + break; + } + } + for (prio = 0; prio < wpa_s->conf->num_prio; prio++) { selected = wpa_supplicant_select_bss( wpa_s, wpa_s->conf->pssid[prio], - selected_ssid); + selected_ssid, 0); if (selected) break; } @@ -2162,7 +2206,12 @@ static void wpa_supplicant_event_disassoc_finish(struct wpa_supplicant *wpa_s, wpa_s->current_ssid = last_ssid; } - if (fast_reconnect) { + if (fast_reconnect && + !wpas_network_disabled(wpa_s, fast_reconnect_ssid) && + !disallowed_bssid(wpa_s, fast_reconnect->bssid) && + !disallowed_ssid(wpa_s, fast_reconnect->ssid, + fast_reconnect->ssid_len) && + !wpas_temp_disabled(wpa_s, fast_reconnect_ssid)) { #ifndef CONFIG_NO_SCAN_PROCESSING wpa_dbg(wpa_s, MSG_DEBUG, "Try to reconnect to the same BSS"); if (wpa_supplicant_connect(wpa_s, fast_reconnect, @@ -2171,6 +2220,14 @@ static void wpa_supplicant_event_disassoc_finish(struct wpa_supplicant *wpa_s, wpa_supplicant_req_scan(wpa_s, 0, 100000); } #endif /* CONFIG_NO_SCAN_PROCESSING */ + } else if (fast_reconnect) { + /* + * Could not reconnect to the same BSS due to network being + * disabled. Use a new scan to match the alternative behavior + * above, i.e., to continue automatic reconnection attempt in a + * way that enforces disabled network rules. + */ + wpa_supplicant_req_scan(wpa_s, 0, 100000); } } diff --git a/wpa_supplicant/gas_query.c b/wpa_supplicant/gas_query.c index b2558470..a63ee6c1 100644 --- a/wpa_supplicant/gas_query.c +++ b/wpa_supplicant/gas_query.c @@ -42,6 +42,7 @@ struct gas_query_pending { struct wpabuf *req; struct wpabuf *adv_proto; struct wpabuf *resp; + struct os_reltime last_oper; void (*cb)(void *ctx, const u8 *dst, u8 dialog_token, enum gas_query_result result, const struct wpabuf *adv_proto, @@ -64,6 +65,16 @@ static void gas_query_tx_comeback_timeout(void *eloop_data, void *user_ctx); static void gas_query_timeout(void *eloop_data, void *user_ctx); +static int ms_from_time(struct os_reltime *last) +{ + struct os_reltime now, res; + + os_get_reltime(&now); + os_reltime_sub(&now, last, &res); + return res.sec * 1000 + res.usec / 1000; +} + + /** * gas_query_init - Initialize GAS query component * @wpa_s: Pointer to wpa_supplicant data @@ -199,6 +210,7 @@ static void gas_query_tx_status(struct wpa_supplicant *wpa_s, { struct gas_query_pending *query; struct gas_query *gas = wpa_s->gas; + int dur; if (gas->current == NULL) { wpa_printf(MSG_DEBUG, "GAS: Unexpected TX status: freq=%u dst=" @@ -209,13 +221,15 @@ static void gas_query_tx_status(struct wpa_supplicant *wpa_s, query = gas->current; + dur = ms_from_time(&query->last_oper); wpa_printf(MSG_DEBUG, "GAS: TX status: freq=%u dst=" MACSTR - " result=%d query=%p dialog_token=%u", - freq, MAC2STR(dst), result, query, query->dialog_token); + " result=%d query=%p dialog_token=%u dur=%d ms", + freq, MAC2STR(dst), result, query, query->dialog_token, dur); if (os_memcmp(dst, query->addr, ETH_ALEN) != 0) { wpa_printf(MSG_DEBUG, "GAS: TX status for unexpected destination"); return; } + os_get_reltime(&query->last_oper); if (result == OFFCHANNEL_SEND_ACTION_SUCCESS) { eloop_cancel_timeout(gas_query_timeout, gas, query); @@ -251,6 +265,7 @@ static int gas_query_tx(struct gas_query *gas, struct gas_query_pending *query, u8 *categ = wpabuf_mhead_u8(req); *categ = WLAN_ACTION_PROTECTED_DUAL; } + os_get_reltime(&query->last_oper); res = offchannel_send_action(gas->wpa_s, query->freq, query->addr, gas->wpa_s->own_addr, query->addr, wpabuf_head(req), wpabuf_len(req), 1000, @@ -452,6 +467,9 @@ int gas_query_rx(struct gas_query *gas, const u8 *da, const u8 *sa, return -1; } + wpa_printf(MSG_DEBUG, "GAS: Response in %d ms from " MACSTR, + ms_from_time(&query->last_oper), MAC2STR(sa)); + if (query->wait_comeback && action == WLAN_PA_GAS_INITIAL_RESP) { wpa_printf(MSG_DEBUG, "GAS: Unexpected initial response from " MACSTR " dialog token %u when waiting for comeback " diff --git a/wpa_supplicant/hs20_supplicant.c b/wpa_supplicant/hs20_supplicant.c index 5f30313b..b873c7c1 100644 --- a/wpa_supplicant/hs20_supplicant.c +++ b/wpa_supplicant/hs20_supplicant.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2009, Atheros Communications, Inc. - * Copyright (c) 2011-2012, Qualcomm Atheros, Inc. + * Copyright (c) 2011-2013, Qualcomm Atheros, Inc. * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -14,22 +14,65 @@ #include "common/ieee802_11_defs.h" #include "common/gas.h" #include "common/wpa_ctrl.h" +#include "rsn_supp/wpa.h" #include "wpa_supplicant_i.h" #include "driver_i.h" #include "config.h" +#include "scan.h" #include "bss.h" +#include "blacklist.h" #include "gas_query.h" #include "interworking.h" #include "hs20_supplicant.h" -void wpas_hs20_add_indication(struct wpabuf *buf) +#define OSU_MAX_ITEMS 10 + +struct osu_lang_string { + char lang[4]; + char text[253]; +}; + +struct osu_icon { + u16 width; + u16 height; + char lang[4]; + char icon_type[256]; + char filename[256]; + unsigned int id; + unsigned int failed:1; +}; + +struct osu_provider { + u8 bssid[ETH_ALEN]; + u8 osu_ssid[32]; + u8 osu_ssid_len; + char server_uri[256]; + u32 osu_methods; /* bit 0 = OMA-DM, bit 1 = SOAP-XML SPP */ + char osu_nai[256]; + struct osu_lang_string friendly_name[OSU_MAX_ITEMS]; + size_t friendly_name_count; + struct osu_lang_string serv_desc[OSU_MAX_ITEMS]; + size_t serv_desc_count; + struct osu_icon icon[OSU_MAX_ITEMS]; + size_t icon_count; +}; + + +void wpas_hs20_add_indication(struct wpabuf *buf, int pps_mo_id) { + u8 conf; + wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC); - wpabuf_put_u8(buf, 5); + wpabuf_put_u8(buf, pps_mo_id >= 0 ? 7 : 5); wpabuf_put_be24(buf, OUI_WFA); wpabuf_put_u8(buf, HS20_INDICATION_OUI_TYPE); - wpabuf_put_u8(buf, 0x00); /* Hotspot Configuration */ + conf = HS20_VERSION; + if (pps_mo_id >= 0) + conf |= HS20_PPS_MO_ID_PRESENT; + wpabuf_put_u8(buf, conf); + if (pps_mo_id >= 0) + wpabuf_put_le16(buf, pps_mo_id); } @@ -62,6 +105,22 @@ int is_hs20_network(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid, } +int hs20_get_pps_mo_id(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid) +{ + struct wpa_cred *cred; + + if (ssid == NULL || ssid->parent_cred == NULL) + return 0; + + for (cred = wpa_s->conf->cred; cred; cred = cred->next) { + if (ssid->parent_cred == cred) + return cred->update_identifier; + } + + return 0; +} + + struct wpabuf * hs20_build_anqp_req(u32 stypes, const u8 *payload, size_t payload_len) { @@ -80,6 +139,11 @@ struct wpabuf * hs20_build_anqp_req(u32 stypes, const u8 *payload, wpabuf_put_u8(buf, 0); /* Reserved */ if (payload) wpabuf_put_data(buf, payload, payload_len); + } else if (stypes == BIT(HS20_STYPE_ICON_REQUEST)) { + wpabuf_put_u8(buf, HS20_STYPE_ICON_REQUEST); + wpabuf_put_u8(buf, 0); /* Reserved */ + if (payload) + wpabuf_put_data(buf, payload, payload_len); } else { u8 i; wpabuf_put_u8(buf, HS20_STYPE_QUERY_LIST); @@ -135,6 +199,116 @@ int hs20_anqp_send_req(struct wpa_supplicant *wpa_s, const u8 *dst, u32 stypes, } +static int hs20_process_icon_binary_file(struct wpa_supplicant *wpa_s, + const u8 *sa, const u8 *pos, + size_t slen) +{ + char fname[256]; + int png; + FILE *f; + u16 data_len; + + wpa_msg(wpa_s, MSG_INFO, "RX-HS20-ANQP " MACSTR " Icon Binary File", + MAC2STR(sa)); + + if (slen < 4) { + wpa_dbg(wpa_s, MSG_DEBUG, "HS 2.0: Too short Icon Binary File " + "value from " MACSTR, MAC2STR(sa)); + return -1; + } + + wpa_printf(MSG_DEBUG, "HS 2.0: Download Status Code %u", *pos); + if (*pos != 0) + return -1; + pos++; + slen--; + + if ((size_t) 1 + pos[0] > slen) { + wpa_dbg(wpa_s, MSG_DEBUG, "HS 2.0: Too short Icon Binary File " + "value from " MACSTR, MAC2STR(sa)); + return -1; + } + wpa_hexdump_ascii(MSG_DEBUG, "Icon Type", pos + 1, pos[0]); + png = os_strncasecmp((char *) pos + 1, "image/png", 9) == 0; + slen -= 1 + pos[0]; + pos += 1 + pos[0]; + + if (slen < 2) { + wpa_dbg(wpa_s, MSG_DEBUG, "HS 2.0: Too short Icon Binary File " + "value from " MACSTR, MAC2STR(sa)); + return -1; + } + data_len = WPA_GET_LE16(pos); + pos += 2; + slen -= 2; + + if (data_len > slen) { + wpa_dbg(wpa_s, MSG_DEBUG, "HS 2.0: Too short Icon Binary File " + "value from " MACSTR, MAC2STR(sa)); + return -1; + } + + wpa_printf(MSG_DEBUG, "Icon Binary Data: %u bytes", data_len); + if (wpa_s->conf->osu_dir == NULL) + return -1; + + wpa_s->osu_icon_id++; + if (wpa_s->osu_icon_id == 0) + wpa_s->osu_icon_id++; + snprintf(fname, sizeof(fname), "%s/osu-icon-%u.%s", + wpa_s->conf->osu_dir, wpa_s->osu_icon_id, + png ? "png" : "icon"); + f = fopen(fname, "wb"); + if (f == NULL) + return -1; + if (fwrite(pos, slen, 1, f) != 1) { + fclose(f); + unlink(fname); + return -1; + } + fclose(f); + + wpa_msg(wpa_s, MSG_INFO, "RX-HS20-ANQP-ICON %s", fname); + return 0; +} + + +static void hs20_continue_icon_fetch(void *eloop_ctx, void *sock_ctx) +{ + struct wpa_supplicant *wpa_s = eloop_ctx; + if (wpa_s->fetch_osu_icon_in_progress) + hs20_next_osu_icon(wpa_s); +} + + +static void hs20_osu_icon_fetch_result(struct wpa_supplicant *wpa_s, int res) +{ + size_t i, j; + struct os_reltime now, tmp; + int dur; + + os_get_reltime(&now); + os_reltime_sub(&now, &wpa_s->osu_icon_fetch_start, &tmp); + dur = tmp.sec * 1000 + tmp.usec / 1000; + wpa_printf(MSG_DEBUG, "HS 2.0: Icon fetch dur=%d ms res=%d", + dur, res); + + for (i = 0; i < wpa_s->osu_prov_count; i++) { + struct osu_provider *osu = &wpa_s->osu_prov[i]; + for (j = 0; j < osu->icon_count; j++) { + struct osu_icon *icon = &osu->icon[j]; + if (icon->id || icon->failed) + continue; + if (res < 0) + icon->failed = 1; + else + icon->id = wpa_s->osu_icon_id; + return; + } + } +} + + void hs20_parse_rx_hs20_anqp_resp(struct wpa_supplicant *wpa_s, const u8 *sa, const u8 *data, size_t slen) { @@ -142,6 +316,7 @@ void hs20_parse_rx_hs20_anqp_resp(struct wpa_supplicant *wpa_s, u8 subtype; struct wpa_bss *bss = wpa_bss_get_bssid(wpa_s, sa); struct wpa_bss_anqp *anqp = NULL; + int ret; if (slen < 2) return; @@ -207,8 +382,530 @@ void hs20_parse_rx_hs20_anqp_resp(struct wpa_supplicant *wpa_s, wpabuf_alloc_copy(pos, slen); } break; + case HS20_STYPE_OSU_PROVIDERS_LIST: + wpa_msg(wpa_s, MSG_INFO, "RX-HS20-ANQP " MACSTR + " OSU Providers list", MAC2STR(sa)); + wpa_s->num_prov_found++; + if (anqp) { + wpabuf_free(anqp->hs20_osu_providers_list); + anqp->hs20_osu_providers_list = + wpabuf_alloc_copy(pos, slen); + } + break; + case HS20_STYPE_ICON_BINARY_FILE: + ret = hs20_process_icon_binary_file(wpa_s, sa, pos, slen); + if (wpa_s->fetch_osu_icon_in_progress) { + hs20_osu_icon_fetch_result(wpa_s, ret); + eloop_cancel_timeout(hs20_continue_icon_fetch, + wpa_s, NULL); + eloop_register_timeout(0, 0, hs20_continue_icon_fetch, + wpa_s, NULL); + } + break; default: wpa_printf(MSG_DEBUG, "HS20: Unsupported subtype %u", subtype); break; } } + + +void hs20_notify_parse_done(struct wpa_supplicant *wpa_s) +{ + if (!wpa_s->fetch_osu_icon_in_progress) + return; + if (eloop_is_timeout_registered(hs20_continue_icon_fetch, wpa_s, NULL)) + return; + /* + * We are going through icon fetch, but no icon response was received. + * Assume this means the current AP could not provide an answer to avoid + * getting stuck in fetch iteration. + */ + hs20_icon_fetch_failed(wpa_s); +} + + +static void hs20_free_osu_prov_entry(struct osu_provider *prov) +{ +} + + +void hs20_free_osu_prov(struct wpa_supplicant *wpa_s) +{ + size_t i; + for (i = 0; i < wpa_s->osu_prov_count; i++) + hs20_free_osu_prov_entry(&wpa_s->osu_prov[i]); + os_free(wpa_s->osu_prov); + wpa_s->osu_prov = NULL; + wpa_s->osu_prov_count = 0; +} + + +static void hs20_osu_fetch_done(struct wpa_supplicant *wpa_s) +{ + char fname[256]; + FILE *f; + size_t i, j; + + wpa_s->fetch_osu_info = 0; + wpa_s->fetch_osu_icon_in_progress = 0; + + if (wpa_s->conf->osu_dir == NULL) { + hs20_free_osu_prov(wpa_s); + wpa_s->fetch_anqp_in_progress = 0; + return; + } + + snprintf(fname, sizeof(fname), "%s/osu-providers.txt", + wpa_s->conf->osu_dir); + f = fopen(fname, "w"); + if (f == NULL) { + hs20_free_osu_prov(wpa_s); + return; + } + for (i = 0; i < wpa_s->osu_prov_count; i++) { + struct osu_provider *osu = &wpa_s->osu_prov[i]; + if (i > 0) + fprintf(f, "\n"); + fprintf(f, "OSU-PROVIDER " MACSTR "\n" + "uri=%s\n" + "methods=%08x\n", + MAC2STR(osu->bssid), osu->server_uri, osu->osu_methods); + if (osu->osu_ssid_len) { + fprintf(f, "osu_ssid=%s\n", + wpa_ssid_txt(osu->osu_ssid, + osu->osu_ssid_len)); + } + if (osu->osu_nai[0]) + fprintf(f, "osu_nai=%s\n", osu->osu_nai); + for (j = 0; j < osu->friendly_name_count; j++) { + fprintf(f, "friendly_name=%s:%s\n", + osu->friendly_name[j].lang, + osu->friendly_name[j].text); + } + for (j = 0; j < osu->serv_desc_count; j++) { + fprintf(f, "desc=%s:%s\n", + osu->serv_desc[j].lang, + osu->serv_desc[j].text); + } + for (j = 0; j < osu->icon_count; j++) { + struct osu_icon *icon = &osu->icon[j]; + if (icon->failed) + continue; /* could not fetch icon */ + fprintf(f, "icon=%u:%u:%u:%s:%s:%s\n", + icon->id, icon->width, icon->height, icon->lang, + icon->icon_type, icon->filename); + } + } + fclose(f); + hs20_free_osu_prov(wpa_s); + + wpa_msg(wpa_s, MSG_INFO, "OSU provider fetch completed"); + wpa_s->fetch_anqp_in_progress = 0; +} + + +void hs20_next_osu_icon(struct wpa_supplicant *wpa_s) +{ + size_t i, j; + + wpa_printf(MSG_DEBUG, "HS 2.0: Ready to fetch next icon"); + + for (i = 0; i < wpa_s->osu_prov_count; i++) { + struct osu_provider *osu = &wpa_s->osu_prov[i]; + for (j = 0; j < osu->icon_count; j++) { + struct osu_icon *icon = &osu->icon[j]; + if (icon->id || icon->failed) + continue; + + wpa_printf(MSG_DEBUG, "HS 2.0: Try to fetch icon '%s' " + "from " MACSTR, icon->filename, + MAC2STR(osu->bssid)); + os_get_reltime(&wpa_s->osu_icon_fetch_start); + if (hs20_anqp_send_req(wpa_s, osu->bssid, + BIT(HS20_STYPE_ICON_REQUEST), + (u8 *) icon->filename, + os_strlen(icon->filename)) < 0) { + icon->failed = 1; + continue; + } + return; + } + } + + wpa_printf(MSG_DEBUG, "HS 2.0: No more icons to fetch"); + hs20_osu_fetch_done(wpa_s); +} + + +static void hs20_osu_add_prov(struct wpa_supplicant *wpa_s, struct wpa_bss *bss, + const u8 *osu_ssid, u8 osu_ssid_len, + const u8 *pos, size_t len) +{ + struct osu_provider *prov; + const u8 *end = pos + len; + u16 len2; + const u8 *pos2; + + wpa_hexdump(MSG_DEBUG, "HS 2.0: Parsing OSU Provider", pos, len); + prov = os_realloc_array(wpa_s->osu_prov, + wpa_s->osu_prov_count + 1, + sizeof(*prov)); + if (prov == NULL) + return; + wpa_s->osu_prov = prov; + prov = &prov[wpa_s->osu_prov_count]; + os_memset(prov, 0, sizeof(*prov)); + + os_memcpy(prov->bssid, bss->bssid, ETH_ALEN); + os_memcpy(prov->osu_ssid, osu_ssid, osu_ssid_len); + prov->osu_ssid_len = osu_ssid_len; + + /* OSU Friendly Name Length */ + if (pos + 2 > end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU " + "Friendly Name Length"); + return; + } + len2 = WPA_GET_LE16(pos); + pos += 2; + if (pos + len2 > end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU " + "Friendly Name Duples"); + return; + } + pos2 = pos; + pos += len2; + + /* OSU Friendly Name Duples */ + while (pos2 + 4 <= pos && prov->friendly_name_count < OSU_MAX_ITEMS) { + struct osu_lang_string *f; + if (pos2 + 1 + pos2[0] > pos || pos2[0] < 3) { + wpa_printf(MSG_DEBUG, "Invalid OSU Friendly Name"); + break; + } + f = &prov->friendly_name[prov->friendly_name_count++]; + os_memcpy(f->lang, pos2 + 1, 3); + os_memcpy(f->text, pos2 + 1 + 3, pos2[0] - 3); + pos2 += 1 + pos2[0]; + } + + /* OSU Server URI */ + if (pos + 1 > end || pos + 1 + pos[0] > end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU Server " + "URI"); + return; + } + os_memcpy(prov->server_uri, pos + 1, pos[0]); + pos += 1 + pos[0]; + + /* OSU Method list */ + if (pos + 1 > end || pos + 1 + pos[0] > end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU Method " + "list"); + return; + } + pos2 = pos + 1; + pos += 1 + pos[0]; + while (pos2 < pos) { + if (*pos2 < 32) + prov->osu_methods |= BIT(*pos2); + pos2++; + } + + /* Icons Available Length */ + if (pos + 2 > end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for Icons " + "Available Length"); + return; + } + len2 = WPA_GET_LE16(pos); + pos += 2; + if (pos + len2 > end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for Icons " + "Available"); + return; + } + pos2 = pos; + pos += len2; + + /* Icons Available */ + while (pos2 < pos) { + struct osu_icon *icon = &prov->icon[prov->icon_count]; + if (pos2 + 2 + 2 + 3 + 1 + 1 > pos) { + wpa_printf(MSG_DEBUG, "HS 2.0: Invalid Icon Metadata"); + break; + } + + icon->width = WPA_GET_LE16(pos2); + pos2 += 2; + icon->height = WPA_GET_LE16(pos2); + pos2 += 2; + os_memcpy(icon->lang, pos2, 3); + pos2 += 3; + + if (pos2 + 1 + pos2[0] > pos) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not room for Icon Type"); + break; + } + os_memcpy(icon->icon_type, pos2 + 1, pos2[0]); + pos2 += 1 + pos2[0]; + + if (pos2 + 1 + pos2[0] > pos) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not room for Icon " + "Filename"); + break; + } + os_memcpy(icon->filename, pos2 + 1, pos2[0]); + pos2 += 1 + pos2[0]; + + prov->icon_count++; + } + + /* OSU_NAI */ + if (pos + 1 > end || pos + 1 + pos[0] > end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU_NAI"); + return; + } + os_memcpy(prov->osu_nai, pos + 1, pos[0]); + pos += 1 + pos[0]; + + /* OSU Service Description Length */ + if (pos + 2 > end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU " + "Service Description Length"); + return; + } + len2 = WPA_GET_LE16(pos); + pos += 2; + if (pos + len2 > end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU " + "Service Description Duples"); + return; + } + pos2 = pos; + pos += len2; + + /* OSU Service Description Duples */ + while (pos2 + 4 <= pos && prov->serv_desc_count < OSU_MAX_ITEMS) { + struct osu_lang_string *f; + if (pos2 + 1 + pos2[0] > pos || pos2[0] < 3) { + wpa_printf(MSG_DEBUG, "Invalid OSU Service " + "Description"); + break; + } + f = &prov->serv_desc[prov->serv_desc_count++]; + os_memcpy(f->lang, pos2 + 1, 3); + os_memcpy(f->text, pos2 + 1 + 3, pos2[0] - 3); + pos2 += 1 + pos2[0]; + } + + wpa_printf(MSG_DEBUG, "HS 2.0: Added OSU Provider through " MACSTR, + MAC2STR(bss->bssid)); + wpa_s->osu_prov_count++; +} + + +void hs20_osu_icon_fetch(struct wpa_supplicant *wpa_s) +{ + struct wpa_bss *bss; + struct wpabuf *prov_anqp; + const u8 *pos, *end; + u16 len; + const u8 *osu_ssid; + u8 osu_ssid_len; + u8 num_providers; + + hs20_free_osu_prov(wpa_s); + + dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) { + if (bss->anqp == NULL) + continue; + prov_anqp = bss->anqp->hs20_osu_providers_list; + if (prov_anqp == NULL) + continue; + wpa_printf(MSG_DEBUG, "HS 2.0: Parsing OSU Providers list from " + MACSTR, MAC2STR(bss->bssid)); + wpa_hexdump_buf(MSG_DEBUG, "HS 2.0: OSU Providers list", + prov_anqp); + pos = wpabuf_head(prov_anqp); + end = pos + wpabuf_len(prov_anqp); + + /* OSU SSID */ + if (pos + 1 > end) + continue; + if (pos + 1 + pos[0] > end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for " + "OSU SSID"); + continue; + } + osu_ssid_len = *pos++; + if (osu_ssid_len > 32) { + wpa_printf(MSG_DEBUG, "HS 2.0: Invalid OSU SSID " + "Length %u", osu_ssid_len); + continue; + } + osu_ssid = pos; + pos += osu_ssid_len; + + if (pos + 1 > end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for " + "Number of OSU Providers"); + continue; + } + num_providers = *pos++; + wpa_printf(MSG_DEBUG, "HS 2.0: Number of OSU Providers: %u", + num_providers); + + /* OSU Providers */ + while (pos + 2 < end && num_providers > 0) { + num_providers--; + len = WPA_GET_LE16(pos); + pos += 2; + if (pos + len > end) + break; + hs20_osu_add_prov(wpa_s, bss, osu_ssid, + osu_ssid_len, pos, len); + pos += len; + } + + if (pos != end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Ignored %d bytes of " + "extra data after OSU Providers", + (int) (end - pos)); + } + } + + wpa_s->fetch_osu_icon_in_progress = 1; + hs20_next_osu_icon(wpa_s); +} + + +static void hs20_osu_scan_res_handler(struct wpa_supplicant *wpa_s, + struct wpa_scan_results *scan_res) +{ + wpa_printf(MSG_DEBUG, "OSU provisioning fetch scan completed"); + wpa_s->network_select = 0; + wpa_s->fetch_all_anqp = 1; + wpa_s->fetch_osu_info = 1; + wpa_s->fetch_osu_icon_in_progress = 0; + + interworking_start_fetch_anqp(wpa_s); +} + + +int hs20_fetch_osu(struct wpa_supplicant *wpa_s) +{ + if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) { + wpa_printf(MSG_DEBUG, "HS 2.0: Cannot start fetch_osu - " + "interface disabled"); + return -1; + } + + if (wpa_s->scanning) { + wpa_printf(MSG_DEBUG, "HS 2.0: Cannot start fetch_osu - " + "scanning"); + return -1; + } + + if (wpa_s->conf->osu_dir == NULL) { + wpa_printf(MSG_DEBUG, "HS 2.0: Cannot start fetch_osu - " + "osu_dir not configured"); + return -1; + } + + if (wpa_s->fetch_anqp_in_progress || wpa_s->network_select) { + wpa_printf(MSG_DEBUG, "HS 2.0: Cannot start fetch_osu - " + "fetch in progress (%d, %d)", + wpa_s->fetch_anqp_in_progress, + wpa_s->network_select); + return -1; + } + + wpa_msg(wpa_s, MSG_INFO, "Starting OSU provisioning information fetch"); + wpa_s->num_osu_scans = 0; + wpa_s->num_prov_found = 0; + hs20_start_osu_scan(wpa_s); + + return 0; +} + + +void hs20_start_osu_scan(struct wpa_supplicant *wpa_s) +{ + wpa_s->num_osu_scans++; + wpa_s->scan_req = MANUAL_SCAN_REQ; + wpa_s->scan_res_handler = hs20_osu_scan_res_handler; + wpa_supplicant_req_scan(wpa_s, 0, 0); +} + + +void hs20_cancel_fetch_osu(struct wpa_supplicant *wpa_s) +{ + wpa_printf(MSG_DEBUG, "Cancel OSU fetch"); + interworking_stop_fetch_anqp(wpa_s); + wpa_s->network_select = 0; + wpa_s->fetch_osu_info = 0; + wpa_s->fetch_osu_icon_in_progress = 0; +} + + +void hs20_icon_fetch_failed(struct wpa_supplicant *wpa_s) +{ + hs20_osu_icon_fetch_result(wpa_s, -1); + eloop_cancel_timeout(hs20_continue_icon_fetch, wpa_s, NULL); + eloop_register_timeout(0, 0, hs20_continue_icon_fetch, wpa_s, NULL); +} + + +void hs20_rx_subscription_remediation(struct wpa_supplicant *wpa_s, + const char *url, u8 osu_method) +{ + if (url) + wpa_msg(wpa_s, MSG_INFO, HS20_SUBSCRIPTION_REMEDIATION "%u %s", + osu_method, url); + else + wpa_msg(wpa_s, MSG_INFO, HS20_SUBSCRIPTION_REMEDIATION); +} + + +void hs20_rx_deauth_imminent_notice(struct wpa_supplicant *wpa_s, u8 code, + u16 reauth_delay, const char *url) +{ + if (!wpa_sm_pmf_enabled(wpa_s->wpa)) { + wpa_printf(MSG_DEBUG, "HS 2.0: Ignore deauthentication imminent notice since PMF was not enabled"); + return; + } + + wpa_msg(wpa_s, MSG_INFO, HS20_DEAUTH_IMMINENT_NOTICE "%u %u %s", + code, reauth_delay, url); + + if (code == HS20_DEAUTH_REASON_CODE_BSS) { + wpa_printf(MSG_DEBUG, "HS 2.0: Add BSS to blacklist"); + wpa_blacklist_add(wpa_s, wpa_s->bssid); + /* TODO: For now, disable full ESS since some drivers may not + * support disabling per BSS. */ + if (wpa_s->current_ssid) { + struct os_time now; + os_get_time(&now); + if (now.sec + reauth_delay <= + wpa_s->current_ssid->disabled_until.sec) + return; + wpa_printf(MSG_DEBUG, "HS 2.0: Disable network for %u seconds (BSS)", + reauth_delay); + wpa_s->current_ssid->disabled_until.sec = + now.sec + reauth_delay; + } + } + + if (code == HS20_DEAUTH_REASON_CODE_ESS && wpa_s->current_ssid) { + struct os_time now; + os_get_time(&now); + if (now.sec + reauth_delay <= + wpa_s->current_ssid->disabled_until.sec) + return; + wpa_printf(MSG_DEBUG, "HS 2.0: Disable network for %u seconds", + reauth_delay); + wpa_s->current_ssid->disabled_until.sec = + now.sec + reauth_delay; + } +} diff --git a/wpa_supplicant/hs20_supplicant.h b/wpa_supplicant/hs20_supplicant.h index 1c8481bf..88e50620 100644 --- a/wpa_supplicant/hs20_supplicant.h +++ b/wpa_supplicant/hs20_supplicant.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2012, Qualcomm Atheros, Inc. + * Copyright (c) 2011-2013, Qualcomm Atheros, Inc. * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -8,7 +8,7 @@ #ifndef HS20_SUPPLICANT_H #define HS20_SUPPLICANT_H -void wpas_hs20_add_indication(struct wpabuf *buf); +void wpas_hs20_add_indication(struct wpabuf *buf, int pps_mo_id); int hs20_anqp_send_req(struct wpa_supplicant *wpa_s, const u8 *dst, u32 stypes, const u8 *payload, size_t payload_len); @@ -18,5 +18,20 @@ void hs20_parse_rx_hs20_anqp_resp(struct wpa_supplicant *wpa_s, const u8 *sa, const u8 *data, size_t slen); int is_hs20_network(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid, struct wpa_bss *bss); +int hs20_get_pps_mo_id(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid); +void hs20_notify_parse_done(struct wpa_supplicant *wpa_s); + +void hs20_rx_subscription_remediation(struct wpa_supplicant *wpa_s, + const char *url, u8 osu_method); +void hs20_rx_deauth_imminent_notice(struct wpa_supplicant *wpa_s, u8 code, + u16 reauth_delay, const char *url); + +void hs20_free_osu_prov(struct wpa_supplicant *wpa_s); +void hs20_next_osu_icon(struct wpa_supplicant *wpa_s); +void hs20_osu_icon_fetch(struct wpa_supplicant *wpa_s); +int hs20_fetch_osu(struct wpa_supplicant *wpa_s); +void hs20_cancel_fetch_osu(struct wpa_supplicant *wpa_s); +void hs20_icon_fetch_failed(struct wpa_supplicant *wpa_s); +void hs20_start_osu_scan(struct wpa_supplicant *wpa_s); #endif /* HS20_SUPPLICANT_H */ diff --git a/wpa_supplicant/interworking.c b/wpa_supplicant/interworking.c index da8971d9..abf5dee1 100644 --- a/wpa_supplicant/interworking.c +++ b/wpa_supplicant/interworking.c @@ -46,9 +46,28 @@ static void interworking_next_anqp_fetch(struct wpa_supplicant *wpa_s); static struct wpa_cred * interworking_credentials_available_realm( - struct wpa_supplicant *wpa_s, struct wpa_bss *bss); + struct wpa_supplicant *wpa_s, struct wpa_bss *bss, int ignore_bw, + int *excluded); static struct wpa_cred * interworking_credentials_available_3gpp( - struct wpa_supplicant *wpa_s, struct wpa_bss *bss); + struct wpa_supplicant *wpa_s, struct wpa_bss *bss, int ignore_bw, + int *excluded); + + +static int cred_prio_cmp(const struct wpa_cred *a, const struct wpa_cred *b) +{ + if (a->priority > b->priority) + return 1; + if (a->priority < b->priority) + return -1; + if (a->provisioning_sp == NULL || b->provisioning_sp == NULL || + os_strcmp(a->provisioning_sp, b->provisioning_sp) != 0) + return 0; + if (a->sp_priority < b->sp_priority) + return 1; + if (a->sp_priority > b->sp_priority) + return -1; + return 0; +} static void interworking_reconnect(struct wpa_supplicant *wpa_s) @@ -102,6 +121,9 @@ static void interworking_anqp_resp_cb(void *ctx, const u8 *dst, { struct wpa_supplicant *wpa_s = ctx; + wpa_printf(MSG_DEBUG, "ANQP: Response callback dst=" MACSTR + " dialog_token=%u result=%d status_code=%u", + MAC2STR(dst), dialog_token, result, status_code); anqp_resp_cb(wpa_s, dst, dialog_token, result, adv_proto, resp, status_code); interworking_next_anqp_fetch(wpa_s); @@ -155,13 +177,45 @@ static int cred_with_domain(struct wpa_supplicant *wpa_s) struct wpa_cred *cred; for (cred = wpa_s->conf->cred; cred; cred = cred->next) { - if (cred->domain || cred->pcsc || cred->imsi) + if (cred->domain || cred->pcsc || cred->imsi || + cred->roaming_partner) + return 1; + } + return 0; +} + + +#ifdef CONFIG_HS20 + +static int cred_with_min_backhaul(struct wpa_supplicant *wpa_s) +{ + struct wpa_cred *cred; + + for (cred = wpa_s->conf->cred; cred; cred = cred->next) { + if (cred->min_dl_bandwidth_home || + cred->min_ul_bandwidth_home || + cred->min_dl_bandwidth_roaming || + cred->min_ul_bandwidth_roaming) return 1; } return 0; } +static int cred_with_conn_capab(struct wpa_supplicant *wpa_s) +{ + struct wpa_cred *cred; + + for (cred = wpa_s->conf->cred; cred; cred = cred->next) { + if (cred->num_req_conn_capab) + return 1; + } + return 0; +} + +#endif /* CONFIG_HS20 */ + + static int additional_roaming_consortiums(struct wpa_bss *bss) { const u8 *ie; @@ -227,13 +281,17 @@ static int interworking_anqp_send_req(struct wpa_supplicant *wpa_s, wpabuf_put_u8(extra, HS20_STYPE_QUERY_LIST); wpabuf_put_u8(extra, 0); /* Reserved */ wpabuf_put_u8(extra, HS20_STYPE_CAPABILITY_LIST); - if (all) { + if (all) wpabuf_put_u8(extra, HS20_STYPE_OPERATOR_FRIENDLY_NAME); + if (all || cred_with_min_backhaul(wpa_s)) wpabuf_put_u8(extra, HS20_STYPE_WAN_METRICS); + if (all || cred_with_conn_capab(wpa_s)) wpabuf_put_u8(extra, HS20_STYPE_CONNECTION_CAPABILITY); + if (all) wpabuf_put_u8(extra, HS20_STYPE_OPERATING_CLASS); - } + if (all) + wpabuf_put_u8(extra, HS20_STYPE_OSU_PROVIDERS_LIST); gas_anqp_set_element_len(extra, len_pos); } #endif /* CONFIG_HS20 */ @@ -918,6 +976,7 @@ static int interworking_connect_3gpp(struct wpa_supplicant *wpa_s, wpa_config_set_quoted(ssid, "password", cred->password) < 0) goto fail; + wpa_s->next_ssid = ssid; wpa_config_update_prio_list(wpa_s->conf); interworking_reconnect(wpa_s); @@ -1046,11 +1105,164 @@ static int cred_excluded_ssid(struct wpa_cred *cred, struct wpa_bss *bss) } +static int cred_below_min_backhaul(struct wpa_supplicant *wpa_s, + struct wpa_cred *cred, struct wpa_bss *bss) +{ + int res; + unsigned int dl_bandwidth, ul_bandwidth; + const u8 *wan; + u8 wan_info, dl_load, ul_load; + u16 lmd; + u32 ul_speed, dl_speed; + + if (!cred->min_dl_bandwidth_home && + !cred->min_ul_bandwidth_home && + !cred->min_dl_bandwidth_roaming && + !cred->min_ul_bandwidth_roaming) + return 0; /* No bandwidth constraint specified */ + + if (bss->anqp == NULL || bss->anqp->hs20_wan_metrics == NULL) + return 0; /* No WAN Metrics known - ignore constraint */ + + wan = wpabuf_head(bss->anqp->hs20_wan_metrics); + wan_info = wan[0]; + if (wan_info & BIT(3)) + return 1; /* WAN link at capacity */ + lmd = WPA_GET_LE16(wan + 11); + if (lmd == 0) + return 0; /* Downlink/Uplink Load was not measured */ + dl_speed = WPA_GET_LE32(wan + 1); + ul_speed = WPA_GET_LE32(wan + 5); + dl_load = wan[9]; + ul_load = wan[10]; + + if (dl_speed >= 0xffffff) + dl_bandwidth = dl_speed / 255 * (255 - dl_load); + else + dl_bandwidth = dl_speed * (255 - dl_load) / 255; + + if (ul_speed >= 0xffffff) + ul_bandwidth = ul_speed / 255 * (255 - ul_load); + else + ul_bandwidth = ul_speed * (255 - ul_load) / 255; + + res = interworking_home_sp_cred(wpa_s, cred, bss->anqp ? + bss->anqp->domain_name : NULL); + if (res > 0) { + if (cred->min_dl_bandwidth_home > dl_bandwidth) + return 1; + if (cred->min_ul_bandwidth_home > ul_bandwidth) + return 1; + } else { + if (cred->min_dl_bandwidth_roaming > dl_bandwidth) + return 1; + if (cred->min_ul_bandwidth_roaming > ul_bandwidth) + return 1; + } + + return 0; +} + + +static int cred_over_max_bss_load(struct wpa_supplicant *wpa_s, + struct wpa_cred *cred, struct wpa_bss *bss) +{ + const u8 *ie; + int res; + + if (!cred->max_bss_load) + return 0; /* No BSS Load constraint specified */ + + ie = wpa_bss_get_ie(bss, WLAN_EID_BSS_LOAD); + if (ie == NULL || ie[1] < 3) + return 0; /* No BSS Load advertised */ + + res = interworking_home_sp_cred(wpa_s, cred, bss->anqp ? + bss->anqp->domain_name : NULL); + if (res <= 0) + return 0; /* Not a home network */ + + return ie[4] > cred->max_bss_load; +} + + +static int has_proto_match(const u8 *pos, const u8 *end, u8 proto) +{ + while (pos + 4 <= end) { + if (pos[0] == proto && pos[3] == 1 /* Open */) + return 1; + pos += 4; + } + + return 0; +} + + +static int has_proto_port_match(const u8 *pos, const u8 *end, u8 proto, + u16 port) +{ + while (pos + 4 <= end) { + if (pos[0] == proto && WPA_GET_LE16(&pos[1]) == port && + pos[3] == 1 /* Open */) + return 1; + pos += 4; + } + + return 0; +} + + +static int cred_conn_capab_missing(struct wpa_supplicant *wpa_s, + struct wpa_cred *cred, struct wpa_bss *bss) +{ + int res; + const u8 *capab, *end; + unsigned int i, j; + int *ports; + + if (!cred->num_req_conn_capab) + return 0; /* No connection capability constraint specified */ + + if (bss->anqp == NULL || bss->anqp->hs20_connection_capability == NULL) + return 0; /* No Connection Capability known - ignore constraint + */ + + res = interworking_home_sp_cred(wpa_s, cred, bss->anqp ? + bss->anqp->domain_name : NULL); + if (res > 0) + return 0; /* No constraint in home network */ + + capab = wpabuf_head(bss->anqp->hs20_connection_capability); + end = capab + wpabuf_len(bss->anqp->hs20_connection_capability); + + for (i = 0; i < cred->num_req_conn_capab; i++) { + ports = cred->req_conn_capab_port[i]; + if (!ports) { + if (!has_proto_match(capab, end, + cred->req_conn_capab_proto[i])) + return 1; + } else { + for (j = 0; ports[j] > -1; j++) { + if (!has_proto_port_match( + capab, end, + cred->req_conn_capab_proto[i], + ports[j])) + return 1; + } + } + } + + return 0; +} + + static struct wpa_cred * interworking_credentials_available_roaming_consortium( - struct wpa_supplicant *wpa_s, struct wpa_bss *bss) + struct wpa_supplicant *wpa_s, struct wpa_bss *bss, int ignore_bw, + int *excluded) { struct wpa_cred *cred, *selected = NULL; const u8 *ie; + int is_excluded = 0; ie = wpa_bss_get_ie(bss, WLAN_EID_ROAMING_CONSORTIUM); @@ -1073,16 +1285,33 @@ static struct wpa_cred * interworking_credentials_available_roaming_consortium( cred->roaming_consortium_len)) continue; - if (cred_excluded_ssid(cred, bss)) - continue; if (cred_no_required_oi_match(cred, bss)) continue; - - if (selected == NULL || - selected->priority < cred->priority) - selected = cred; + if (!ignore_bw && cred_below_min_backhaul(wpa_s, cred, bss)) + continue; + if (!ignore_bw && cred_over_max_bss_load(wpa_s, cred, bss)) + continue; + if (!ignore_bw && cred_conn_capab_missing(wpa_s, cred, bss)) + continue; + if (cred_excluded_ssid(cred, bss)) { + if (excluded == NULL) + continue; + if (selected == NULL) { + selected = cred; + is_excluded = 1; + } + } else { + if (selected == NULL || is_excluded || + cred_prio_cmp(selected, cred) < 0) { + selected = cred; + is_excluded = 0; + } + } } + if (excluded) + *excluded = is_excluded; + return selected; } @@ -1191,6 +1420,8 @@ static int interworking_set_eap_params(struct wpa_ssid *ssid, cred->domain_suffix_match) < 0) return -1; + ssid->eap.ocsp = cred->ocsp; + return 0; } @@ -1241,6 +1472,7 @@ static int interworking_connect_roaming_consortium( cred->eap_method->method == EAP_TYPE_TTLS) < 0) goto fail; + wpa_s->next_ssid = ssid; wpa_config_update_prio_list(wpa_s->conf); interworking_reconnect(wpa_s); @@ -1253,7 +1485,8 @@ fail: } -int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss) +static int interworking_connect_helper(struct wpa_supplicant *wpa_s, + struct wpa_bss *bss, int allow_excluded) { struct wpa_cred *cred, *cred_rc, *cred_3gpp; struct wpa_ssid *ssid; @@ -1261,6 +1494,7 @@ int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss) struct nai_realm_eap *eap = NULL; u16 count, i; char buf[100]; + int excluded = 0, *excl = allow_excluded ? &excluded : NULL; if (wpa_s->conf->cred == NULL || bss == NULL) return -1; @@ -1271,6 +1505,10 @@ int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss) return -1; } + wpa_printf(MSG_DEBUG, "Interworking: Considering BSS " MACSTR + " for connection (allow_excluded=%d)", + MAC2STR(bss->bssid), allow_excluded); + if (!wpa_bss_get_ie(bss, WLAN_EID_RSN)) { /* * We currently support only HS 2.0 networks and those are @@ -1281,35 +1519,80 @@ int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss) return -1; } - cred_rc = interworking_credentials_available_roaming_consortium(wpa_s, - bss); + cred_rc = interworking_credentials_available_roaming_consortium( + wpa_s, bss, 0, excl); if (cred_rc) { wpa_printf(MSG_DEBUG, "Interworking: Highest roaming " - "consortium matching credential priority %d", - cred_rc->priority); + "consortium matching credential priority %d " + "sp_priority %d", + cred_rc->priority, cred_rc->sp_priority); + if (allow_excluded && excl && !(*excl)) + excl = NULL; } - cred = interworking_credentials_available_realm(wpa_s, bss); + cred = interworking_credentials_available_realm(wpa_s, bss, 0, excl); if (cred) { wpa_printf(MSG_DEBUG, "Interworking: Highest NAI Realm list " - "matching credential priority %d", - cred->priority); + "matching credential priority %d sp_priority %d", + cred->priority, cred->sp_priority); + if (allow_excluded && excl && !(*excl)) + excl = NULL; } - cred_3gpp = interworking_credentials_available_3gpp(wpa_s, bss); + cred_3gpp = interworking_credentials_available_3gpp(wpa_s, bss, 0, + excl); if (cred_3gpp) { wpa_printf(MSG_DEBUG, "Interworking: Highest 3GPP matching " - "credential priority %d", cred_3gpp->priority); + "credential priority %d sp_priority %d", + cred_3gpp->priority, cred_3gpp->sp_priority); + if (allow_excluded && excl && !(*excl)) + excl = NULL; + } + + if (!cred_rc && !cred && !cred_3gpp) { + wpa_printf(MSG_DEBUG, "Interworking: No full credential matches - consider options without BW(etc.) limits"); + cred_rc = interworking_credentials_available_roaming_consortium( + wpa_s, bss, 1, excl); + if (cred_rc) { + wpa_printf(MSG_DEBUG, "Interworking: Highest roaming " + "consortium matching credential priority %d " + "sp_priority %d (ignore BW)", + cred_rc->priority, cred_rc->sp_priority); + if (allow_excluded && excl && !(*excl)) + excl = NULL; + } + + cred = interworking_credentials_available_realm(wpa_s, bss, 1, + excl); + if (cred) { + wpa_printf(MSG_DEBUG, "Interworking: Highest NAI Realm " + "list matching credential priority %d " + "sp_priority %d (ignore BW)", + cred->priority, cred->sp_priority); + if (allow_excluded && excl && !(*excl)) + excl = NULL; + } + + cred_3gpp = interworking_credentials_available_3gpp(wpa_s, bss, + 1, excl); + if (cred_3gpp) { + wpa_printf(MSG_DEBUG, "Interworking: Highest 3GPP " + "matching credential priority %d " + "sp_priority %d (ignore BW)", + cred_3gpp->priority, cred_3gpp->sp_priority); + if (allow_excluded && excl && !(*excl)) + excl = NULL; + } } if (cred_rc && - (cred == NULL || cred_rc->priority >= cred->priority) && - (cred_3gpp == NULL || cred_rc->priority >= cred_3gpp->priority)) + (cred == NULL || cred_prio_cmp(cred_rc, cred) >= 0) && + (cred_3gpp == NULL || cred_prio_cmp(cred_rc, cred_3gpp) >= 0)) return interworking_connect_roaming_consortium(wpa_s, cred_rc, bss); if (cred_3gpp && - (cred == NULL || cred_3gpp->priority >= cred->priority)) { + (cred == NULL || cred_prio_cmp(cred_3gpp, cred) >= 0)) { return interworking_connect_3gpp(wpa_s, cred_3gpp, bss); } @@ -1443,6 +1726,7 @@ int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss) nai_realm_free(realm, count); + wpa_s->next_ssid = ssid; wpa_config_update_prio_list(wpa_s->conf); interworking_reconnect(wpa_s); @@ -1456,13 +1740,21 @@ fail: } +int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss) +{ + return interworking_connect_helper(wpa_s, bss, 1); +} + + static struct wpa_cred * interworking_credentials_available_3gpp( - struct wpa_supplicant *wpa_s, struct wpa_bss *bss) + struct wpa_supplicant *wpa_s, struct wpa_bss *bss, int ignore_bw, + int *excluded) { struct wpa_cred *selected = NULL; #ifdef INTERWORKING_3GPP struct wpa_cred *cred; int ret; + int is_excluded = 0; if (bss->anqp == NULL || bss->anqp->anqp_3gpp == NULL) return NULL; @@ -1534,26 +1826,49 @@ static struct wpa_cred * interworking_credentials_available_3gpp( ret = plmn_id_match(bss->anqp->anqp_3gpp, imsi, mnc_len); wpa_printf(MSG_DEBUG, "PLMN match %sfound", ret ? "" : "not "); if (ret) { - if (cred_excluded_ssid(cred, bss)) - continue; if (cred_no_required_oi_match(cred, bss)) continue; - if (selected == NULL || - selected->priority < cred->priority) - selected = cred; + if (!ignore_bw && + cred_below_min_backhaul(wpa_s, cred, bss)) + continue; + if (!ignore_bw && + cred_over_max_bss_load(wpa_s, cred, bss)) + continue; + if (!ignore_bw && + cred_conn_capab_missing(wpa_s, cred, bss)) + continue; + if (cred_excluded_ssid(cred, bss)) { + if (excluded == NULL) + continue; + if (selected == NULL) { + selected = cred; + is_excluded = 1; + } + } else { + if (selected == NULL || is_excluded || + cred_prio_cmp(selected, cred) < 0) { + selected = cred; + is_excluded = 0; + } + } } } + + if (excluded) + *excluded = is_excluded; #endif /* INTERWORKING_3GPP */ return selected; } static struct wpa_cred * interworking_credentials_available_realm( - struct wpa_supplicant *wpa_s, struct wpa_bss *bss) + struct wpa_supplicant *wpa_s, struct wpa_bss *bss, int ignore_bw, + int *excluded) { struct wpa_cred *cred, *selected = NULL; struct nai_realm *realm; u16 count, i; + int is_excluded = 0; if (bss->anqp == NULL || bss->anqp->nai_realm == NULL) return NULL; @@ -1578,13 +1893,32 @@ static struct wpa_cred * interworking_credentials_available_realm( if (!nai_realm_match(&realm[i], cred->realm)) continue; if (nai_realm_find_eap(cred, &realm[i])) { - if (cred_excluded_ssid(cred, bss)) - continue; if (cred_no_required_oi_match(cred, bss)) continue; - if (selected == NULL || - selected->priority < cred->priority) - selected = cred; + if (!ignore_bw && + cred_below_min_backhaul(wpa_s, cred, bss)) + continue; + if (!ignore_bw && + cred_over_max_bss_load(wpa_s, cred, bss)) + continue; + if (!ignore_bw && + cred_conn_capab_missing(wpa_s, cred, bss)) + continue; + if (cred_excluded_ssid(cred, bss)) { + if (excluded == NULL) + continue; + if (selected == NULL) { + selected = cred; + is_excluded = 1; + } + } else { + if (selected == NULL || is_excluded || + cred_prio_cmp(selected, cred) < 0) + { + selected = cred; + is_excluded = 0; + } + } break; } } @@ -1592,14 +1926,19 @@ static struct wpa_cred * interworking_credentials_available_realm( nai_realm_free(realm, count); + if (excluded) + *excluded = is_excluded; + return selected; } -static struct wpa_cred * interworking_credentials_available( - struct wpa_supplicant *wpa_s, struct wpa_bss *bss) +static struct wpa_cred * interworking_credentials_available_helper( + struct wpa_supplicant *wpa_s, struct wpa_bss *bss, int ignore_bw, + int *excluded) { struct wpa_cred *cred, *cred2; + int excluded1, excluded2; if (disallowed_bssid(wpa_s, bss->bssid) || disallowed_ssid(wpa_s, bss->ssid, bss->ssid_len)) { @@ -1608,26 +1947,56 @@ static struct wpa_cred * interworking_credentials_available( return NULL; } - cred = interworking_credentials_available_realm(wpa_s, bss); - cred2 = interworking_credentials_available_3gpp(wpa_s, bss); - if (cred && cred2 && cred2->priority >= cred->priority) + cred = interworking_credentials_available_realm(wpa_s, bss, ignore_bw, + &excluded1); + cred2 = interworking_credentials_available_3gpp(wpa_s, bss, ignore_bw, + &excluded2); + if (cred && cred2 && + (cred_prio_cmp(cred2, cred) >= 0 || (!excluded2 && excluded1))) { cred = cred2; - if (!cred) + excluded1 = excluded2; + } + if (!cred) { cred = cred2; + excluded1 = excluded2; + } - cred2 = interworking_credentials_available_roaming_consortium(wpa_s, - bss); - if (cred && cred2 && cred2->priority >= cred->priority) + cred2 = interworking_credentials_available_roaming_consortium( + wpa_s, bss, ignore_bw, &excluded2); + if (cred && cred2 && + (cred_prio_cmp(cred2, cred) >= 0 || (!excluded2 && excluded1))) { cred = cred2; - if (!cred) + excluded1 = excluded2; + } + if (!cred) { cred = cred2; + excluded1 = excluded2; + } + if (excluded) + *excluded = excluded1; return cred; } -static int domain_name_list_contains(struct wpabuf *domain_names, - const char *domain) +static struct wpa_cred * interworking_credentials_available( + struct wpa_supplicant *wpa_s, struct wpa_bss *bss, int *excluded) +{ + struct wpa_cred *cred; + + if (excluded) + *excluded = 0; + cred = interworking_credentials_available_helper(wpa_s, bss, 0, + excluded); + if (cred) + return cred; + return interworking_credentials_available_helper(wpa_s, bss, 1, + excluded); +} + + +int domain_name_list_contains(struct wpabuf *domain_names, + const char *domain, int exact_match) { const u8 *pos, *end; size_t len; @@ -1645,6 +2014,12 @@ static int domain_name_list_contains(struct wpabuf *domain_names, if (pos[0] == len && os_strncasecmp(domain, (const char *) (pos + 1), len) == 0) return 1; + if (!exact_match && pos[0] > len && pos[pos[0] - len] == '.') { + const char *ap = (const char *) (pos + 1); + int offset = pos[0] - len; + if (os_strncasecmp(domain, ap + offset, len) == 0) + return 1; + } pos += 1 + pos[0]; } @@ -1687,7 +2062,7 @@ int interworking_home_sp_cred(struct wpa_supplicant *wpa_s, wpa_printf(MSG_DEBUG, "Interworking: Search for match " "with SIM/USIM domain %s", realm); if (realm && - domain_name_list_contains(domain_names, realm)) + domain_name_list_contains(domain_names, realm, 1)) return 1; if (realm) ret = 0; @@ -1700,7 +2075,7 @@ int interworking_home_sp_cred(struct wpa_supplicant *wpa_s, for (i = 0; i < cred->num_domain; i++) { wpa_printf(MSG_DEBUG, "Interworking: Search for match with " "home SP FQDN %s", cred->domain[i]); - if (domain_name_list_contains(domain_names, cred->domain[i])) + if (domain_name_list_contains(domain_names, cred->domain[i], 1)) return 1; } @@ -1752,19 +2127,127 @@ static int interworking_find_network_match(struct wpa_supplicant *wpa_s) } +static int roaming_partner_match(struct wpa_supplicant *wpa_s, + struct roaming_partner *partner, + struct wpabuf *domain_names) +{ + wpa_printf(MSG_DEBUG, "Interworking: Comparing roaming_partner info fqdn='%s' exact_match=%d priority=%u country='%s'", + partner->fqdn, partner->exact_match, partner->priority, + partner->country); + wpa_hexdump_ascii(MSG_DEBUG, "Interworking: Domain names", + wpabuf_head(domain_names), + wpabuf_len(domain_names)); + if (!domain_name_list_contains(domain_names, partner->fqdn, + partner->exact_match)) + return 0; + /* TODO: match Country */ + return 1; +} + + +static u8 roaming_prio(struct wpa_supplicant *wpa_s, struct wpa_cred *cred, + struct wpa_bss *bss) +{ + size_t i; + + if (bss->anqp == NULL || bss->anqp->domain_name == NULL) { + wpa_printf(MSG_DEBUG, "Interworking: No ANQP domain name info -> use default roaming partner priority 128"); + return 128; /* cannot check preference with domain name */ + } + + if (interworking_home_sp_cred(wpa_s, cred, bss->anqp->domain_name) > 0) + { + wpa_printf(MSG_DEBUG, "Interworking: Determined to be home SP -> use maximum preference 0 as roaming partner priority"); + return 0; /* max preference for home SP network */ + } + + for (i = 0; i < cred->num_roaming_partner; i++) { + if (roaming_partner_match(wpa_s, &cred->roaming_partner[i], + bss->anqp->domain_name)) { + wpa_printf(MSG_DEBUG, "Interworking: Roaming partner preference match - priority %u", + cred->roaming_partner[i].priority); + return cred->roaming_partner[i].priority; + } + } + + wpa_printf(MSG_DEBUG, "Interworking: No roaming partner preference match - use default roaming partner priority 128"); + return 128; +} + + +static struct wpa_bss * pick_best_roaming_partner(struct wpa_supplicant *wpa_s, + struct wpa_bss *selected, + struct wpa_cred *cred) +{ + struct wpa_bss *bss; + u8 best_prio, prio; + struct wpa_cred *cred2; + + /* + * Check if any other BSS is operated by a more preferred roaming + * partner. + */ + + best_prio = roaming_prio(wpa_s, cred, selected); + wpa_printf(MSG_DEBUG, "Interworking: roaming_prio=%u for selected BSS " + MACSTR " (cred=%d)", best_prio, MAC2STR(selected->bssid), + cred->id); + + dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) { + if (bss == selected) + continue; + cred2 = interworking_credentials_available(wpa_s, bss, NULL); + if (!cred2) + continue; + if (!wpa_bss_get_ie(bss, WLAN_EID_RSN)) + continue; + prio = roaming_prio(wpa_s, cred2, bss); + wpa_printf(MSG_DEBUG, "Interworking: roaming_prio=%u for BSS " + MACSTR " (cred=%d)", prio, MAC2STR(bss->bssid), + cred2->id); + if (prio < best_prio) { + int bh1, bh2, load1, load2, conn1, conn2; + bh1 = cred_below_min_backhaul(wpa_s, cred, selected); + load1 = cred_over_max_bss_load(wpa_s, cred, selected); + conn1 = cred_conn_capab_missing(wpa_s, cred, selected); + bh2 = cred_below_min_backhaul(wpa_s, cred2, bss); + load2 = cred_over_max_bss_load(wpa_s, cred2, bss); + conn2 = cred_conn_capab_missing(wpa_s, cred2, bss); + wpa_printf(MSG_DEBUG, "Interworking: old: %d %d %d new: %d %d %d", + bh1, load1, conn1, bh2, load2, conn2); + if (bh1 || load1 || conn1 || !(bh2 || load2 || conn2)) { + wpa_printf(MSG_DEBUG, "Interworking: Better roaming partner " MACSTR " selected", MAC2STR(bss->bssid)); + best_prio = prio; + selected = bss; + } + } + } + + return selected; +} + + static void interworking_select_network(struct wpa_supplicant *wpa_s) { struct wpa_bss *bss, *selected = NULL, *selected_home = NULL; - int selected_prio = -999999, selected_home_prio = -999999; + struct wpa_bss *selected2 = NULL, *selected2_home = NULL; unsigned int count = 0; const char *type; int res; - struct wpa_cred *cred; + struct wpa_cred *cred, *selected_cred = NULL; + struct wpa_cred *selected_home_cred = NULL; + struct wpa_cred *selected2_cred = NULL; + struct wpa_cred *selected2_home_cred = NULL; wpa_s->network_select = 0; + wpa_printf(MSG_DEBUG, "Interworking: Select network (auto_select=%d)", + wpa_s->auto_select); dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) { - cred = interworking_credentials_available(wpa_s, bss); + int excluded = 0; + int bh, bss_load, conn_capab; + cred = interworking_credentials_available(wpa_s, bss, + &excluded); if (!cred) continue; if (!wpa_bss_get_ie(bss, WLAN_EID_RSN)) { @@ -1777,7 +2260,8 @@ static void interworking_select_network(struct wpa_supplicant *wpa_s) "RSN", MAC2STR(bss->bssid)); continue; } - count++; + if (!excluded) + count++; res = interworking_home_sp(wpa_s, bss->anqp ? bss->anqp->domain_name : NULL); if (res > 0) @@ -1786,29 +2270,75 @@ static void interworking_select_network(struct wpa_supplicant *wpa_s) type = "roaming"; else type = "unknown"; - wpa_msg(wpa_s, MSG_INFO, INTERWORKING_AP MACSTR " type=%s", - MAC2STR(bss->bssid), type); + bh = cred_below_min_backhaul(wpa_s, cred, bss); + bss_load = cred_over_max_bss_load(wpa_s, cred, bss); + conn_capab = cred_conn_capab_missing(wpa_s, cred, bss); + wpa_msg(wpa_s, MSG_INFO, "%s" MACSTR " type=%s%s%s%s id=%d priority=%d sp_priority=%d", + excluded ? INTERWORKING_BLACKLISTED : INTERWORKING_AP, + MAC2STR(bss->bssid), type, + bh ? " below_min_backhaul=1" : "", + bss_load ? " over_max_bss_load=1" : "", + conn_capab ? " conn_capab_missing=1" : "", + cred->id, cred->priority, cred->sp_priority); + if (excluded) + continue; if (wpa_s->auto_select || (wpa_s->conf->auto_interworking && wpa_s->auto_network_select)) { - if (selected == NULL || - cred->priority > selected_prio) { - selected = bss; - selected_prio = cred->priority; - } - if (res > 0 && - (selected_home == NULL || - cred->priority > selected_home_prio)) { - selected_home = bss; - selected_home_prio = cred->priority; + if (bh || bss_load || conn_capab) { + if (selected2_cred == NULL || + cred_prio_cmp(cred, selected2_cred) > 0) { + wpa_printf(MSG_DEBUG, "Interworking: Mark as selected2"); + selected2 = bss; + selected2_cred = cred; + } + if (res > 0 && + (selected2_home_cred == NULL || + cred_prio_cmp(cred, selected2_home_cred) > + 0)) { + wpa_printf(MSG_DEBUG, "Interworking: Mark as selected2_home"); + selected2_home = bss; + selected2_home_cred = cred; + } + } else { + if (selected_cred == NULL || + cred_prio_cmp(cred, selected_cred) > 0) { + wpa_printf(MSG_DEBUG, "Interworking: Mark as selected"); + selected = bss; + selected_cred = cred; + } + if (res > 0 && + (selected_home_cred == NULL || + cred_prio_cmp(cred, selected_home_cred) > + 0)) { + wpa_printf(MSG_DEBUG, "Interworking: Mark as selected_home"); + selected_home = bss; + selected_home_cred = cred; + } } } } if (selected_home && selected_home != selected && - selected_home_prio >= selected_prio) { + selected_home_cred && + (selected_cred == NULL || + cred_prio_cmp(selected_home_cred, selected_cred) >= 0)) { /* Prefer network operated by the Home SP */ + wpa_printf(MSG_DEBUG, "Interworking: Overrided selected with selected_home"); selected = selected_home; + selected_cred = selected_home_cred; + } + + if (!selected) { + if (selected2_home) { + wpa_printf(MSG_DEBUG, "Interworking: Use home BSS with BW limit mismatch since no other network could be selected"); + selected = selected2_home; + selected_cred = selected2_home_cred; + } else if (selected2) { + wpa_printf(MSG_DEBUG, "Interworking: Use visited BSS with BW limit mismatch since no other network could be selected"); + selected = selected2; + selected_cred = selected2_cred; + } } if (count == 0) { @@ -1837,8 +2367,18 @@ static void interworking_select_network(struct wpa_supplicant *wpa_s) "with matching credentials found"); } - if (selected) + if (selected) { + wpa_printf(MSG_DEBUG, "Interworking: Selected " MACSTR, + MAC2STR(selected->bssid)); + selected = pick_best_roaming_partner(wpa_s, selected, + selected_cred); + wpa_printf(MSG_DEBUG, "Interworking: Selected " MACSTR + " (after best roaming partner selection)", + MAC2STR(selected->bssid)); + wpa_msg(wpa_s, MSG_INFO, INTERWORKING_SELECTED MACSTR, + MAC2STR(selected->bssid)); interworking_connect(wpa_s, selected); + } } @@ -1885,8 +2425,21 @@ static void interworking_next_anqp_fetch(struct wpa_supplicant *wpa_s) int found = 0; const u8 *ie; - if (eloop_terminated() || !wpa_s->fetch_anqp_in_progress) + wpa_printf(MSG_DEBUG, "Interworking: next_anqp_fetch - " + "fetch_anqp_in_progress=%d fetch_osu_icon_in_progress=%d", + wpa_s->fetch_anqp_in_progress, + wpa_s->fetch_osu_icon_in_progress); + + if (eloop_terminated() || !wpa_s->fetch_anqp_in_progress) { + wpa_printf(MSG_DEBUG, "Interworking: Stop next-ANQP-fetch"); + return; + } + + if (wpa_s->fetch_osu_icon_in_progress) { + wpa_printf(MSG_DEBUG, "Interworking: Next icon (in progress)"); + hs20_next_osu_icon(wpa_s); return; + } dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) { if (!(bss->caps & IEEE80211_CAP_ESS)) @@ -1920,6 +2473,17 @@ static void interworking_next_anqp_fetch(struct wpa_supplicant *wpa_s) } if (found == 0) { + if (wpa_s->fetch_osu_info) { + if (wpa_s->num_prov_found == 0 && + wpa_s->num_osu_scans < 3) { + wpa_printf(MSG_DEBUG, "HS 2.0: No OSU providers seen - try to scan again"); + hs20_start_osu_scan(wpa_s); + return; + } + wpa_printf(MSG_DEBUG, "Interworking: Next icon"); + hs20_osu_icon_fetch(wpa_s); + return; + } wpa_msg(wpa_s, MSG_INFO, "ANQP fetch completed"); wpa_s->fetch_anqp_in_progress = 0; if (wpa_s->network_select) @@ -1947,6 +2511,7 @@ int interworking_fetch_anqp(struct wpa_supplicant *wpa_s) wpa_s->network_select = 0; wpa_s->fetch_all_anqp = 1; + wpa_s->fetch_osu_info = 0; interworking_start_fetch_anqp(wpa_s); @@ -2144,14 +2709,22 @@ void anqp_resp_cb(void *ctx, const u8 *dst, u8 dialog_token, u16 slen; struct wpa_bss *bss = NULL, *tmp; - if (result != GAS_QUERY_SUCCESS) + wpa_printf(MSG_DEBUG, "Interworking: anqp_resp_cb dst=" MACSTR + " dialog_token=%u result=%d status_code=%u", + MAC2STR(dst), dialog_token, result, status_code); + if (result != GAS_QUERY_SUCCESS) { + if (wpa_s->fetch_osu_icon_in_progress) + hs20_icon_fetch_failed(wpa_s); return; + } pos = wpabuf_head(adv_proto); if (wpabuf_len(adv_proto) < 4 || pos[0] != WLAN_EID_ADV_PROTO || pos[1] < 2 || pos[3] != ACCESS_NETWORK_QUERY_PROTOCOL) { wpa_printf(MSG_DEBUG, "ANQP: Unexpected Advertisement " "Protocol in response"); + if (wpa_s->fetch_osu_icon_in_progress) + hs20_icon_fetch_failed(wpa_s); return; } @@ -2191,6 +2764,8 @@ void anqp_resp_cb(void *ctx, const u8 *dst, u8 dialog_token, slen); pos += slen; } + + hs20_notify_parse_done(wpa_s); } @@ -2211,6 +2786,7 @@ int interworking_select(struct wpa_supplicant *wpa_s, int auto_select, wpa_s->auto_network_select = 0; wpa_s->auto_select = !!auto_select; wpa_s->fetch_all_anqp = 0; + wpa_s->fetch_osu_info = 0; wpa_printf(MSG_DEBUG, "Interworking: Start scan for network " "selection"); wpa_s->scan_res_handler = interworking_scan_res_handler; diff --git a/wpa_supplicant/interworking.h b/wpa_supplicant/interworking.h index c8e70938..bb0ceb81 100644 --- a/wpa_supplicant/interworking.h +++ b/wpa_supplicant/interworking.h @@ -29,5 +29,7 @@ void interworking_start_fetch_anqp(struct wpa_supplicant *wpa_s); int interworking_home_sp_cred(struct wpa_supplicant *wpa_s, struct wpa_cred *cred, struct wpabuf *domain_names); +int domain_name_list_contains(struct wpabuf *domain_names, + const char *domain, int exact_match); #endif /* INTERWORKING_H */ diff --git a/wpa_supplicant/notify.c b/wpa_supplicant/notify.c index a82fbf3a..2db1d544 100644 --- a/wpa_supplicant/notify.c +++ b/wpa_supplicant/notify.c @@ -252,6 +252,8 @@ void wpas_notify_persistent_group_removed(struct wpa_supplicant *wpa_s, void wpas_notify_network_removed(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid) { + if (wpa_s->next_ssid == ssid) + wpa_s->next_ssid = NULL; if (wpa_s->wpa) wpa_sm_pmksa_cache_flush(wpa_s->wpa, ssid); if (!ssid->p2p_group && wpa_s->global->p2p_group_formation != wpa_s) diff --git a/wpa_supplicant/p2p_supplicant.c b/wpa_supplicant/p2p_supplicant.c index fa75fa5d..b8781983 100644 --- a/wpa_supplicant/p2p_supplicant.c +++ b/wpa_supplicant/p2p_supplicant.c @@ -722,12 +722,10 @@ static int wpas_p2p_store_persistent_group(struct wpa_supplicant *wpa_s, changed = 1; } -#ifndef CONFIG_NO_CONFIG_WRITE if (changed && wpa_s->conf->update_config && wpa_config_write(wpa_s->confname, wpa_s->conf)) { wpa_printf(MSG_DEBUG, "P2P: Failed to update configuration"); } -#endif /* CONFIG_NO_CONFIG_WRITE */ return s->id; } @@ -795,11 +793,9 @@ static void wpas_p2p_add_persistent_group_client(struct wpa_supplicant *wpa_s, addr, ETH_ALEN); } -#ifndef CONFIG_NO_CONFIG_WRITE if (wpa_s->parent->conf->update_config && wpa_config_write(wpa_s->parent->confname, wpa_s->parent->conf)) wpa_printf(MSG_DEBUG, "P2P: Failed to update configuration"); -#endif /* CONFIG_NO_CONFIG_WRITE */ } @@ -3152,11 +3148,9 @@ static void wpas_remove_persistent_peer(struct wpa_supplicant *wpa_s, ssid->p2p_client_list + (i + 1) * ETH_ALEN, (ssid->num_p2p_clients - i - 1) * ETH_ALEN); ssid->num_p2p_clients--; -#ifndef CONFIG_NO_CONFIG_WRITE if (wpa_s->parent->conf->update_config && wpa_config_write(wpa_s->parent->confname, wpa_s->parent->conf)) wpa_printf(MSG_DEBUG, "P2P: Failed to update configuration"); -#endif /* CONFIG_NO_CONFIG_WRITE */ } @@ -6816,11 +6810,9 @@ void wpas_p2p_new_psk_cb(struct wpa_supplicant *wpa_s, const u8 *mac_addr, } dl_list_add(&persistent->psk_list, &p->list); -#ifndef CONFIG_NO_CONFIG_WRITE if (wpa_s->parent->conf->update_config && wpa_config_write(wpa_s->parent->confname, wpa_s->parent->conf)) wpa_printf(MSG_DEBUG, "P2P: Failed to update configuration"); -#endif /* CONFIG_NO_CONFIG_WRITE */ } @@ -6831,14 +6823,10 @@ static void wpas_p2p_remove_psk(struct wpa_supplicant *wpa_s, int res; res = wpas_p2p_remove_psk_entry(wpa_s, s, addr, iface_addr); - if (res > 0) { -#ifndef CONFIG_NO_CONFIG_WRITE - if (wpa_s->conf->update_config && - wpa_config_write(wpa_s->confname, wpa_s->conf)) - wpa_dbg(wpa_s, MSG_DEBUG, - "P2P: Failed to update configuration"); -#endif /* CONFIG_NO_CONFIG_WRITE */ - } + if (res > 0 && wpa_s->conf->update_config && + wpa_config_write(wpa_s->confname, wpa_s->conf)) + wpa_dbg(wpa_s, MSG_DEBUG, + "P2P: Failed to update configuration"); } diff --git a/wpa_supplicant/scan.c b/wpa_supplicant/scan.c index 6c742d6d..f7eb5373 100644 --- a/wpa_supplicant/scan.c +++ b/wpa_supplicant/scan.c @@ -365,11 +365,17 @@ static void wpas_add_interworking_elements(struct wpa_supplicant *wpa_s, return; wpabuf_put_u8(buf, WLAN_EID_EXT_CAPAB); - wpabuf_put_u8(buf, 4); + wpabuf_put_u8(buf, 6); wpabuf_put_u8(buf, 0x00); wpabuf_put_u8(buf, 0x00); wpabuf_put_u8(buf, 0x00); wpabuf_put_u8(buf, 0x80); /* Bit 31 - Interworking */ + wpabuf_put_u8(buf, 0x00); +#ifdef CONFIG_HS20 + wpabuf_put_u8(buf, 0x40); /* Bit 46 - WNM-Notification */ +#else /* CONFIG_HS20 */ + wpabuf_put_u8(buf, 0x00); +#endif /* CONFIG_HS20 */ wpabuf_put_u8(buf, WLAN_EID_INTERWORKING); wpabuf_put_u8(buf, is_zero_ether_addr(wpa_s->conf->hessid) ? 1 : @@ -425,7 +431,7 @@ static struct wpabuf * wpa_supplicant_extra_ies(struct wpa_supplicant *wpa_s) #ifdef CONFIG_HS20 if (wpa_s->conf->hs20 && wpabuf_resize(&extra_ie, 7) == 0) - wpas_hs20_add_indication(extra_ie); + wpas_hs20_add_indication(extra_ie, -1); #endif /* CONFIG_HS20 */ return extra_ie; diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c index e712ac43..63beaefa 100644 --- a/wpa_supplicant/sme.c +++ b/wpa_supplicant/sme.c @@ -360,7 +360,8 @@ static void sme_send_authentication(struct wpa_supplicant *wpa_s, struct wpabuf *hs20; hs20 = wpabuf_alloc(20); if (hs20) { - wpas_hs20_add_indication(hs20); + int pps_mo_id = hs20_get_pps_mo_id(wpa_s, ssid); + wpas_hs20_add_indication(hs20, pps_mo_id); os_memcpy(wpa_s->sme.assoc_req_ie + wpa_s->sme.assoc_req_ie_len, wpabuf_head(hs20), wpabuf_len(hs20)); @@ -475,6 +476,11 @@ void sme_authenticate(struct wpa_supplicant *wpa_s, return; } + if (radio_work_pending(wpa_s, "sme-connect")) { + wpa_dbg(wpa_s, MSG_DEBUG, "SME: Reject sme_authenticate() call since pending work exist"); + return; + } + cwork = os_zalloc(sizeof(*cwork)); if (cwork == NULL) return; @@ -751,6 +757,10 @@ void sme_associate(struct wpa_supplicant *wpa_s, enum wpas_mode mode, params.wpa_proto = WPA_PROTO_WPA; wpa_sm_set_assoc_wpa_ie(wpa_s->wpa, elems.wpa_ie - 2, elems.wpa_ie_len + 2); + } else if (elems.osen) { + params.wpa_proto = WPA_PROTO_OSEN; + wpa_sm_set_assoc_wpa_ie(wpa_s->wpa, elems.osen - 2, + elems.osen_len + 2); } else wpa_sm_set_assoc_wpa_ie(wpa_s->wpa, NULL, 0); if (wpa_s->current_ssid && wpa_s->current_ssid->p2p_group) diff --git a/wpa_supplicant/wnm_sta.c b/wpa_supplicant/wnm_sta.c index 65b27838..0619f6db 100644 --- a/wpa_supplicant/wnm_sta.c +++ b/wpa_supplicant/wnm_sta.c @@ -18,6 +18,7 @@ #include "ctrl_iface.h" #include "bss.h" #include "wnm_sta.h" +#include "hs20_supplicant.h" #define MAX_TFS_IE_LEN 1024 #define WNM_MAX_NEIGHBOR_REPORT 10 @@ -751,6 +752,153 @@ int wnm_send_bss_transition_mgmt_query(struct wpa_supplicant *wpa_s, } +static void ieee802_11_rx_wnm_notif_req_wfa(struct wpa_supplicant *wpa_s, + const u8 *sa, const u8 *data, + int len) +{ + const u8 *pos, *end, *next; + u8 ie, ie_len; + + pos = data; + end = data + len; + + while (pos + 1 < end) { + ie = *pos++; + ie_len = *pos++; + wpa_printf(MSG_DEBUG, "WNM: WFA subelement %u len %u", + ie, ie_len); + if (ie_len > end - pos) { + wpa_printf(MSG_DEBUG, "WNM: Not enough room for " + "subelement"); + break; + } + next = pos + ie_len; + if (ie_len < 4) { + pos = next; + continue; + } + wpa_printf(MSG_DEBUG, "WNM: Subelement OUI %06x type %u", + WPA_GET_BE24(pos), pos[3]); + +#ifdef CONFIG_HS20 + if (ie == WLAN_EID_VENDOR_SPECIFIC && ie_len >= 5 && + WPA_GET_BE24(pos) == OUI_WFA && + pos[3] == HS20_WNM_SUB_REM_NEEDED) { + /* Subscription Remediation subelement */ + const u8 *ie_end; + u8 url_len; + char *url; + u8 osu_method; + + wpa_printf(MSG_DEBUG, "WNM: Subscription Remediation " + "subelement"); + ie_end = pos + ie_len; + pos += 4; + url_len = *pos++; + if (url_len == 0) { + wpa_printf(MSG_DEBUG, "WNM: No Server URL included"); + url = NULL; + osu_method = 1; + } else { + if (pos + url_len + 1 > ie_end) { + wpa_printf(MSG_DEBUG, "WNM: Not enough room for Server URL (len=%u) and Server Method (left %d)", + url_len, + (int) (ie_end - pos)); + break; + } + url = os_malloc(url_len + 1); + if (url == NULL) + break; + os_memcpy(url, pos, url_len); + url[url_len] = '\0'; + osu_method = pos[url_len]; + } + hs20_rx_subscription_remediation(wpa_s, url, + osu_method); + os_free(url); + pos = next; + continue; + } + + if (ie == WLAN_EID_VENDOR_SPECIFIC && ie_len >= 8 && + WPA_GET_BE24(pos) == OUI_WFA && + pos[3] == HS20_WNM_DEAUTH_IMMINENT_NOTICE) { + const u8 *ie_end; + u8 url_len; + char *url; + u8 code; + u16 reauth_delay; + + ie_end = pos + ie_len; + pos += 4; + code = *pos++; + reauth_delay = WPA_GET_LE16(pos); + pos += 2; + url_len = *pos++; + wpa_printf(MSG_DEBUG, "WNM: HS 2.0 Deauthentication " + "Imminent - Reason Code %u " + "Re-Auth Delay %u URL Length %u", + code, reauth_delay, url_len); + if (pos + url_len > ie_end) + break; + url = os_malloc(url_len + 1); + if (url == NULL) + break; + os_memcpy(url, pos, url_len); + url[url_len] = '\0'; + hs20_rx_deauth_imminent_notice(wpa_s, code, + reauth_delay, url); + os_free(url); + pos = next; + continue; + } +#endif /* CONFIG_HS20 */ + + pos = next; + } +} + + +static void ieee802_11_rx_wnm_notif_req(struct wpa_supplicant *wpa_s, + const u8 *sa, const u8 *frm, int len) +{ + const u8 *pos, *end; + u8 dialog_token, type; + + /* Dialog Token [1] | Type [1] | Subelements */ + + if (len < 2 || sa == NULL) + return; + end = frm + len; + pos = frm; + dialog_token = *pos++; + type = *pos++; + + wpa_dbg(wpa_s, MSG_DEBUG, "WNM: Received WNM-Notification Request " + "(dialog_token %u type %u sa " MACSTR ")", + dialog_token, type, MAC2STR(sa)); + wpa_hexdump(MSG_DEBUG, "WNM-Notification Request subelements", + pos, end - pos); + + if (wpa_s->wpa_state != WPA_COMPLETED || + os_memcmp(sa, wpa_s->bssid, ETH_ALEN) != 0) { + wpa_dbg(wpa_s, MSG_DEBUG, "WNM: WNM-Notification frame not " + "from our AP - ignore it"); + return; + } + + switch (type) { + case 1: + ieee802_11_rx_wnm_notif_req_wfa(wpa_s, sa, pos, end - pos); + break; + default: + wpa_dbg(wpa_s, MSG_DEBUG, "WNM: Ignore unknown " + "WNM-Notification type %u", type); + break; + } +} + + void ieee802_11_rx_wnm_action(struct wpa_supplicant *wpa_s, const struct ieee80211_mgmt *mgmt, size_t len) { @@ -782,6 +930,9 @@ void ieee802_11_rx_wnm_action(struct wpa_supplicant *wpa_s, case WNM_SLEEP_MODE_RESP: ieee802_11_rx_wnmsleep_resp(wpa_s, pos, end - pos); break; + case WNM_NOTIFICATION_REQ: + ieee802_11_rx_wnm_notif_req(wpa_s, mgmt->sa, pos, end - pos); + break; default: wpa_printf(MSG_ERROR, "WNM: Unknown request"); break; diff --git a/wpa_supplicant/wpa_cli.c b/wpa_supplicant/wpa_cli.c index 6278806a..06911830 100644 --- a/wpa_supplicant/wpa_cli.c +++ b/wpa_supplicant/wpa_cli.c @@ -2298,6 +2298,37 @@ static int wpa_cli_cmd_get_nai_home_realm_list(struct wpa_ctrl *ctrl, int argc, return wpa_ctrl_command(ctrl, cmd); } + +static int wpa_cli_cmd_hs20_icon_request(struct wpa_ctrl *ctrl, int argc, + char *argv[]) +{ + char cmd[512]; + + if (argc < 2) { + printf("Command needs two arguments (dst mac addr and " + "icon name)\n"); + return -1; + } + + if (write_cmd(cmd, sizeof(cmd), "HS20_ICON_REQUEST", argc, argv) < 0) + return -1; + + return wpa_ctrl_command(ctrl, cmd); +} + + +static int wpa_cli_cmd_fetch_osu(struct wpa_ctrl *ctrl, int argc, char *argv[]) +{ + return wpa_ctrl_command(ctrl, "FETCH_OSU"); +} + + +static int wpa_cli_cmd_cancel_fetch_osu(struct wpa_ctrl *ctrl, int argc, + char *argv[]) +{ + return wpa_ctrl_command(ctrl, "CANCEL_FETCH_OSU"); +} + #endif /* CONFIG_HS20 */ @@ -2831,6 +2862,14 @@ static struct wpa_cli_cmd wpa_cli_commands[] = { { "nai_home_realm_list", wpa_cli_cmd_get_nai_home_realm_list, wpa_cli_complete_bss, cli_cmd_flag_none, "<addr> <home realm> = get HS20 nai home realm list" }, + { "hs20_icon_request", wpa_cli_cmd_hs20_icon_request, + wpa_cli_complete_bss, cli_cmd_flag_none, + "<addr> <icon name> = get Hotspot 2.0 OSU icon" }, + { "fetch_osu", wpa_cli_cmd_fetch_osu, NULL, cli_cmd_flag_none, + "= fetch OSU provider information from all APs" }, + { "cancel_fetch_osu", wpa_cli_cmd_cancel_fetch_osu, NULL, + cli_cmd_flag_none, + "= cancel fetch_osu command" }, #endif /* CONFIG_HS20 */ { "sta_autoconnect", wpa_cli_cmd_sta_autoconnect, NULL, cli_cmd_flag_none, @@ -3181,6 +3220,10 @@ static void wpa_cli_action_process(const char *msg) wpa_cli_exec(action_file, ctrl_ifname, pos); } else if (str_match(pos, ESS_DISASSOC_IMMINENT)) { wpa_cli_exec(action_file, ctrl_ifname, pos); + } else if (str_match(pos, HS20_SUBSCRIPTION_REMEDIATION)) { + wpa_cli_exec(action_file, ctrl_ifname, pos); + } else if (str_match(pos, HS20_DEAUTH_IMMINENT_NOTICE)) { + wpa_cli_exec(action_file, ctrl_ifname, pos); } else if (str_match(pos, WPA_EVENT_TERMINATING)) { printf("wpa_supplicant is terminating - stop monitoring\n"); wpa_cli_quit = 1; diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index e942b622..ad1a03e1 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -483,6 +483,10 @@ static void wpa_supplicant_cleanup(struct wpa_supplicant *wpa_s) os_free(wpa_s->last_scan_res); wpa_s->last_scan_res = NULL; + +#ifdef CONFIG_HS20 + hs20_free_osu_prov(wpa_s); +#endif /* CONFIG_HS20 */ } @@ -933,13 +937,14 @@ int wpa_supplicant_set_suites(struct wpa_supplicant *wpa_s, { struct wpa_ie_data ie; int sel, proto; - const u8 *bss_wpa, *bss_rsn; + const u8 *bss_wpa, *bss_rsn, *bss_osen; if (bss) { bss_wpa = wpa_bss_get_vendor_ie(bss, WPA_IE_VENDOR_TYPE); bss_rsn = wpa_bss_get_ie(bss, WLAN_EID_RSN); + bss_osen = wpa_bss_get_vendor_ie(bss, OSEN_IE_VENDOR_TYPE); } else - bss_wpa = bss_rsn = NULL; + bss_wpa = bss_rsn = bss_osen = NULL; if (bss_rsn && (ssid->proto & WPA_PROTO_RSN) && wpa_parse_wpa_ie(bss_rsn, 2 + bss_rsn[1], &ie) == 0 && @@ -955,11 +960,22 @@ int wpa_supplicant_set_suites(struct wpa_supplicant *wpa_s, (ie.key_mgmt & ssid->key_mgmt)) { wpa_dbg(wpa_s, MSG_DEBUG, "WPA: using IEEE 802.11i/D3.0"); proto = WPA_PROTO_WPA; +#ifdef CONFIG_HS20 + } else if (bss_osen && (ssid->proto & WPA_PROTO_OSEN)) { + wpa_dbg(wpa_s, MSG_DEBUG, "HS 2.0: using OSEN"); + /* TODO: parse OSEN element */ + ie.group_cipher = WPA_CIPHER_CCMP; + ie.pairwise_cipher = WPA_CIPHER_CCMP; + ie.key_mgmt = WPA_KEY_MGMT_OSEN; + proto = WPA_PROTO_OSEN; +#endif /* CONFIG_HS20 */ } else if (bss) { wpa_msg(wpa_s, MSG_WARNING, "WPA: Failed to select WPA/RSN"); return -1; } else { - if (ssid->proto & WPA_PROTO_RSN) + if (ssid->proto & WPA_PROTO_OSEN) + proto = WPA_PROTO_OSEN; + else if (ssid->proto & WPA_PROTO_RSN) proto = WPA_PROTO_RSN; else proto = WPA_PROTO_WPA; @@ -992,7 +1008,7 @@ int wpa_supplicant_set_suites(struct wpa_supplicant *wpa_s, wpa_s->wpa_proto = proto; wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_PROTO, proto); wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_RSN_ENABLED, - !!(ssid->proto & WPA_PROTO_RSN)); + !!(ssid->proto & (WPA_PROTO_RSN | WPA_PROTO_OSEN))); if (bss || !wpa_s->ap_ies_from_associnfo) { if (wpa_sm_set_ap_wpa_ie(wpa_s->wpa, bss_wpa, @@ -1063,6 +1079,11 @@ int wpa_supplicant_set_suites(struct wpa_supplicant *wpa_s, } else if (sel & WPA_KEY_MGMT_WPA_NONE) { wpa_s->key_mgmt = WPA_KEY_MGMT_WPA_NONE; wpa_dbg(wpa_s, MSG_DEBUG, "WPA: using KEY_MGMT WPA-NONE"); +#ifdef CONFIG_HS20 + } else if (sel & WPA_KEY_MGMT_OSEN) { + wpa_s->key_mgmt = WPA_KEY_MGMT_OSEN; + wpa_dbg(wpa_s, MSG_DEBUG, "HS 2.0: using KEY_MGMT OSEN"); +#endif /* CONFIG_HS20 */ } else { wpa_msg(wpa_s, MSG_WARNING, "WPA: Failed to select " "authenticated key management type"); @@ -1208,6 +1229,10 @@ static void wpas_ext_capab_byte(struct wpa_supplicant *wpa_s, u8 *pos, int idx) #endif /* CONFIG_INTERWORKING */ break; case 5: /* Bits 40-47 */ +#ifdef CONFIG_HS20 + if (wpa_s->conf->hs20) + *pos |= 0x40; /* Bit 46 - WNM-Notification */ +#endif /* CONFIG_HS20 */ break; case 6: /* Bits 48-55 */ break; @@ -1218,7 +1243,7 @@ static void wpas_ext_capab_byte(struct wpa_supplicant *wpa_s, u8 *pos, int idx) int wpas_build_ext_capab(struct wpa_supplicant *wpa_s, u8 *buf) { u8 *pos = buf; - u8 len = 4, i; + u8 len = 6, i; if (len < wpa_s->extended_capa_len) len = wpa_s->extended_capa_len; @@ -1366,6 +1391,11 @@ void wpa_supplicant_associate(struct wpa_supplicant *wpa_s, return; } + if (radio_work_pending(wpa_s, "connect")) { + wpa_dbg(wpa_s, MSG_DEBUG, "Reject wpa_supplicant_associate() call since pending work exist"); + return; + } + cwork = os_zalloc(sizeof(*cwork)); if (cwork == NULL) return; @@ -1581,7 +1611,8 @@ static void wpas_start_assoc_cb(struct wpa_radio_work *work, int deinit) struct wpabuf *hs20; hs20 = wpabuf_alloc(20); if (hs20) { - wpas_hs20_add_indication(hs20); + int pps_mo_id = hs20_get_pps_mo_id(wpa_s, ssid); + wpas_hs20_add_indication(hs20, pps_mo_id); os_memcpy(wpa_ie + wpa_ie_len, wpabuf_head(hs20), wpabuf_len(hs20)); wpa_ie_len += wpabuf_len(hs20); @@ -3237,6 +3268,20 @@ void radio_work_done(struct wpa_radio_work *work) } +int radio_work_pending(struct wpa_supplicant *wpa_s, const char *type) +{ + struct wpa_radio_work *work; + struct wpa_radio *radio = wpa_s->radio; + + dl_list_for_each(work, &radio->work, struct wpa_radio_work, list) { + if (work->wpa_s == wpa_s && os_strcmp(work->type, type) == 0) + return 1; + } + + return 0; +} + + static int wpas_init_driver(struct wpa_supplicant *wpa_s, struct wpa_interface *iface) { @@ -4299,17 +4344,23 @@ void wpas_auth_failed(struct wpa_supplicant *wpa_s) if (ssid->auth_failures > 50) dur = 300; - else if (ssid->auth_failures > 20) - dur = 120; else if (ssid->auth_failures > 10) - dur = 60; + dur = 120; else if (ssid->auth_failures > 5) + dur = 90; + else if (ssid->auth_failures > 3) + dur = 60; + else if (ssid->auth_failures > 2) dur = 30; else if (ssid->auth_failures > 1) dur = 20; else dur = 10; + if (ssid->auth_failures > 1 && + wpa_key_mgmt_wpa_ieee8021x(ssid->key_mgmt)) + dur += os_random() % (ssid->auth_failures * 10); + os_get_reltime(&now); if (now.sec + dur <= ssid->disabled_until.sec) return; diff --git a/wpa_supplicant/wpa_supplicant.conf b/wpa_supplicant/wpa_supplicant.conf index b6276320..442b44cb 100644 --- a/wpa_supplicant/wpa_supplicant.conf +++ b/wpa_supplicant/wpa_supplicant.conf @@ -432,6 +432,57 @@ fast_reauth=1 # matching with the network. Multiple entries can be used to specify more # than one SSID. # +# roaming_partner: Roaming partner information +# This optional field can be used to configure preferences between roaming +# partners. The field is a string in following format: +# <FQDN>,<0/1 exact match>,<priority>,<* or country code> +# (non-exact match means any subdomain matches the entry; priority is in +# 0..255 range with 0 being the highest priority) +# +# update_identifier: PPS MO ID +# (Hotspot 2.0 PerProviderSubscription/UpdateIdentifier) +# +# provisioning_sp: FQDN of the SP that provisioned the credential +# This optional field can be used to keep track of the SP that provisioned +# the credential to find the PPS MO (./Wi-Fi/<provisioning_sp>). +# +# Minimum backhaul threshold (PPS/<X+>/Policy/MinBackhauldThreshold/*) +# These fields can be used to specify minimum download/upload backhaul +# bandwidth that is preferred for the credential. This constraint is +# ignored if the AP does not advertise WAN Metrics information or if the +# limit would prevent any connection. Values are in kilobits per second. +# min_dl_bandwidth_home +# min_ul_bandwidth_home +# min_dl_bandwidth_roaming +# min_ul_bandwidth_roaming +# +# max_bss_load: Maximum BSS Load Channel Utilization (1..255) +# (PPS/<X+>/Policy/MaximumBSSLoadValue) +# This value is used as the maximum channel utilization for network +# selection purposes for home networks. If the AP does not advertise +# BSS Load or if the limit would prevent any connection, this constraint +# will be ignored. +# +# req_conn_capab: Required connection capability +# (PPS/<X+>/Policy/RequiredProtoPortTuple) +# This value is used to configure set of required protocol/port pairs that +# a roaming network shall support (include explicitly in Connection +# Capability ANQP element). This constraint is ignored if the AP does not +# advertise Connection Capability or if this constraint would prevent any +# network connection. This policy is not used in home networks. +# Format: <protocol>[:<comma-separated list of ports] +# Multiple entries can be used to list multiple requirements. +# For example, number of common TCP protocols: +# req_conn_capab=6,22,80,443 +# For example, IPSec/IKE: +# req_conn_capab=17:500 +# req_conn_capab=50 +# +# ocsp: Whether to use/require OCSP to check server certificate +# 0 = do not use OCSP stapling (TLS certificate status extension) +# 1 = try to use OCSP stapling, but not require response +# 2 = require valid OCSP stapling response +# # for example: # #cred={ diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h index bcdb4d03..13147340 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -308,6 +308,7 @@ void radio_work_done(struct wpa_radio_work *work); void radio_remove_works(struct wpa_supplicant *wpa_s, const char *type, int remove_all); void radio_work_check_next(struct wpa_supplicant *wpa_s); +int radio_work_pending(struct wpa_supplicant *wpa_s, const char *type); struct wpa_connect_work { unsigned int sme:1; @@ -419,6 +420,9 @@ struct wpa_supplicant { enum { WPA_SETBAND_AUTO, WPA_SETBAND_5G, WPA_SETBAND_2G } setband; + /* Preferred network for the next connection attempt */ + struct wpa_ssid *next_ssid; + /* previous scan was wildcard when interleaving between * wildcard scans and specific SSID scan when max_ssids=1 */ int prev_scan_wildcard; @@ -767,7 +771,15 @@ struct wpa_supplicant { unsigned int auto_select:1; unsigned int auto_network_select:1; unsigned int fetch_all_anqp:1; + unsigned int fetch_osu_info:1; + unsigned int fetch_osu_icon_in_progress:1; struct wpa_bss *interworking_gas_bss; + unsigned int osu_icon_id; + struct osu_provider *osu_prov; + size_t osu_prov_count; + struct os_reltime osu_icon_fetch_start; + unsigned int num_osu_scans; + unsigned int num_prov_found; #endif /* CONFIG_INTERWORKING */ unsigned int drv_capa_known; diff --git a/wpa_supplicant/wpas_module_tests.c b/wpa_supplicant/wpas_module_tests.c index 4e390241..86b70a94 100644 --- a/wpa_supplicant/wpas_module_tests.c +++ b/wpa_supplicant/wpas_module_tests.c @@ -9,6 +9,65 @@ #include "utils/includes.h" #include "utils/common.h" +#include "wpa_supplicant_i.h" +#include "blacklist.h" + + +static int wpas_blacklist_module_tests(void) +{ + struct wpa_supplicant wpa_s; + int ret = -1; + + os_memset(&wpa_s, 0, sizeof(wpa_s)); + + wpa_blacklist_clear(&wpa_s); + + if (wpa_blacklist_get(NULL, NULL) != NULL || + wpa_blacklist_get(NULL, (u8 *) "123456") != NULL || + wpa_blacklist_get(&wpa_s, NULL) != NULL || + wpa_blacklist_get(&wpa_s, (u8 *) "123456") != NULL) + goto fail; + + if (wpa_blacklist_add(NULL, NULL) == 0 || + wpa_blacklist_add(NULL, (u8 *) "123456") == 0 || + wpa_blacklist_add(&wpa_s, NULL) == 0) + goto fail; + + if (wpa_blacklist_del(NULL, NULL) == 0 || + wpa_blacklist_del(NULL, (u8 *) "123456") == 0 || + wpa_blacklist_del(&wpa_s, NULL) == 0 || + wpa_blacklist_del(&wpa_s, (u8 *) "123456") == 0) + goto fail; + + if (wpa_blacklist_add(&wpa_s, (u8 *) "111111") < 0 || + wpa_blacklist_add(&wpa_s, (u8 *) "111111") < 0 || + wpa_blacklist_add(&wpa_s, (u8 *) "222222") < 0 || + wpa_blacklist_add(&wpa_s, (u8 *) "333333") < 0 || + wpa_blacklist_add(&wpa_s, (u8 *) "444444") < 0 || + wpa_blacklist_del(&wpa_s, (u8 *) "333333") < 0 || + wpa_blacklist_del(&wpa_s, (u8 *) "xxxxxx") == 0 || + wpa_blacklist_get(&wpa_s, (u8 *) "xxxxxx") != NULL || + wpa_blacklist_get(&wpa_s, (u8 *) "111111") == NULL || + wpa_blacklist_get(&wpa_s, (u8 *) "222222") == NULL || + wpa_blacklist_get(&wpa_s, (u8 *) "444444") == NULL || + wpa_blacklist_del(&wpa_s, (u8 *) "111111") < 0 || + wpa_blacklist_del(&wpa_s, (u8 *) "222222") < 0 || + wpa_blacklist_del(&wpa_s, (u8 *) "444444") < 0 || + wpa_blacklist_add(&wpa_s, (u8 *) "111111") < 0 || + wpa_blacklist_add(&wpa_s, (u8 *) "222222") < 0 || + wpa_blacklist_add(&wpa_s, (u8 *) "333333") < 0) + goto fail; + + ret = 0; +fail: + wpa_blacklist_clear(&wpa_s); + + if (ret) + wpa_printf(MSG_ERROR, "blacklist module test failure"); + + return ret; +} + int wpas_module_tests(void) { @@ -16,6 +75,9 @@ int wpas_module_tests(void) wpa_printf(MSG_INFO, "wpa_supplicant module tests"); + if (wpas_blacklist_module_tests() < 0) + ret = -1; + #ifdef CONFIG_WPS { int wps_module_tests(void); |
