Cumulative patch from commit 5e32f8256f5fcce8b70a95e070375ac549ac935a

5e32f82 tests: Verify HS 2.0R2 deauthentication request
b61e70c HS 2.0R2: Add WFA server-only EAP-TLS server method
8d2a992 HS 2.0R2: RADIUS server support to request Subscr Remediation
ae6d15c HS 2.0R2 AP: Add OSU Providers list ANQP element
f7bd7a0 HS 2.0R2 AP: Add Icon Request and Icon binary File ANQP elements
97596f8 HS 2.0R2 AP: Add support for Session Info URL RADIUS AVP
8e1146d HS 2.0R2 AP: Add support for deauthentication request
a14896e HS 2.0R2 AP: Add OSEN implementation
6ca0853 HS 2.0R2 AP: Use Subscr Remediation request from RADIUS server
7bc9c25 HS 2.0R2 AP: Add STA's Hotspot 2.0 Release Number into Access-Request
76579ec HS 2.0R2 AP: Add AP Hotspot 2.0 Release Number as WFA RADIUS VSA
0dd100f HS 2.0R2 AP: Add definition and helper function for WFA RADIUS VSA
3fb17a9 HS 2.0R2 AP: Add WNM-Notification Request for Subscription Remediation
d5d2478 HS 2.0R2 AP: Update HS 2.0 Indication element to Release 2
a6739e1 HS 2.0R2: Try to scan multiple times for OSU providers
cf6d08a Interworking: Add OCSP parameter to the cred block
6402f2f Interworking: Add more debug info on roaming partner preferences
7479489 Interworking: Add sp_priority cred parameter
751ac99 Interworking: Use a helper function to compare cred priority
aff419f Interworking: Remove separate credential priority tracking
533536d HS 2.0R2: Disable full ESS for as a workaround for per-BSS issues
8a77f1b HS 2.0R2: Slow down connection attempts on EAP failures
76a55a8 HS 2.0R2: Add more debug to network selection
8b4b9fb HS 2.0R2: Fix bandwidth policy BSS selection
28f2a7c HS 2.0R2: Allow excluded network to be selected based on user override
33fb8c5 HS 2.0R2: Add support for Policy/RequiredProtoPortTuple
a45b2dc HS 2.0R2: Add support for Policy/MaximumBSSLoadValue
4cad9df HS 2.0R2: Add support for Policy/MinBackhaulThreshold
aa26ba6 HS 2.0R2: Add tracking of provisioning SP
8e5fdfa HS 2.0R2: Add WFA server-only EAP-TLS peer method
df0f01d HS 2.0R2: Add OSEN client implementation
a5d7563 HS 2.0R2: Add common OSEN definitions
230e373 HS 2.0R2: Add GAS operation duration statistics into debug
b572df8 HS 2.0R2: Add routine for fetching OSU provider information
1d2215f HS 2.0R2: Add OSU Providers list ANQP element
184e110 HS 2.0R2: Add Icon Request and Icon binary File ANQP elements
7ef6947 HS 2.0R2: Add STA support for Deauthentication Request notification
95a3ea9 HS 2.0R2: Add WNM-Notification Request for Subscription Remediation
f9cd147 HS 2.0R2: Update Indication element to Release 2
bc00053 Interworking: Allow roaming partner configuration
ae6f927 nl80211: Add driver capability for GTK_NOT_USED
2c49d04 Do not clear global pmf setting on FLUSH
eef7235 Only try fast reconnect if network is not disabled
3d910ef Interworking: Prefer last added network during network selection
2a33687 P2P: Remove unnecessary ifdef CONFIG_NO_CONFIG_WRITE
050d8b5 Fix documentation for wpa_supplicant_global_ctrl_iface_process()
8c9cb81 DFS: Fix coding style (missing whitespace)
4f1e01b DFS: Add VHT160 available channels
b8058a6 hostapd: DFS allow mixed channels
4db216f wpa_supplicant: Add support for IPv6 with UDP ctrl_iface
e2364d1 hostapd: Deauthenticate clients forbidden by maclist changes
1748f1d hostapd: Make it possible to remove addresses from maclists
064eb05 Add os_remove_in_array()
c1151e4 Force OFDM/HT/VHT to be disabled on channel 14
bfb79dd nl80211: Show regulatory rule flags in debug output
3d7ad2f hostapd: Configure spectrum management capability
e0392f8 hostapd: Add Power Constraint element
891330f Fix spelling s/algorith/algorithm/
f0e30c8 Do not start another connect work while one is pending
3290398 WPS: Fix UNSUBSCRIBE error returns if NT or CALLBACK header is used
f34df28 WPS: Fix UNSUBSCRIBE to return 412 if no SID match found
80f256a WPS: Remove unnecessary filename NULL check

