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/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,