Change-Id: I7dc25a8bb0074f4970ade8d42dfa60da166baf96
Signed-off-by: Dmitry Shmidt <dimitrysh@google.com>
diff --git a/hostapd/Android.mk b/hostapd/Android.mk
index 5d6fe56..888ee2b 100644
--- a/hostapd/Android.mk
+++ b/hostapd/Android.mk
@@ -201,6 +201,10 @@
 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 25c560f..c541d43 100644
--- a/hostapd/Makefile
+++ b/hostapd/Makefile
@@ -185,6 +185,10 @@
 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 19d6ad3..fa7d14a 100644
--- a/hostapd/config_file.c
+++ b/hostapd/config_file.c
@@ -129,6 +129,8 @@
 	}
 
 	while (fgets(buf, sizeof(buf), f)) {
+		int i, rem = 0;
+
 		line++;
 
 		if (buf[0] == '#')
@@ -143,14 +145,32 @@
 		}
 		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 @@
 	}
 	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 @@
 			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 @@
 			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 @@
 					   "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 7f5de62..dbdc8c6 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 @@
 
 #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 @@
 		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 @@
 		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 981e539..8477c21 100644
--- a/hostapd/eap_register.c
+++ b/hostapd/eap_register.c
@@ -44,6 +44,13 @@
 		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 c503ce2..c745fe8 100644
--- a/hostapd/hostapd.conf
+++ b/hostapd/hostapd.conf
@@ -108,6 +108,20 @@
 # (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 @@
 # 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 @@
 # 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 f688327..2c1f130 100644
--- a/hostapd/hostapd.eap_user_sqlite
+++ b/hostapd/hostapd.eap_user_sqlite
@@ -2,6 +2,7 @@
 	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 eee8504..8caca4f 100644
--- a/hostapd/hostapd_cli.c
+++ b/hostapd/hostapd_cli.c
@@ -743,6 +743,51 @@
 }
 
 
+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 @@
 	{ "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 368b202..b995892 100644
--- a/src/ap/ap_config.c
+++ b/src/ap/ap_config.c
@@ -154,6 +154,8 @@
 	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 @@
 	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 @@
 		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 @@
 		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 4631ca9..e1e34e2 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -45,7 +45,8 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 
 	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 e998fc6..b8b260a 100644
--- a/src/ap/ap_drv_ops.c
+++ b/src/ap/ap_drv_ops.c
@@ -170,6 +170,17 @@
 			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 7183aba..7691012 100644
--- a/src/ap/authsrv.c
+++ b/src/ap/authsrv.c
@@ -80,6 +80,7 @@
 	}
 	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 @@
 #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 b1ebf6e..e06ce77 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_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 @@
 
 	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 @@
 
 #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 @@
 	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 @@
 
 #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 @@
 	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 ec691db..612f534 100644
--- a/src/ap/dfs.c
+++ b/src/ap/dfs.c
@@ -78,6 +78,11 @@
 	 * 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 @@
 		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 @@
 
 	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 @@
 
 	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 @@
 		      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 859ff79..a619c55 100644
--- a/src/ap/dfs.h
+++ b/src/ap/dfs.h
@@ -21,5 +21,6 @@
 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 9af9646..6fb1056 100644
--- a/src/ap/drv_callbacks.c
+++ b/src/ap/drv_callbacks.c
@@ -78,6 +78,12 @@
 		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 @@
 			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 79d50e5..371a73f 100644
--- a/src/ap/eap_user_db.c
+++ b/src/ap/eap_user_db.c
@@ -89,6 +89,8 @@
 			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 @@
 	}
 
 	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 b5fb7df..fd1041e 100644
--- a/src/ap/gas_serv.c
+++ b/src/ap/gas_serv.c
@@ -159,6 +159,10 @@
 		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_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 @@
 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;
 
-	buf = wpabuf_alloc(1400);
+	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(len);
 	if (buf == NULL)
 		return NULL;
 
@@ -559,6 +734,10 @@
 		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 @@
 	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 @@
 			     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_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 @@
 	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 @@
 
 	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 @@
 	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 @@
 
 		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 74739fe..7e392b3 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 98148da..75baec0 100644
--- a/src/ap/hostapd.c
+++ b/src/ap/hostapd.c
@@ -87,7 +87,7 @@
 	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 @@
 		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 45d518b..d7909fa 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 98698ce..152439f 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 4e66379..7d36790 100644
--- a/src/ap/hw_features.c
+++ b/src/ap/hw_features.c
@@ -943,6 +943,15 @@
 	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 9251ac3..3e704e5 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 @@
 {
 	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 @@
 	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 @@
 	    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 @@
 			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 @@
 		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 eadaa4d..b78fd01 100644
--- a/src/ap/ieee802_11_shared.c
+++ b/src/ap/ieee802_11_shared.c
@@ -199,6 +199,10 @@
 		}
 		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 @@
 	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 21f815a..b12c9d6 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 @@
 		}
 	}
 
+#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 @@
 	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 @@
 		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 @@
 			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 @@
 	}
 #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 @@
 
 #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 @@
 }
 
 
+#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_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_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 @@
 	}
 	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 @@
 
 
 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 24e764d..f7af088 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 @@
 		   __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 @@
 	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 @@
 }
 
 
+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 240b926..c0bab6f 100644
--- a/src/ap/sta_info.h
+++ b/src/ap/sta_info.h
@@ -57,6 +57,8 @@
 	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 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 @@
 			    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 707a63f..cc64ff1 100644
--- a/src/ap/wpa_auth.c
+++ b/src/ap/wpa_auth.c
@@ -211,6 +211,8 @@
 	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 @@
 		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 @@
 		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 @@
 
 	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 @@
 	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 @@
 		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 @@
 	 * 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 bc3dec4..d99db69 100644
--- a/src/ap/wpa_auth.h
+++ b/src/ap/wpa_auth.h
@@ -232,6 +232,9 @@
 			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 5af1495..da5fea7 100644
--- a/src/ap/wpa_auth_glue.c
+++ b/src/ap/wpa_auth_glue.c
@@ -73,6 +73,19 @@
 #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 274f4d6..7a49751 100644
--- a/src/ap/wpa_auth_ie.c
+++ b/src/ap/wpa_auth_ie.c
@@ -295,6 +295,55 @@
 }
 
 
+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 @@
 
 	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 @@
 }
 
 
+#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 @@
 		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 f945882..d2067ba 100644
--- a/src/ap/wpa_auth_ie.h
+++ b/src/ap/wpa_auth_ie.h
@@ -43,6 +43,9 @@
 	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 4811e8e..d4091e3 100644
--- a/src/common/defs.h
+++ b/src/common/defs.h
@@ -48,12 +48,14 @@
 #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_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 @@
 #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 50bdc01..cdee6bc 100644
--- a/src/common/ieee802_11_common.c
+++ b/src/common/ieee802_11_common.c
@@ -108,6 +108,11 @@
 			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 4fb2e84..9b8bbd1 100644
--- a/src/common/ieee802_11_common.h
+++ b/src/common/ieee802_11_common.h
@@ -41,6 +41,7 @@
 	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 @@
 	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 0e39caf..520e55d 100644
--- a/src/common/ieee802_11_defs.h
+++ b/src/common/ieee802_11_defs.h
@@ -786,6 +786,7 @@
 #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 @@
 
 #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 @@
 #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 37b265d..c9d0ccb 100644
--- a/src/common/wpa_common.c
+++ b/src/common/wpa_common.c
@@ -56,6 +56,11 @@
 	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 dcc035c..5684ef3 100644
--- a/src/common/wpa_common.h
+++ b/src/common/wpa_common.h
@@ -67,6 +67,7 @@
 #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 @@
 
 /* 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 f4af94a..5820a13 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 @@
 		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 @@
 		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 @@
 			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 @@
 		} 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 @@
 			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 759cee4..6d17228 100644
--- a/src/common/wpa_ctrl.h
+++ b/src/common/wpa_ctrl.h
@@ -159,8 +159,10 @@
 #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 @@
 /* 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 632ae3a..d2aad24 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -847,6 +847,11 @@
 	 * disable_dgaf - Whether group-addressed frames are disabled
 	 */
 	int disable_dgaf;
+
+	/**
+	 * osen - Whether OSEN security is enabled
+	 */
+	int osen;
 };
 
 /**
@@ -875,6 +880,7 @@
 #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 32a371d..42578b6 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -3525,6 +3525,9 @@
 		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 @@
 	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 @@
 		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 @@
 
 	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 @@
 			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;
+		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",
-			   start, end, max_bw, max_eirp);
+		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 @@
 		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 f5890be..4f14a01 100644
--- a/src/eap_common/eap_defs.h
+++ b/src/eap_common/eap_defs.h
@@ -72,13 +72,16 @@
 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 d3cbaca..fee1b7b 100644
--- a/src/eap_peer/eap_aka.c
+++ b/src/eap_peer/eap_aka.c
@@ -316,7 +316,7 @@
 
 #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 a465fd2..e35c919 100644
--- a/src/eap_peer/eap_methods.h
+++ b/src/eap_peer/eap_methods.h
@@ -86,6 +86,7 @@
 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 d2066cd..bb9f3f2 100644
--- a/src/eap_peer/eap_tls.c
+++ b/src/eap_peer/eap_tls.c
@@ -98,6 +98,33 @@
 #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 @@
 	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 b3a99b6..fe9bfe0 100644
--- a/src/eap_peer/eap_tls_common.c
+++ b/src/eap_peer/eap_tls_common.c
@@ -23,6 +23,10 @@
 		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 @@
 		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 1a5e0f8..390c216 100644
--- a/src/eap_peer/eap_tls_common.h
+++ b/src/eap_peer/eap_tls_common.h
@@ -87,6 +87,7 @@
 
 /* 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 36b230b..197b232 100644
--- a/src/eap_server/eap.h
+++ b/src/eap_server/eap.h
@@ -32,6 +32,7 @@
 			    * 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 429cb72..0baa327 100644
--- a/src/eap_server/eap_methods.h
+++ b/src/eap_server/eap_methods.h
@@ -27,6 +27,7 @@
 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 447f47c..6bed62f 100644
--- a/src/eap_server/eap_server_tls.c
+++ b/src/eap_server/eap_server_tls.c
@@ -94,6 +94,28 @@
 #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 @@
 		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 @@
 	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 526e1bc..de5ab0d 100644
--- a/src/eap_server/eap_server_tls_common.c
+++ b/src/eap_server/eap_server_tls_common.c
@@ -25,6 +25,10 @@
 		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 @@
 		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 11f5827..91449af 100644
--- a/src/eap_server/eap_tls_common.h
+++ b/src/eap_server/eap_tls_common.h
@@ -64,6 +64,7 @@
 
 /* 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 a257781..525bdee 100644
--- a/src/eapol_auth/eapol_auth_sm.c
+++ b/src/eapol_auth/eapol_auth_sm.c
@@ -219,7 +219,8 @@
 	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 @@
 				   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 @@
 			   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 @@
 				 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 f0ff464..320a0ad 100644
--- a/src/eapol_auth/eapol_auth_sm.h
+++ b/src/eapol_auth/eapol_auth_sm.h
@@ -60,7 +60,8 @@
 			   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 d7f893a..25baddb 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_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 1070fc7..370b517 100644
--- a/src/radius/radius.c
+++ b/src/radius/radius.c
@@ -1220,6 +1220,33 @@
 }
 
 
+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 ad65b04..d8bf21e 100644
--- a/src/radius/radius.h
+++ b/src/radius/radius.h
@@ -163,6 +163,18 @@
        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 @@
 			     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 2904b2f..5074b60 100644
--- a/src/radius/radius_server.c
+++ b/src/radius/radius_server.c
@@ -77,6 +77,8 @@
 	u8 last_identifier;
 	struct radius_msg *last_reply;
 	u8 last_authenticator[16];
+
+	unsigned int remediation:1;
 };
 
 /**
@@ -307,6 +309,9 @@
 #ifdef CONFIG_RADIUS_TEST
 	char *dump_msk_file;
 #endif /* CONFIG_RADIUS_TEST */
+
+	char *subscr_remediation_url;
+	u8 subscr_remediation_method;
 };
 
 
@@ -622,6 +627,34 @@
 		}
 	}
 
+#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 @@
 		}
 	}
 
+	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 @@
 #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 @@
 {
 	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 78f5fc2..e85d009 100644
--- a/src/radius/radius_server.h
+++ b/src/radius/radius_server.h
@@ -209,6 +209,9 @@
 #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 4474c3b..ba50263 100644
--- a/src/rsn_supp/wpa.c
+++ b/src/rsn_supp/wpa.c
@@ -89,7 +89,10 @@
 	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 @@
 	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 @@
 	}
 
 	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 @@
 		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 @@
 
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 			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 @@
 #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 @@
 #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 @@
 	}
 	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 @@
 {
 	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 e58bdc4..9c11183 100644
--- a/src/rsn_supp/wpa_ie.c
+++ b/src/rsn_supp/wpa_ie.c
@@ -222,6 +222,64 @@
 }
 
 
+#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 @@
 					  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 2e2350a..d63ac29 100644
--- a/src/utils/os.h
+++ b/src/utils/os.h
@@ -549,6 +549,21 @@
 	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 11386d8..2a3b636 100644
--- a/src/wps/wps_upnp_web.c
+++ b/src/wps/wps_upnp_web.c
@@ -324,8 +324,6 @@
 	 * 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 @@
 			.....
 		}
 #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 @@
 			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 @@
 				   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 2880b2d..c745cb2 100644
--- a/wpa_supplicant/Android.mk
+++ b/wpa_supplicant/Android.mk
@@ -280,6 +280,7 @@
 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 7b556e8..2b8cb93 100644
--- a/wpa_supplicant/Makefile
+++ b/wpa_supplicant/Makefile
@@ -286,6 +286,7 @@
 OBJS += hs20_supplicant.o
 CFLAGS += -DCONFIG_HS20
 CONFIG_INTERWORKING=y
+NEED_AES_OMAC1=y
 endif
 
 ifdef CONFIG_INTERWORKING
@@ -1267,6 +1268,11 @@
 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 @@
 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 ad29ef7..b6f0673 100644
--- a/wpa_supplicant/README-HS20
+++ b/wpa_supplicant/README-HS20
@@ -213,6 +213,63 @@
 #	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 9ea6903..482fc64 100644
--- a/wpa_supplicant/bss.c
+++ b/wpa_supplicant/bss.c
@@ -98,6 +98,7 @@
 	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 @@
 	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 4deeb5f..30df7ca 100644
--- a/wpa_supplicant/bss.h
+++ b/wpa_supplicant/bss.h
@@ -39,6 +39,7 @@
 	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 2dd7054..da9580e 100644
--- a/wpa_supplicant/config.c
+++ b/wpa_supplicant/config.c
@@ -405,6 +405,8 @@
 		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 @@
 		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 @@
 	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 @@
 	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 @@
 }
 
 
+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 @@
 		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 @@
 		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 @@
 		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 @@
 	{ 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 e7bdaa5..de43970 100644
--- a/wpa_supplicant/config.h
+++ b/wpa_supplicant/config.h
@@ -236,6 +236,58 @@
 		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 @@
 	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 6312a77..850a6cf 100644
--- a/wpa_supplicant/config_file.c
+++ b/wpa_supplicant/config_file.c
@@ -796,6 +796,41 @@
 			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 e95b55b..9f5d4f4 100644
--- a/wpa_supplicant/ctrl_iface.c
+++ b/wpa_supplicant/ctrl_iface.c
@@ -1554,6 +1554,9 @@
 {
 	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 @@
 
 #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 @@
 			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 @@
 	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 @@
 		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 @@
 				   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 @@
 	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 @@
 	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 @@
 	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 @@
 	} 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 b0dec53..d54cc07 100644
--- a/wpa_supplicant/ctrl_iface.h
+++ b/wpa_supplicant/ctrl_iface.h
@@ -32,7 +32,7 @@
 					 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 8c09ba1..9d0674d 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 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_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 @@
 	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 @@
 	}
 
 #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 @@
 			   "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 @@
 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 @@
 	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 @@
 	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 @@
 	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 6684782..91eea35 100644
--- a/wpa_supplicant/defconfig
+++ b/wpa_supplicant/defconfig
@@ -192,8 +192,10 @@
 # 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 6cd2fc5..ece5716 100644
--- a/wpa_supplicant/eap_register.c
+++ b/wpa_supplicant/eap_register.c
@@ -40,6 +40,13 @@
 		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 c5e65ef..c0f55ee 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -398,6 +398,9 @@
 	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 @@
 		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 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 @@
 	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 @@
 
 	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 @@
 		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 @@
 			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_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 @@
 		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 @@
 		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 @@
 			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 b255847..a63ee6c 100644
--- a/wpa_supplicant/gas_query.c
+++ b/wpa_supplicant/gas_query.c
@@ -42,6 +42,7 @@
 	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_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 @@
 {
 	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 @@
 
 	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 @@
 		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 @@
 		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 5f30313..b873c7c 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 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 @@
 		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 @@
 }
 
 
+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 @@
 	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 @@
 				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 1c8481b..88e5062 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 @@
 				  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 da8971d..abf5dee 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 @@
 {
 	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 @@
 	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 @@
 		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 @@
 	    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_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 @@
 					      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 @@
 				  cred->domain_suffix_match) < 0)
 		return -1;
 
+	ssid->eap.ocsp = cred->ocsp;
+
 	return 0;
 }
 
@@ -1241,6 +1472,7 @@
 		    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 @@
 }
 
 
-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 @@
 	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 @@
 		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 @@
 		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 @@
 
 	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 @@
 }
 
 
+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 @@
 		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 @@
 			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 @@
 
 	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 @@
 		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 @@
 		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 @@
 		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 @@
 	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 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 @@
 				   "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 @@
 			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 @@
 			"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 @@
 	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 @@
 	}
 
 	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 @@
 
 	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 @@
 	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 @@
 						slen);
 		pos += slen;
 	}
+
+	hs20_notify_parse_done(wpa_s);
 }
 
 
@@ -2211,6 +2786,7 @@
 	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 c8e7093..bb0ceb8 100644
--- a/wpa_supplicant/interworking.h
+++ b/wpa_supplicant/interworking.h
@@ -29,5 +29,7 @@
 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 a82fbf3..2db1d54 100644
--- a/wpa_supplicant/notify.c
+++ b/wpa_supplicant/notify.c
@@ -252,6 +252,8 @@
 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 fa75fa5..b878198 100644
--- a/wpa_supplicant/p2p_supplicant.c
+++ b/wpa_supplicant/p2p_supplicant.c
@@ -722,12 +722,10 @@
 		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 @@
 			  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 @@
 		   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 @@
 	}
 	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 @@
 	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 6c742d6..f7eb537 100644
--- a/wpa_supplicant/scan.c
+++ b/wpa_supplicant/scan.c
@@ -365,11 +365,17 @@
 		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 @@
 
 #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 e712ac4..63beaef 100644
--- a/wpa_supplicant/sme.c
+++ b/wpa_supplicant/sme.c
@@ -360,7 +360,8 @@
 		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 @@
 		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 @@
 		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 65b2783..0619f6d 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 @@
 }
 
 
+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 @@
 	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 6278806..0691183 100644
--- a/wpa_supplicant/wpa_cli.c
+++ b/wpa_supplicant/wpa_cli.c
@@ -2298,6 +2298,37 @@
 	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 @@
 	{ "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 @@
 		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 e942b62..ad1a03e 100644
--- a/wpa_supplicant/wpa_supplicant.c
+++ b/wpa_supplicant/wpa_supplicant.c
@@ -483,6 +483,10 @@
 
 	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 @@
 {
 	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 @@
 		   (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 @@
 	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 @@
 	} 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 @@
 #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 @@
 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 @@
 		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 @@
 		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 @@
 }
 
 
+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 @@
 
 	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 b627632..442b44c 100644
--- a/wpa_supplicant/wpa_supplicant.conf
+++ b/wpa_supplicant/wpa_supplicant.conf
@@ -432,6 +432,57 @@
 #	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 bcdb4d0..1314734 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -308,6 +308,7 @@
 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 @@
 
 	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 @@
 	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 4e39024..86b70a9 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 @@
 
 	wpa_printf(MSG_INFO, "wpa_supplicant module tests");
 
+	if (wpas_blacklist_module_tests() < 0)
+		ret = -1;
+
 #ifdef CONFIG_WPS
 	{
 		int wps_module_tests(void);