Merge "Cumulative patch from commit 3e949655c"
diff --git a/hostapd/config_file.c b/hostapd/config_file.c
index b1ab13e..b26da71 100644
--- a/hostapd/config_file.c
+++ b/hostapd/config_file.c
@@ -2049,6 +2049,24 @@
 }
 
 
+static int hs20_parse_osu_nai2(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_nai2);
+	bss->last_osu->osu_nai2 = os_strdup(pos);
+	if (bss->last_osu->osu_nai2 == NULL)
+		return -1;
+	bss->hs20_osu_providers_nai_count++;
+
+	return 0;
+}
+
+
 static int hs20_parse_osu_method_list(struct hostapd_bss_config *bss, char *pos,
 				      int line)
 {
@@ -3761,6 +3779,9 @@
 	} else if (os_strcmp(buf, "osu_nai") == 0) {
 		if (hs20_parse_osu_nai(bss, pos, line) < 0)
 			return 1;
+	} else if (os_strcmp(buf, "osu_nai2") == 0) {
+		if (hs20_parse_osu_nai2(bss, pos, line) < 0)
+			return 1;
 	} else if (os_strcmp(buf, "osu_method_list") == 0) {
 		if (hs20_parse_osu_method_list(bss, pos, line) < 0)
 			return 1;
@@ -4087,6 +4108,8 @@
 				   line, pos);
 			return 1;
 		}
+	} else if (os_strcmp(buf, "coloc_intf_reporting") == 0) {
+		bss->coloc_intf_reporting = atoi(pos);
 #endif /* CONFIG_OWE */
 	} else {
 		wpa_printf(MSG_ERROR,
diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c
index 2d68e88..e539a09 100644
--- a/hostapd/ctrl_iface.c
+++ b/hostapd/ctrl_iface.c
@@ -992,6 +992,42 @@
 	return ret;
 }
 
+
+static int hostapd_ctrl_iface_coloc_intf_req(struct hostapd_data *hapd,
+					     const char *cmd)
+{
+	u8 addr[ETH_ALEN];
+	struct sta_info *sta;
+	const char *pos;
+	unsigned int auto_report, timeout;
+
+	if (hwaddr_aton(cmd, addr)) {
+		wpa_printf(MSG_DEBUG, "Invalid STA MAC address");
+		return -1;
+	}
+
+	sta = ap_get_sta(hapd, addr);
+	if (!sta) {
+		wpa_printf(MSG_DEBUG, "Station " MACSTR
+			   " not found for Collocated Interference Request",
+			   MAC2STR(addr));
+		return -1;
+	}
+
+	pos = cmd + 17;
+	if (*pos != ' ')
+		return -1;
+	pos++;
+	auto_report = atoi(pos);
+	pos = os_strchr(pos, ' ');
+	if (!pos)
+		return -1;
+	pos++;
+	timeout = atoi(pos);
+
+	return wnm_send_coloc_intf_req(hapd, sta, auto_report, timeout);
+}
+
 #endif /* CONFIG_WNM_AP */
 
 
@@ -1386,6 +1422,12 @@
 			hostapd_disassoc_deny_mac(hapd);
 		} else if (os_strcasecmp(cmd, "accept_mac_file") == 0) {
 			hostapd_disassoc_accept_mac(hapd);
+		} else if (os_strncmp(cmd, "wme_ac_", 7) == 0 ||
+			   os_strncmp(cmd, "wmm_ac_", 7) == 0) {
+			hapd->parameter_set_count++;
+			if (ieee802_11_update_beacons(hapd->iface))
+				wpa_printf(MSG_DEBUG,
+					   "Failed to update beacons with WMM parameters");
 		}
 	}
 
@@ -2955,6 +2997,9 @@
 	} else if (os_strncmp(buf, "BSS_TM_REQ ", 11) == 0) {
 		if (hostapd_ctrl_iface_bss_tm_req(hapd, buf + 11))
 			reply_len = -1;
+	} else if (os_strncmp(buf, "COLOC_INTF_REQ ", 15) == 0) {
+		if (hostapd_ctrl_iface_coloc_intf_req(hapd, buf + 15))
+			reply_len = -1;
 #endif /* CONFIG_WNM_AP */
 	} else if (os_strcmp(buf, "GET_CONFIG") == 0) {
 		reply_len = hostapd_ctrl_iface_get_config(hapd, reply,
diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
index 70f9713..a005217 100644
--- a/hostapd/hostapd.conf
+++ b/hostapd/hostapd.conf
@@ -2227,12 +2227,15 @@
 # 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.
+# last added OSU provider. osu_nai specifies the OSU_NAI value for OSEN
+# authentication when using a standalone OSU BSS. osu_nai2 specifies the OSU_NAI
+# value for OSEN authentication when using a shared BSS (Single SSID) for OSU.
 #
 #osu_server_uri=https://example.com/osu/
 #osu_friendly_name=eng:Example operator
 #osu_friendly_name=fin:Esimerkkipalveluntarjoaja
 #osu_nai=anonymous@example.com
+#osu_nai2=anonymous@example.com
 #osu_method_list=1 0
 #osu_icon=icon32
 #osu_icon=icon64
diff --git a/hs20/client/est.c b/hs20/client/est.c
index 9f1519b..b1aacb8 100644
--- a/hs20/client/est.c
+++ b/hs20/client/est.c
@@ -666,7 +666,6 @@
 	char *buf, *resp, *req, *req2;
 	size_t buflen, resp_len, len, pkcs7_len;
 	unsigned char *pkcs7;
-	FILE *f;
 	char client_cert_buf[200];
 	char client_key_buf[200];
 	const char *client_cert = NULL, *client_key = NULL;
@@ -721,11 +720,6 @@
 		return -1;
 	}
 	wpa_printf(MSG_DEBUG, "EST simpleenroll response: %s", resp);
-	f = fopen("Cert/est-resp.raw", "w");
-	if (f) {
-		fwrite(resp, resp_len, 1, f);
-		fclose(f);
-	}
 
 	pkcs7 = base64_decode((unsigned char *) resp, resp_len, &pkcs7_len);
 	if (pkcs7 == NULL) {
diff --git a/hs20/client/osu_client.c b/hs20/client/osu_client.c
index a7ddd19..9e1b0c7 100644
--- a/hs20/client/osu_client.c
+++ b/hs20/client/osu_client.c
@@ -293,7 +293,6 @@
 
 	unlink("Cert/est-req.b64");
 	unlink("Cert/est-req.pem");
-	unlink("Cert/est-resp.raw");
 	rmdir("Cert");
 
 	return 0;
@@ -437,7 +436,7 @@
 	if (node == NULL) {
 		wpa_printf(MSG_INFO, "No Policy/PolicyUpdate/TrustRoot/CertURL found from PPS");
 		xml_node_free(ctx->xml, pps);
-		return -1;
+		return -2;
 	}
 
 	ret = download_cert(ctx, node, ca_fname);
@@ -464,7 +463,7 @@
 	if (node == NULL) {
 		wpa_printf(MSG_INFO, "No AAAServerTrustRoot/CertURL found from PPS");
 		xml_node_free(ctx->xml, pps);
-		return -1;
+		return -2;
 	}
 
 	aaa = xml_node_first_child(ctx->xml, node);
@@ -486,7 +485,7 @@
 {
 	char *dir, *pos;
 	char fname[300];
-	int ret;
+	int ret, ret1;
 
 	dir = os_strdup(pps_fname);
 	if (dir == NULL)
@@ -501,9 +500,13 @@
 	snprintf(fname, sizeof(fname), "%s/ca.pem", dir);
 	ret = cmd_dl_osu_ca(ctx, pps_fname, fname);
 	snprintf(fname, sizeof(fname), "%s/polupd-ca.pem", dir);
-	cmd_dl_polupd_ca(ctx, pps_fname, fname);
+	ret1 = cmd_dl_polupd_ca(ctx, pps_fname, fname);
+	if (ret == 0 && ret1 == -1)
+		ret = -1;
 	snprintf(fname, sizeof(fname), "%s/aaa-ca.pem", dir);
-	cmd_dl_aaa_ca(ctx, pps_fname, fname);
+	ret1 = cmd_dl_aaa_ca(ctx, pps_fname, fname);
+	if (ret == 0 && ret1 == -1)
+		ret = -1;
 
 	os_free(dir);
 
@@ -1986,7 +1989,9 @@
 	char url[256];
 	unsigned int methods;
 	char osu_ssid[33];
+	char osu_ssid2[33];
 	char osu_nai[256];
+	char osu_nai2[256];
 	struct osu_lang_text friendly_name[MAX_OSU_VALS];
 	size_t friendly_name_count;
 	struct osu_lang_text serv_desc[MAX_OSU_VALS];
@@ -2045,12 +2050,24 @@
 			continue;
 		}
 
+		if (strncmp(buf, "osu_ssid2=", 10) == 0) {
+			snprintf(last->osu_ssid2, sizeof(last->osu_ssid2),
+				 "%s", buf + 10);
+			continue;
+		}
+
 		if (os_strncmp(buf, "osu_nai=", 8) == 0) {
 			os_snprintf(last->osu_nai, sizeof(last->osu_nai),
 				    "%s", buf + 8);
 			continue;
 		}
 
+		if (os_strncmp(buf, "osu_nai2=", 9) == 0) {
+			os_snprintf(last->osu_nai2, sizeof(last->osu_nai2),
+				    "%s", buf + 9);
+			continue;
+		}
+
 		if (strncmp(buf, "friendly_name=", 14) == 0) {
 			struct osu_lang_text *txt;
 			if (last->friendly_name_count == MAX_OSU_VALS)
@@ -2126,9 +2143,9 @@
 
 
 static int osu_connect(struct hs20_osu_client *ctx, const char *bssid,
-		       const char *ssid, const char *url,
+		       const char *ssid, const char *ssid2, const char *url,
 		       unsigned int methods, int no_prod_assoc,
-		       const char *osu_nai)
+		       const char *osu_nai, const char *osu_nai2)
 {
 	int id;
 	const char *ifname = ctx->ifname;
@@ -2136,17 +2153,41 @@
 	struct wpa_ctrl *mon;
 	int res;
 
+	if (ssid2 && ssid2[0] == '\0')
+		ssid2 = NULL;
+
+	if (ctx->osu_ssid) {
+		if (os_strcmp(ssid, ctx->osu_ssid) == 0) {
+			wpa_printf(MSG_DEBUG,
+				   "Enforced OSU SSID matches ANQP info");
+			ssid2 = NULL;
+		} else if (ssid2 && os_strcmp(ssid2, ctx->osu_ssid) == 0) {
+			wpa_printf(MSG_DEBUG,
+				   "Enforced OSU SSID matches RSN[OSEN] info");
+			ssid = ssid2;
+		} else {
+			wpa_printf(MSG_INFO, "Enforced OSU SSID did not match");
+			write_summary(ctx, "Enforced OSU SSID did not match");
+			return -1;
+		}
+	}
+
 	id = add_network(ifname);
 	if (id < 0)
 		return -1;
 	if (set_network_quoted(ifname, id, "ssid", ssid) < 0)
 		return -1;
+	if (ssid2)
+		osu_nai = osu_nai2;
 	if (osu_nai && os_strlen(osu_nai) > 0) {
 		char dir[255], fname[300];
 		if (getcwd(dir, sizeof(dir)) == NULL)
 			return -1;
 		os_snprintf(fname, sizeof(fname), "%s/osu-ca.pem", dir);
 
+		if (ssid2 && set_network_quoted(ifname, id, "ssid", ssid2) < 0)
+			return -1;
+
 		if (set_network(ifname, id, "proto", "OSEN") < 0 ||
 		    set_network(ifname, id, "key_mgmt", "OSEN") < 0 ||
 		    set_network(ifname, id, "pairwise", "CCMP") < 0 ||
@@ -2156,6 +2197,10 @@
 		    set_network_quoted(ifname, id, "identity", osu_nai) < 0 ||
 		    set_network_quoted(ifname, id, "ca_cert", fname) < 0)
 			return -1;
+	} else if (ssid2) {
+		wpa_printf(MSG_INFO, "No OSU_NAI set for RSN[OSEN]");
+		write_summary(ctx, "No OSU_NAI set for RSN[OSEN]");
+		return -1;
 	} else {
 		if (set_network(ifname, id, "key_mgmt", "NONE") < 0)
 			return -1;
@@ -2331,8 +2376,12 @@
 		fprintf(f, "</table></a><br><small>BSSID: %s<br>\n"
 			"SSID: %s<br>\n",
 			last->bssid, last->osu_ssid);
+		if (last->osu_ssid2[0])
+			fprintf(f, "SSID2: %s<br>\n", last->osu_ssid2);
 		if (last->osu_nai[0])
 			fprintf(f, "NAI: %s<br>\n", last->osu_nai);
+		if (last->osu_nai2[0])
+			fprintf(f, "NAI2: %s<br>\n", last->osu_nai2);
 		fprintf(f, "URL: %s<br>\n"
 			"methods:%s%s<br>\n"
 			"</small></p>\n",
@@ -2359,6 +2408,8 @@
 		ret = 0;
 		wpa_printf(MSG_INFO, "BSSID: %s", last->bssid);
 		wpa_printf(MSG_INFO, "SSID: %s", last->osu_ssid);
+		if (last->osu_ssid2[0])
+			wpa_printf(MSG_INFO, "SSID2: %s", last->osu_ssid2);
 		wpa_printf(MSG_INFO, "URL: %s", last->url);
 		write_summary(ctx, "Selected OSU provider id=%d BSSID=%s SSID=%s URL=%s",
 			      ret, last->bssid, last->osu_ssid, last->url);
@@ -2413,10 +2464,13 @@
 					   "No supported OSU provisioning method");
 				ret = -1;
 			}
-		} else if (connect)
+		} else if (connect) {
 			ret = osu_connect(ctx, last->bssid, last->osu_ssid,
+					  last->osu_ssid2,
 					  last->url, last->methods,
-					  no_prod_assoc, last->osu_nai);
+					  no_prod_assoc, last->osu_nai,
+					  last->osu_nai2);
+		}
 	} else
 		ret = -1;
 
@@ -3134,7 +3188,7 @@
 		return -1;
 
 	for (;;) {
-		c = getopt(argc, argv, "df:hKNO:qr:s:S:tw:x:");
+		c = getopt(argc, argv, "df:hKNo:O:qr:s:S:tw:x:");
 		if (c < 0)
 			break;
 		switch (c) {
@@ -3151,6 +3205,9 @@
 		case 'N':
 			no_prod_assoc = 1;
 			break;
+		case 'o':
+			ctx.osu_ssid = optarg;
+			break;
 		case 'O':
 			friendly_name = optarg;
 			break;
diff --git a/hs20/client/osu_client.h b/hs20/client/osu_client.h
index 9a7059e..5c8e6d0 100644
--- a/hs20/client/osu_client.h
+++ b/hs20/client/osu_client.h
@@ -47,6 +47,7 @@
 	int client_cert_present;
 	char **server_dnsname;
 	size_t server_dnsname_count;
+	const char *osu_ssid; /* Enforced OSU_SSID for testing purposes */
 #define WORKAROUND_OCSP_OPTIONAL 0x00000001
 	unsigned long int workarounds;
 };
diff --git a/hs20/server/hs20_spp_server.c b/hs20/server/hs20_spp_server.c
index 591f66b..abd6867 100644
--- a/hs20/server/hs20_spp_server.c
+++ b/hs20/server/hs20_spp_server.c
@@ -70,6 +70,10 @@
 	ctx->addr = getenv("HS20ADDR");
 	if (ctx->addr)
 		debug_print(ctx, 1, "Connection from %s", ctx->addr);
+	ctx->test = getenv("HS20TEST");
+	if (ctx->test)
+		debug_print(ctx, 1, "Requested test functionality: %s",
+			    ctx->test);
 
 	user = getenv("HS20USER");
 	if (user && strlen(user) == 0)
diff --git a/hs20/server/spp_server.c b/hs20/server/spp_server.c
index 51c1d96..e5af4c2 100644
--- a/hs20/server/spp_server.c
+++ b/hs20/server/spp_server.c
@@ -57,19 +57,26 @@
 			  const char *user, const char *realm,
 			  const char *sessionid, const char *pw,
 			  const char *redirect_uri,
-			  enum hs20_session_operation operation)
+			  enum hs20_session_operation operation,
+			  const u8 *mac_addr)
 {
 	char *sql;
 	int ret = 0;
+	char addr[20];
 
+	if (mac_addr)
+		snprintf(addr, sizeof(addr), MACSTR, MAC2STR(mac_addr));
+	else
+		addr[0] = '\0';
 	sql = sqlite3_mprintf("INSERT INTO sessions(timestamp,id,user,realm,"
-			      "operation,password,redirect_uri) "
+			      "operation,password,redirect_uri,mac_addr,test) "
 			      "VALUES "
 			      "(strftime('%%Y-%%m-%%d %%H:%%M:%%f','now'),"
-			      "%Q,%Q,%Q,%d,%Q,%Q)",
+			      "%Q,%Q,%Q,%d,%Q,%Q,%Q,%Q)",
 			      sessionid, user ? user : "", realm ? realm : "",
 			      operation, pw ? pw : "",
-			      redirect_uri ? redirect_uri : "");
+			      redirect_uri ? redirect_uri : "",
+			      addr, ctx->test);
 	if (sql == NULL)
 		return -1;
 	debug_print(ctx, 1, "DB: %s", sql);
@@ -329,6 +336,29 @@
 }
 
 
+static void add_text_node_conf_corrupt(struct hs20_svc *ctx, const char *realm,
+				       xml_node_t *parent, const char *name,
+				       const char *field)
+{
+	char *val;
+
+	val = db_get_osu_config_val(ctx, realm, field);
+	if (val) {
+		size_t len;
+
+		len = os_strlen(val);
+		if (len > 0) {
+			if (val[len - 1] == '0')
+				val[len - 1] = '1';
+			else
+				val[len - 1] = '0';
+		}
+	}
+	xml_node_create_text(ctx->xml, parent, NULL, name, val ? val : "");
+	os_free(val);
+}
+
+
 static int new_password(char *buf, int buflen)
 {
 	int i;
@@ -533,7 +563,8 @@
 
 
 static int add_username_password(struct hs20_svc *ctx, xml_node_t *cred,
-				 const char *user, const char *pw)
+				 const char *user, const char *pw,
+				 int machine_managed)
 {
 	xml_node_t *node;
 
@@ -541,7 +572,8 @@
 	if (node == NULL)
 		return -1;
 
-	add_text_node(ctx, node, "MachineManaged", "TRUE");
+	add_text_node(ctx, node, "MachineManaged",
+		      machine_managed ? "TRUE" : "FALSE");
 	add_text_node(ctx, node, "SoftTokenApp", "");
 	add_eap_ttls(ctx, node);
 
@@ -566,7 +598,7 @@
 
 static xml_node_t * build_credential_pw(struct hs20_svc *ctx,
 					const char *user, const char *realm,
-					const char *pw)
+					const char *pw, int machine_managed)
 {
 	xml_node_t *cred;
 
@@ -576,7 +608,7 @@
 		return NULL;
 	}
 	add_creation_date(ctx, cred);
-	if (add_username_password(ctx, cred, user, pw) < 0) {
+	if (add_username_password(ctx, cred, user, pw, machine_managed) < 0) {
 		xml_node_free(ctx->xml, cred);
 		return NULL;
 	}
@@ -593,7 +625,7 @@
 	if (new_password(new_pw, new_pw_len) < 0)
 		return NULL;
 	debug_print(ctx, 1, "Update password to '%s'", new_pw);
-	return build_credential_pw(ctx, user, realm, new_pw);
+	return build_credential_pw(ctx, user, realm, new_pw, 1);
 }
 
 
@@ -703,8 +735,23 @@
 		cred = build_credential_cert(ctx, real_user ? real_user : user,
 					     realm, cert);
 	} else {
-		cred = build_credential(ctx, real_user ? real_user : user,
-					realm, new_pw, sizeof(new_pw));
+		char *pw;
+
+		pw = db_get_session_val(ctx, user, realm, session_id,
+					"password");
+		if (pw && pw[0]) {
+			debug_print(ctx, 1, "New password from the user: '%s'",
+				    pw);
+			snprintf(new_pw, sizeof(new_pw), "%s", pw);
+			free(pw);
+			cred = build_credential_pw(ctx,
+						   real_user ? real_user : user,
+						   realm, new_pw, 0);
+		} else {
+			cred = build_credential(ctx,
+						real_user ? real_user : user,
+						realm, new_pw, sizeof(new_pw));
+		}
 	}
 	free(real_user);
 	if (!cred) {
@@ -721,7 +768,7 @@
 	}
 
 	snprintf(buf, sizeof(buf),
-		 "./Wi-Fi/%s/PerProviderSubscription/Credential1/Credential",
+		 "./Wi-Fi/%s/PerProviderSubscription/Cred01/Credential",
 		 realm);
 
 	if (add_update_node(ctx, spp_node, ns, buf, cred) < 0) {
@@ -742,7 +789,7 @@
 		debug_print(ctx, 1, "Request DB password update on success "
 			    "notification");
 		db_add_session(ctx, user, realm, session_id, new_pw, NULL,
-			       UPDATE_PASSWORD);
+			       UPDATE_PASSWORD, NULL);
 	}
 
 	return spp_node;
@@ -771,7 +818,7 @@
 		      "requires policy remediation", NULL);
 
 	db_add_session(ctx, user, realm, session_id, NULL, NULL,
-		       POLICY_REMEDIATION);
+		       POLICY_REMEDIATION, NULL);
 
 	policy = build_policy(ctx, user, realm, dmacc);
 	if (!policy) {
@@ -787,7 +834,7 @@
 		return NULL;
 
 	snprintf(buf, sizeof(buf),
-		 "./Wi-Fi/%s/PerProviderSubscription/Credential1/Policy",
+		 "./Wi-Fi/%s/PerProviderSubscription/Cred01/Policy",
 		 realm);
 
 	if (add_update_node(ctx, spp_node, ns, buf, policy) < 0) {
@@ -844,7 +891,7 @@
 		return NULL;
 
 	db_add_session(ctx, user, realm, session_id, NULL, redirect_uri,
-		       USER_REMEDIATION);
+		       USER_REMEDIATION, NULL);
 
 	snprintf(uri, sizeof(uri), "%s%s", val, session_id);
 	os_free(val);
@@ -866,7 +913,7 @@
 		return NULL;
 
 	db_add_session(ctx, user, realm, session_id, NULL, redirect_uri,
-		       FREE_REMEDIATION);
+		       FREE_REMEDIATION, NULL);
 
 	snprintf(uri, sizeof(uri), "%s%s", val, session_id);
 	os_free(val);
@@ -940,8 +987,10 @@
 				       redirect_uri);
 	else if (type && strcmp(type, "policy") == 0)
 		ret = policy_remediation(ctx, user, realm, session_id, dmacc);
-	else
+	else if (type && strcmp(type, "machine") == 0)
 		ret = machine_remediation(ctx, user, realm, session_id, dmacc);
+	else
+		ret = no_sub_rem(ctx, user, realm, session_id);
 	free(type);
 
 	return ret;
@@ -1033,7 +1082,8 @@
 			"No update available at this time", NULL);
 	}
 
-	db_add_session(ctx, user, realm, session_id, NULL, NULL, POLICY_UPDATE);
+	db_add_session(ctx, user, realm, session_id, NULL, NULL, POLICY_UPDATE,
+		       NULL);
 
 	status = "Update complete, request sppUpdateResponse";
 	spp_node = build_post_dev_data_response(ctx, &ns, session_id, status,
@@ -1042,7 +1092,7 @@
 		return NULL;
 
 	snprintf(buf, sizeof(buf),
-		 "./Wi-Fi/%s/PerProviderSubscription/Credential1/Policy",
+		 "./Wi-Fi/%s/PerProviderSubscription/Cred01/Policy",
 		 realm);
 
 	if (add_update_node(ctx, spp_node, ns, buf, policy) < 0) {
@@ -1146,14 +1196,15 @@
 static xml_node_t * hs20_subscription_registration(struct hs20_svc *ctx,
 						   const char *realm,
 						   const char *session_id,
-						   const char *redirect_uri)
+						   const char *redirect_uri,
+						   const u8 *mac_addr)
 {
 	xml_namespace_t *ns;
 	xml_node_t *spp_node, *exec_node;
 	char uri[300], *val;
 
 	if (db_add_session(ctx, NULL, realm, session_id, NULL, redirect_uri,
-			   SUBSCRIPTION_REGISTRATION) < 0)
+			   SUBSCRIPTION_REGISTRATION, mac_addr) < 0)
 		return NULL;
 	val = db_get_osu_config_val(ctx, realm, "signup_url");
 	if (val == NULL)
@@ -1213,9 +1264,9 @@
 static xml_node_t * build_pps(struct hs20_svc *ctx,
 			      const char *user, const char *realm,
 			      const char *pw, const char *cert,
-			      int machine_managed)
+			      int machine_managed, const char *test)
 {
-	xml_node_t *pps, *c, *trust, *aaa, *aaa1, *upd, *homesp;
+	xml_node_t *pps, *c, *trust, *aaa, *aaa1, *upd, *homesp, *p;
 	xml_node_t *cred, *eap, *userpw;
 
 	pps = xml_node_create_root(ctx->xml, NULL, NULL, NULL,
@@ -1225,7 +1276,7 @@
 
 	add_text_node(ctx, pps, "UpdateIdentifier", "1");
 
-	c = xml_node_create(ctx->xml, pps, NULL, "Credential1");
+	c = xml_node_create(ctx->xml, pps, NULL, "Cred01");
 
 	add_text_node(ctx, c, "CredentialPriority", "1");
 
@@ -1233,18 +1284,51 @@
 	aaa1 = xml_node_create(ctx->xml, aaa, NULL, "AAA1");
 	add_text_node_conf(ctx, realm, aaa1, "CertURL",
 			   "aaa_trust_root_cert_url");
-	add_text_node_conf(ctx, realm, aaa1, "CertSHA256Fingerprint",
-			   "aaa_trust_root_cert_fingerprint");
+	if (test && os_strcmp(test, "corrupt_aaa_hash") == 0) {
+		debug_print(ctx, 1,
+			    "TEST: Corrupt PPS/Cred*/AAAServerTrustRoot/Root*/CertSHA256FingerPrint");
+		add_text_node_conf_corrupt(ctx, realm, aaa1,
+					   "CertSHA256Fingerprint",
+					   "aaa_trust_root_cert_fingerprint");
+	} else {
+		add_text_node_conf(ctx, realm, aaa1, "CertSHA256Fingerprint",
+				   "aaa_trust_root_cert_fingerprint");
+	}
+
+	if (test && os_strcmp(test, "corrupt_polupd_hash") == 0) {
+		debug_print(ctx, 1,
+			    "TEST: Corrupt PPS/Cred*/Policy/PolicyUpdate/Trustroot/CertSHA256FingerPrint");
+		p = xml_node_create(ctx->xml, c, NULL, "Policy");
+		upd = xml_node_create(ctx->xml, p, NULL, "PolicyUpdate");
+		add_text_node(ctx, upd, "UpdateInterval", "30");
+		add_text_node(ctx, upd, "UpdateMethod", "SPP-ClientInitiated");
+		add_text_node(ctx, upd, "Restriction", "Unrestricted");
+		add_text_node_conf(ctx, realm, upd, "URI", "policy_url");
+		trust = xml_node_create(ctx->xml, upd, NULL, "TrustRoot");
+		add_text_node_conf(ctx, realm, trust, "CertURL",
+				   "policy_trust_root_cert_url");
+		add_text_node_conf_corrupt(ctx, realm, trust,
+					   "CertSHA256Fingerprint",
+					   "policy_trust_root_cert_fingerprint");
+	}
 
 	upd = xml_node_create(ctx->xml, c, NULL, "SubscriptionUpdate");
 	add_text_node(ctx, upd, "UpdateInterval", "4294967295");
-	add_text_node(ctx, upd, "UpdateMethod", "ClientInitiated");
+	add_text_node(ctx, upd, "UpdateMethod", "SPP-ClientInitiated");
 	add_text_node(ctx, upd, "Restriction", "HomeSP");
 	add_text_node_conf(ctx, realm, upd, "URI", "spp_http_auth_url");
 	trust = xml_node_create(ctx->xml, upd, NULL, "TrustRoot");
 	add_text_node_conf(ctx, realm, trust, "CertURL", "trust_root_cert_url");
-	add_text_node_conf(ctx, realm, trust, "CertSHA256Fingerprint",
-			   "trust_root_cert_fingerprint");
+	if (test && os_strcmp(test, "corrupt_subrem_hash") == 0) {
+		debug_print(ctx, 1,
+			    "TEST: Corrupt PPS/Cred*/SubscriptionUpdate/Trustroot/CertSHA256FingerPrint");
+		add_text_node_conf_corrupt(ctx, realm, trust,
+					   "CertSHA256Fingerprint",
+					   "trust_root_cert_fingerprint");
+	} else {
+		add_text_node_conf(ctx, realm, trust, "CertSHA256Fingerprint",
+				   "trust_root_cert_fingerprint");
+	}
 
 	homesp = xml_node_create(ctx->xml, c, NULL, "HomeSP");
 	add_text_node_conf(ctx, realm, homesp, "FriendlyName", "friendly_name");
@@ -1328,7 +1412,7 @@
 	xml_node_t *pps, *tnds;
 	char buf[400];
 	char *str;
-	char *user, *realm, *pw, *type, *mm;
+	char *user, *realm, *pw, *type, *mm, *test;
 	const char *status;
 	int cert = 0;
 	int machine_managed = 0;
@@ -1387,9 +1471,15 @@
 		return NULL;
 
 	fingerprint = db_get_session_val(ctx, NULL, NULL, session_id, "cert");
+	test = db_get_session_val(ctx, NULL, NULL, session_id, "test");
+	if (test)
+		debug_print(ctx, 1, "TEST: Requested special behavior: %s",
+			    test);
 	pps = build_pps(ctx, user, realm, pw,
-			fingerprint ? fingerprint : NULL, machine_managed);
+			fingerprint ? fingerprint : NULL, machine_managed,
+			test);
 	free(fingerprint);
+	free(test);
 	if (!pps) {
 		xml_node_free(ctx->xml, spp_node);
 		free(user);
@@ -1460,7 +1550,7 @@
 		return NULL;
 	}
 
-	cred = build_credential_pw(ctx, free_account, realm, pw);
+	cred = build_credential_pw(ctx, free_account, realm, pw, 1);
 	free(free_account);
 	free(pw);
 	if (!cred) {
@@ -1475,7 +1565,7 @@
 		return NULL;
 
 	snprintf(buf, sizeof(buf),
-		 "./Wi-Fi/%s/PerProviderSubscription/Credential1/Credential",
+		 "./Wi-Fi/%s/PerProviderSubscription/Cred01/Credential",
 		 realm);
 
 	if (add_update_node(ctx, spp_node, ns, buf, cred) < 0) {
@@ -1606,11 +1696,12 @@
 	char *req_reason_buf = NULL;
 	char str[200];
 	xml_node_t *ret = NULL, *devinfo = NULL, *devdetail = NULL;
-	xml_node_t *mo;
+	xml_node_t *mo, *macaddr;
 	char *version;
 	int valid;
 	char *supp, *pos;
 	char *err;
+	u8 wifi_mac_addr[ETH_ALEN];
 
 	version = xml_node_get_attr_value_ns(ctx->xml, node, SPP_NS_URI,
 					     "sppVersion");
@@ -1716,6 +1807,29 @@
 		goto out;
 	}
 	os_free(err);
+
+	os_memset(wifi_mac_addr, 0, ETH_ALEN);
+	macaddr = get_node(ctx->xml, devdetail,
+			   "Ext/org.wi-fi/Wi-Fi/Wi-FiMACAddress");
+	if (macaddr) {
+		char *addr, buf[50];
+
+		addr = xml_node_get_text(ctx->xml, macaddr);
+		if (addr && hwaddr_compact_aton(addr, wifi_mac_addr) == 0) {
+			snprintf(buf, sizeof(buf), "DevDetail MAC address: "
+				 MACSTR, MAC2STR(wifi_mac_addr));
+			hs20_eventlog(ctx, user, realm, session_id, buf, NULL);
+			xml_node_get_text_free(ctx->xml, addr);
+		} else {
+			hs20_eventlog(ctx, user, realm, session_id,
+				      "Could not extract MAC address from DevDetail",
+				      NULL);
+		}
+	} else {
+		hs20_eventlog(ctx, user, realm, session_id,
+			      "No MAC address in DevDetail", NULL);
+	}
+
 	if (user)
 		db_update_mo(ctx, user, realm, "devdetail", devdetail);
 
@@ -1762,7 +1876,7 @@
 			else
 				oper = NO_OPERATION;
 			if (db_add_session(ctx, user, realm, session_id, NULL,
-					   NULL, oper) < 0)
+					   NULL, oper, NULL) < 0)
 				goto out;
 
 			ret = spp_exec_upload_mo(ctx, session_id,
@@ -1799,7 +1913,8 @@
 
 	if (strcasecmp(req_reason, "Subscription registration") == 0) {
 		ret = hs20_subscription_registration(ctx, realm, session_id,
-						     redirect_uri);
+						     redirect_uri,
+						     wifi_mac_addr);
 		hs20_eventlog_node(ctx, user, realm, session_id,
 				   "subscription registration response",
 				   ret);
@@ -1948,13 +2063,15 @@
 		goto out;
 	}
 
-	sql = sqlite3_mprintf("INSERT INTO users(identity,realm,phase2,"
-			      "methods,cert,cert_pem,machine_managed) VALUES "
-			      "(%Q,%Q,1,%Q,%Q,%Q,%d)",
+	str = db_get_session_val(ctx, NULL, NULL, session_id, "mac_addr");
+
+	sql = sqlite3_mprintf("INSERT INTO users(identity,realm,phase2,methods,cert,cert_pem,machine_managed,mac_addr) VALUES (%Q,%Q,1,%Q,%Q,%Q,%d,%Q)",
 			      user, realm, cert ? "TLS" : "TTLS-MSCHAPV2",
 			      fingerprint ? fingerprint : "",
 			      cert_pem ? cert_pem : "",
-			      pw_mm && atoi(pw_mm) ? 1 : 0);
+			      pw_mm && atoi(pw_mm) ? 1 : 0,
+			      str ? str : "");
+	free(str);
 	if (sql == NULL)
 		goto out;
 	debug_print(ctx, 1, "DB: %s", sql);
@@ -1996,6 +2113,32 @@
 		free(str);
 	}
 
+	if (cert && user) {
+		const char *serialnum;
+
+		str = db_get_session_val(ctx, NULL, NULL, session_id,
+					 "mac_addr");
+
+		if (os_strncmp(user, "cert-", 5) == 0)
+			serialnum = user + 5;
+		else
+			serialnum = "";
+		sql = sqlite3_mprintf("INSERT OR REPLACE INTO cert_enroll (mac_addr,user,realm,serialnum) VALUES(%Q,%Q,%Q,%Q)",
+				      str ? str : "", user, realm ? realm : "",
+				      serialnum);
+		free(str);
+		if (sql) {
+			debug_print(ctx, 1, "DB: %s", sql);
+			if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) !=
+			    SQLITE_OK) {
+				debug_print(ctx, 1,
+					    "Failed to add cert_enroll entry into sqlite database: %s",
+					    sqlite3_errmsg(ctx->db));
+			}
+			sqlite3_free(sql);
+		}
+	}
+
 	if (ret == 0) {
 		hs20_eventlog(ctx, user, realm, session_id,
 			      "completed subscription registration", NULL);
@@ -2119,6 +2262,9 @@
 					      "", dmacc);
 			free(val);
 		}
+		if (oper == POLICY_UPDATE)
+			db_update_val(ctx, user, realm, "polupd_done", "1",
+				      dmacc);
 		ret = build_spp_exchange_complete(
 			ctx, session_id,
 			"Exchange complete, release TLS connection", NULL);
diff --git a/hs20/server/spp_server.h b/hs20/server/spp_server.h
index 7b27be3..3556f5c 100644
--- a/hs20/server/spp_server.h
+++ b/hs20/server/spp_server.h
@@ -16,6 +16,7 @@
 	FILE *debug_log;
 	sqlite3 *db;
 	const char *addr;
+	const char *test;
 };
 
 
diff --git a/hs20/server/sql.txt b/hs20/server/sql.txt
index 74d9f4a..666ef13 100644
--- a/hs20/server/sql.txt
+++ b/hs20/server/sql.txt
@@ -22,7 +22,9 @@
 	devinfo TEXT,
 	devdetail TEXT,
 	cert TEXT,
-	cert_pem TEXT
+	cert_pem TEXT,
+	mac_addr TEXT,
+	test TEXT
 );
 
 CREATE index sessions_id_index ON sessions(id);
@@ -51,7 +53,10 @@
 	shared INTEGER,
 	cert TEXT,
 	cert_pem TEXT,
-	t_c_timestamp INTEGER
+	t_c_timestamp INTEGER,
+	mac_addr TEXT,
+	last_msk TEXT,
+	polupd_done TEXT,
 );
 
 CREATE TABLE wildcards(
@@ -81,3 +86,10 @@
 	waiting_coa_ack BOOLEAN,
 	coa_ack_received BOOLEAN
 );
+
+CREATE TABLE cert_enroll(
+	mac_addr TEXT PRIMARY KEY,
+	user TEXT,
+	realm TEXT,
+	serialnum TEXT
+);
diff --git a/hs20/server/www/est.php b/hs20/server/www/est.php
index a45648b..6983ec9 100644
--- a/hs20/server/www/est.php
+++ b/hs20/server/www/est.php
@@ -2,7 +2,7 @@
 
 require('config.php');
 
-$params = split("/", $_SERVER["PATH_INFO"], 3);
+$params = explode("/", $_SERVER["PATH_INFO"], 3);
 $realm = $params[1];
 $cmd = $params[2];
 $method = $_SERVER["REQUEST_METHOD"];
diff --git a/hs20/server/www/remediation-pw.php b/hs20/server/www/remediation-pw.php
new file mode 100644
index 0000000..76fdccb
--- /dev/null
+++ b/hs20/server/www/remediation-pw.php
@@ -0,0 +1,41 @@
+<?php
+
+require('config.php');
+
+$db = new PDO($osu_db);
+if (!$db) {
+   die($sqliteerror);
+}
+
+if (isset($_POST["id"]))
+  $id = preg_replace("/[^a-fA-F0-9]/", "", $_POST["id"]);
+else
+  die("Missing session id");
+
+$pw = $_POST["password"];
+if (strlen($id) < 32 || !isset($pw)) {
+  die("Invalid POST data");
+}
+
+$row = $db->query("SELECT rowid,* FROM sessions WHERE id='$id'")->fetch();
+if ($row == false) {
+   die("Session not found");
+}
+$user = $row['user'];
+$realm = $row['realm'];
+
+$uri = $row['redirect_uri'];
+$rowid = $row['rowid'];
+
+if (!$db->exec("UPDATE sessions SET password='$pw' WHERE rowid=$rowid")) {
+  die("Failed to update session database");
+}
+
+$db->exec("INSERT INTO eventlog(user,realm,sessionid,timestamp,notes) " .
+	"VALUES ('$user', '$realm', '$id', " .
+	"strftime('%Y-%m-%d %H:%M:%f','now'), " .
+	"'completed user input response for subscription remediation')");
+
+header("Location: $uri", true, 302);
+
+?>
diff --git a/hs20/server/www/remediation.php b/hs20/server/www/remediation.php
index 392a7bd..3628065 100644
--- a/hs20/server/www/remediation.php
+++ b/hs20/server/www/remediation.php
@@ -6,13 +6,50 @@
 
 <?php
 
-echo "SessionID: " . $_GET["session_id"] . "<br>\n";
+require('config.php');
 
-echo "<a href=\"redirect.php?id=" . $_GET["session_id"] . "\">Complete user subscription remediation</a><br>\n";
+$db = new PDO($osu_db);
+if (!$db) {
+   die($sqliteerror);
+}
+
+if (isset($_GET["session_id"]))
+	$id = preg_replace("/[^a-fA-F0-9]/", "", $_GET["session_id"]);
+else
+	$id = 0;
+echo "SessionID: " . $id . "<br>\n";
+
+$row = $db->query("SELECT * FROM sessions WHERE id='$id'")->fetch();
+if ($row == false) {
+   die("Session not found");
+}
+
+$username = $row['user'];
+echo "User: " . $username . "@" . $row['realm'] . "<br>\n";
+
+$user = $db->query("SELECT machine_managed,methods FROM users WHERE identity='$username'")->fetch();
+if ($user == false) {
+   die("User not found");
+}
+
+echo "<hr><br>\n";
+
+$cert = $user['methods'] == "TLS" || strncmp($username, "cert-", 5) == 0;
+
+if ($cert) {
+   echo "<a href=\"redirect.php?id=" . $_GET["session_id"] . "\">Complete user subscription remediation</a><br>\n";
+} else if ($user['machine_managed'] == "1") {
+   echo "<a href=\"redirect.php?id=" . $_GET["session_id"] . "\">Complete user subscription remediation</a><br>\n";
+   echo "This will provide a new machine-generated password.<br>\n";
+} else {
+   echo "<form action=\"remediation-pw.php\" method=\"POST\">\n";
+   echo "<input type=\"hidden\" name=\"id\" value=\"$id\">\n";
+   echo "New password: <input type=\"password\" name=\"password\"><br>\n";
+   echo "<input type=\"submit\" value=\"Change password\">\n";
+   echo "</form>\n";
+}
 
 ?>
 
-This will provide a new machine-generated password.
-
 </body>
 </html>
diff --git a/hs20/server/www/signup.php b/hs20/server/www/signup.php
index aeb2f68..80a9d40 100644
--- a/hs20/server/www/signup.php
+++ b/hs20/server/www/signup.php
@@ -15,19 +15,30 @@
    die($sqliteerror);
 }
 
-$row = $db->query("SELECT realm FROM sessions WHERE id='$id'")->fetch();
+$row = $db->query("SELECT realm,test FROM sessions WHERE id='$id'")->fetch();
 if ($row == false) {
    die("Session not found for id: $id");
 }
 $realm = $row['realm'];
+$test = $row['test'];
+
+if (strlen($test) > 0) {
+  echo "<p style=\"color:#FF0000\">Special test functionality: $test</red></big></p>\n";
+}
 
 echo "<h3>Sign up for a subscription - $realm</h3>\n";
 
+echo "<p>This page can be used to select between three different types of subscriptions for testing purposes.</p>\n";
+
+echo "<h4>Option 1 - shared free access credential</h4>\n";
+
 $row = $db->query("SELECT value FROM osu_config WHERE realm='$realm' AND field='free_account'")->fetch();
 if ($row && strlen($row['value']) > 0) {
   echo "<p><a href=\"free.php?session_id=$id\">Sign up for free access</a></p>\n";
 }
 
+echo "<h4>Option 2 - username/password credential</h4>\n";
+
 echo "<form action=\"add-mo.php\" method=\"POST\">\n";
 echo "<input type=\"hidden\" name=\"id\" value=\"$id\">\n";
 ?>
@@ -39,6 +50,8 @@
 </form>
 
 <?php
+echo "<h4>Option 3 - client certificate credential</h4>\n";
+
 echo "<p><a href=\"cert-enroll.php?id=$id\">Enroll a client certificate</a></p>\n"
 ?>
 
diff --git a/hs20/server/www/spp.php b/hs20/server/www/spp.php
index 002d028..f10e5ab 100644
--- a/hs20/server/www/spp.php
+++ b/hs20/server/www/spp.php
@@ -20,6 +20,11 @@
   die("Realm not specified");
 }
 
+if (isset($_GET["test"]))
+  $test = PREG_REPLACE("/[^0-9a-zA-Z\_\-]/i", '', $_GET["test"]);
+else
+  $test = "";
+
 unset($user);
 putenv("HS20CERT");
 
@@ -100,6 +105,7 @@
 putenv("HS20POST=$postdata");
 $addr = $_SERVER["REMOTE_ADDR"];
 putenv("HS20ADDR=$addr");
+putenv("HS20TEST=$test");
 
 $last = exec("$osu_root/spp/hs20_spp_server -r$osu_root -f/tmp/hs20_spp_server.log", $output, $ret);
 
diff --git a/hs20/server/www/terms.php b/hs20/server/www/terms.php
index e360be5..e269b3c 100644
--- a/hs20/server/www/terms.php
+++ b/hs20/server/www/terms.php
@@ -59,8 +59,10 @@
       if (!$row) {
          die("No current session for the specified MAC address");
       }
-      $waiting = $row[0] == 1;
-      $ack = $row[1] == 1;
+      if (strlen($row[0]) > 0)
+            $waiting = $row[0] == 1;
+      if (strlen($row[1]) > 0)
+            $ack = $row[1] == 1;
       $res->closeCursor();
       if (!$waiting)
          break;
diff --git a/hs20/server/www/users.php b/hs20/server/www/users.php
index c265372..f546de3 100644
--- a/hs20/server/www/users.php
+++ b/hs20/server/www/users.php
@@ -191,6 +191,9 @@
 }
 echo "<br>\n";
 
+if (strncmp($row['identity'], "cert-", 5) != 0)
+   echo "Machine managed: " . ($row['machine_managed'] == "1" ? "TRUE" : "FALSE") . "<br>\n";
+
 echo "<form>Policy: <select name=\"policy\" " .
 	"onChange=\"window.location='users.php?cmd=policy&id=" .
 	$row['rowid'] . "&policy=' + this.value;\">\n";
@@ -313,10 +316,10 @@
 echo "[<a href=\"users.php?cmd=eventlog&limit=50\">Eventlog</a>] ";
 echo "<br>\n";
 
-echo "<table border=1>\n";
-echo "<tr><th>User<th>Realm<th>Remediation<th>Policy<th>Account type<th>Phase 2 method(s)<th>DevId<th>T&C\n";
+echo "<table border=1 cellspacing=0 cellpadding=0>\n";
+echo "<tr><th>User<th>Realm<th><small>Remediation</small><th>Policy<th><small>Account type</small><th><small>Phase 2 method(s)</small><th>DevId<th>MAC Address<th>T&C\n";
 
-$res = $db->query('SELECT rowid,* FROM users WHERE phase2=1');
+$res = $db->query('SELECT rowid,* FROM users WHERE phase2=1 ORDER BY identity');
 foreach ($res as $row) {
 	echo "<tr><td><a href=\"users.php?id=" . $row['rowid'] . "\"> " .
 	    $row['identity'] . " </a>";
@@ -324,7 +327,7 @@
 	$rem = $row['remediation'];
 	echo "<td>";
 	if ($rem == "") {
-		echo "Not required";
+		echo "-";
 	} else if ($rem == "user") {
 		echo "User";
 	} else if ($rem == "policy") {
@@ -339,17 +342,18 @@
 	  echo "<td>shared";
 	else
 	  echo "<td>default";
-	echo "<td>" . $row['methods'];
+	echo "<td><small>" . $row['methods'] . "</small>";
 	echo "<td>";
 	$xml = xml_parser_create();
 	xml_parse_into_struct($xml, $row['devinfo'], $devinfo);
 	foreach($devinfo as $k) {
 	  if ($k['tag'] == 'DEVID') {
-	    echo $k['value'];
+	    echo "<small>" . $k['value'] . "</small>";
 	    break;
 	  }
 	}
-	echo "<td>" . $row['t_c_timestamp'];
+	echo "<td><small>" . $row['mac_addr'] . "</small>";
+	echo "<td><small>" . $row['t_c_timestamp'] . "</small>";
 	echo "\n";
 }
 echo "</table>\n";
diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
index 820cba9..f9b6f29 100644
--- a/src/ap/ap_config.c
+++ b/src/ap/ap_config.c
@@ -631,6 +631,7 @@
 				os_free(p->icons[j]);
 			os_free(p->icons);
 			os_free(p->osu_nai);
+			os_free(p->osu_nai2);
 			os_free(p->service_desc);
 		}
 		os_free(conf->hs20_osu_providers);
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index 5b71126..778366d 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -585,10 +585,12 @@
 		char **icons;
 		size_t icons_count;
 		char *osu_nai;
+		char *osu_nai2;
 		unsigned int service_desc_count;
 		struct hostapd_lang_string *service_desc;
 	} *hs20_osu_providers, *last_osu;
 	size_t hs20_osu_providers_count;
+	size_t hs20_osu_providers_nai_count;
 	char **hs20_operator_icon;
 	size_t hs20_operator_icon_count;
 	unsigned int hs20_deauth_req_timeout;
@@ -682,6 +684,8 @@
 	char owe_transition_ifname[IFNAMSIZ + 1];
 	int *owe_groups;
 #endif /* CONFIG_OWE */
+
+	int coloc_intf_reporting;
 };
 
 /**
diff --git a/src/ap/authsrv.c b/src/ap/authsrv.c
index 66d255c..95d004e 100644
--- a/src/ap/authsrv.c
+++ b/src/ap/authsrv.c
@@ -155,6 +155,40 @@
 #endif /* RADIUS_SERVER */
 
 
+#ifdef EAP_TLS_FUNCS
+static void authsrv_tls_event(void *ctx, enum tls_event ev,
+			      union tls_event_data *data)
+{
+	switch (ev) {
+	case TLS_CERT_CHAIN_SUCCESS:
+		wpa_printf(MSG_DEBUG, "authsrv: remote certificate verification success");
+		break;
+	case TLS_CERT_CHAIN_FAILURE:
+		wpa_printf(MSG_INFO, "authsrv: certificate chain failure: reason=%d depth=%d subject='%s' err='%s'",
+			   data->cert_fail.reason,
+			   data->cert_fail.depth,
+			   data->cert_fail.subject,
+			   data->cert_fail.reason_txt);
+		break;
+	case TLS_PEER_CERTIFICATE:
+		wpa_printf(MSG_DEBUG, "authsrv: peer certificate: depth=%d serial_num=%s subject=%s",
+			   data->peer_cert.depth,
+			   data->peer_cert.serial_num ? data->peer_cert.serial_num : "N/A",
+			   data->peer_cert.subject);
+		break;
+	case TLS_ALERT:
+		if (data->alert.is_local)
+			wpa_printf(MSG_DEBUG, "authsrv: local TLS alert: %s",
+				   data->alert.description);
+		else
+			wpa_printf(MSG_DEBUG, "authsrv: remote TLS alert: %s",
+				   data->alert.description);
+		break;
+	}
+}
+#endif /* EAP_TLS_FUNCS */
+
+
 int authsrv_init(struct hostapd_data *hapd)
 {
 #ifdef EAP_TLS_FUNCS
@@ -167,6 +201,8 @@
 		os_memset(&conf, 0, sizeof(conf));
 		conf.tls_session_lifetime = hapd->conf->tls_session_lifetime;
 		conf.tls_flags = hapd->conf->tls_flags;
+		conf.event_cb = authsrv_tls_event;
+		conf.cb_ctx = hapd;
 		hapd->ssl_ctx = tls_init(&conf);
 		if (hapd->ssl_ctx == NULL) {
 			wpa_printf(MSG_ERROR, "Failed to initialize TLS");
diff --git a/src/ap/beacon.c b/src/ap/beacon.c
index 7d079d2..59bd4af 100644
--- a/src/ap/beacon.c
+++ b/src/ap/beacon.c
@@ -453,8 +453,9 @@
 	/* Extended supported rates */
 	pos = hostapd_eid_ext_supp_rates(hapd, pos);
 
-	/* RSN, MDIE, WPA */
-	pos = hostapd_eid_wpa(hapd, pos, epos - pos);
+	/* RSN, MDIE */
+	if (hapd->conf->wpa != WPA_PROTO_WPA)
+		pos = hostapd_eid_wpa(hapd, pos, epos - pos);
 
 	pos = hostapd_eid_bss_load(hapd, pos, epos - pos);
 
@@ -517,6 +518,10 @@
 		pos = hostapd_eid_vendor_vht(hapd, pos);
 #endif /* CONFIG_IEEE80211AC */
 
+	/* WPA */
+	if (hapd->conf->wpa == WPA_PROTO_WPA)
+		pos = hostapd_eid_wpa(hapd, pos, epos - pos);
+
 	/* Wi-Fi Alliance WMM */
 	pos = hostapd_eid_wmm(hapd, pos);
 
@@ -1152,9 +1157,11 @@
 	/* Extended supported rates */
 	tailpos = hostapd_eid_ext_supp_rates(hapd, tailpos);
 
-	/* RSN, MDIE, WPA */
-	tailpos = hostapd_eid_wpa(hapd, tailpos, tail + BEACON_TAIL_BUF_SIZE -
-				  tailpos);
+	/* RSN, MDIE */
+	if (hapd->conf->wpa != WPA_PROTO_WPA)
+		tailpos = hostapd_eid_wpa(hapd, tailpos,
+					  tail + BEACON_TAIL_BUF_SIZE -
+					  tailpos);
 
 	tailpos = hostapd_eid_rm_enabled_capab(hapd, tailpos,
 					       tail + BEACON_TAIL_BUF_SIZE -
@@ -1223,6 +1230,12 @@
 		tailpos = hostapd_eid_vendor_vht(hapd, tailpos);
 #endif /* CONFIG_IEEE80211AC */
 
+	/* WPA */
+	if (hapd->conf->wpa == WPA_PROTO_WPA)
+		tailpos = hostapd_eid_wpa(hapd, tailpos,
+					  tail + BEACON_TAIL_BUF_SIZE -
+					  tailpos);
+
 	/* Wi-Fi Alliance WMM */
 	tailpos = hostapd_eid_wmm(hapd, tailpos);
 
diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c
index 98a2eec..1135aea 100644
--- a/src/ap/drv_callbacks.c
+++ b/src/ap/drv_callbacks.c
@@ -338,10 +338,14 @@
 			goto fail;
 		}
 #ifdef CONFIG_IEEE80211W
-		if ((sta->flags & WLAN_STA_MFP) && !sta->sa_query_timed_out &&
+		if ((sta->flags & (WLAN_STA_ASSOC | WLAN_STA_MFP)) ==
+		    (WLAN_STA_ASSOC | WLAN_STA_MFP) &&
+		    !sta->sa_query_timed_out &&
 		    sta->sa_query_count > 0)
 			ap_check_sa_query_timeout(hapd, sta);
-		if ((sta->flags & WLAN_STA_MFP) && !sta->sa_query_timed_out &&
+		if ((sta->flags & (WLAN_STA_ASSOC | WLAN_STA_MFP)) ==
+		    (WLAN_STA_ASSOC | WLAN_STA_MFP) &&
+		    !sta->sa_query_timed_out &&
 		    (sta->auth_alg != WLAN_AUTH_FT)) {
 			/*
 			 * STA has already been associated with MFP and SA
diff --git a/src/ap/gas_serv.c b/src/ap/gas_serv.c
index 04fb3e1..a7df810 100644
--- a/src/ap/gas_serv.c
+++ b/src/ap/gas_serv.c
@@ -181,6 +181,8 @@
 		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_osu_providers_nai_count)
+		wpabuf_put_u8(buf, HS20_STYPE_OSU_PROVIDERS_NAI_LIST);
 	if (hapd->conf->hs20_icons_count)
 		wpabuf_put_u8(buf, HS20_STYPE_ICON_REQUEST);
 	if (hapd->conf->hs20_operator_icon_count)
@@ -817,6 +819,40 @@
 }
 
 
+static void anqp_add_osu_provider_nai(struct wpabuf *buf,
+				      struct hs20_osu_provider *p)
+{
+	/* OSU_NAI for shared BSS (Single SSID) */
+	if (p->osu_nai2) {
+		wpabuf_put_u8(buf, os_strlen(p->osu_nai2));
+		wpabuf_put_str(buf, p->osu_nai2);
+	} else {
+		wpabuf_put_u8(buf, 0);
+	}
+}
+
+
+static void anqp_add_osu_providers_nai_list(struct hostapd_data *hapd,
+					    struct wpabuf *buf)
+{
+	if (hapd->conf->hs20_osu_providers_nai_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_NAI_LIST);
+		wpabuf_put_u8(buf, 0); /* Reserved */
+
+		for (i = 0; i < hapd->conf->hs20_osu_providers_count; i++) {
+			anqp_add_osu_provider_nai(
+				buf, &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)
@@ -1024,6 +1060,8 @@
 		anqp_add_icon_binary_file(hapd, buf, icon_name, icon_name_len);
 	if (request & ANQP_REQ_OPERATOR_ICON_METADATA)
 		anqp_add_operator_icon_metadata(hapd, buf);
+	if (request & ANQP_REQ_OSU_PROVIDERS_NAI_LIST)
+		anqp_add_osu_providers_nai_list(hapd, buf);
 #endif /* CONFIG_HS20 */
 
 #ifdef CONFIG_MBO
@@ -1216,6 +1254,11 @@
 			     "Operator Icon Metadata",
 			     hapd->conf->hs20_operator_icon_count, qi);
 		break;
+	case HS20_STYPE_OSU_PROVIDERS_NAI_LIST:
+		set_anqp_req(ANQP_REQ_OSU_PROVIDERS_NAI_LIST,
+			     "OSU Providers NAI List",
+			     hapd->conf->hs20_osu_providers_nai_count, qi);
+		break;
 	default:
 		wpa_printf(MSG_DEBUG, "ANQP: Unsupported HS 2.0 subtype %u",
 			   subtype);
diff --git a/src/ap/gas_serv.h b/src/ap/gas_serv.h
index 0afdcb1..2cf1817 100644
--- a/src/ap/gas_serv.h
+++ b/src/ap/gas_serv.h
@@ -62,6 +62,8 @@
 	(0x10000 << HS20_STYPE_ICON_REQUEST)
 #define ANQP_REQ_OPERATOR_ICON_METADATA \
 	(0x10000 << HS20_STYPE_OPERATOR_ICON_METADATA)
+#define ANQP_REQ_OSU_PROVIDERS_NAI_LIST \
+	(0x10000 << HS20_STYPE_OSU_PROVIDERS_NAI_LIST)
 /* The first MBO ANQP-element can be included in the optimized bitmap. */
 #define ANQP_REQ_MBO_CELL_DATA_CONN_PREF \
 	(BIT(29) << MBO_ANQP_SUBTYPE_CELL_CONN_PREF)
diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c
index 23d2720..117ee08 100644
--- a/src/ap/hostapd.c
+++ b/src/ap/hostapd.c
@@ -2620,6 +2620,11 @@
 		!!(hapd_iface->drv_flags &
 		   WPA_DRIVER_FLAGS_AP_TEARDOWN_SUPPORT);
 
+#ifdef NEED_AP_MLME
+	for (j = 0; j < hapd_iface->num_bss; j++)
+		hostapd_cleanup_cs_params(hapd_iface->bss[j]);
+#endif /* NEED_AP_MLME */
+
 	/* same as hostapd_interface_deinit without deinitializing ctrl-iface */
 	for (j = 0; j < hapd_iface->num_bss; j++) {
 		struct hostapd_data *hapd = hapd_iface->bss[j];
@@ -3428,7 +3433,6 @@
 				const struct hostapd_freq_params *freq_params)
 {
 	int vht_seg0_idx = 0, vht_seg1_idx = 0, vht_bw = VHT_CHANWIDTH_USE_HT;
-	unsigned int i;
 
 	wpa_printf(MSG_DEBUG, "Restarting all CSA-related BSSes");
 
@@ -3470,10 +3474,8 @@
 	/*
 	 * cs_params must not be cleared earlier because the freq_params
 	 * argument may actually point to one of these.
+	 * These params will be cleared during interface disable below.
 	 */
-	for (i = 0; i < iface->num_bss; i++)
-		hostapd_cleanup_cs_params(iface->bss[i]);
-
 	hostapd_disable_iface(iface);
 	hostapd_enable_iface(iface);
 }
diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c
index 84e74ee..5279abc 100644
--- a/src/ap/hw_features.c
+++ b/src/ap/hw_features.c
@@ -679,7 +679,8 @@
 	if (!ieee80211n_supported_ht_capab(iface))
 		return -1;
 #ifdef CONFIG_IEEE80211AC
-	if (!ieee80211ac_supported_vht_capab(iface))
+	if (iface->conf->ieee80211ac &&
+	    !ieee80211ac_supported_vht_capab(iface))
 		return -1;
 #endif /* CONFIG_IEEE80211AC */
 	ret = ieee80211n_check_40mhz(iface);
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index d8b34fa..d2d6b17 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -1665,9 +1665,11 @@
 				      is_probe_req);
 
 	if (res == HOSTAPD_ACL_REJECT) {
-		wpa_printf(MSG_INFO,
-			   "Station " MACSTR " not allowed to authenticate",
-			   MAC2STR(addr));
+		if (!is_probe_req)
+			wpa_printf(MSG_DEBUG,
+				   "Station " MACSTR
+				   " not allowed to authenticate",
+				   MAC2STR(addr));
 		return HOSTAPD_ACL_REJECT;
 	}
 
@@ -2583,10 +2585,14 @@
 		if (resp != WLAN_STATUS_SUCCESS)
 			return resp;
 #ifdef CONFIG_IEEE80211W
-		if ((sta->flags & WLAN_STA_MFP) && !sta->sa_query_timed_out &&
+		if ((sta->flags & (WLAN_STA_ASSOC | WLAN_STA_MFP)) ==
+		    (WLAN_STA_ASSOC | WLAN_STA_MFP) &&
+		    !sta->sa_query_timed_out &&
 		    sta->sa_query_count > 0)
 			ap_check_sa_query_timeout(hapd, sta);
-		if ((sta->flags & WLAN_STA_MFP) && !sta->sa_query_timed_out &&
+		if ((sta->flags & (WLAN_STA_ASSOC | WLAN_STA_MFP)) ==
+		    (WLAN_STA_ASSOC | WLAN_STA_MFP) &&
+		    !sta->sa_query_timed_out &&
 		    (!reassoc || sta->auth_alg != WLAN_AUTH_FT)) {
 			/*
 			 * STA has already been associated with MFP and SA
@@ -4052,7 +4058,8 @@
 
 	sta = ap_get_sta(hapd, mgmt->da);
 	if (!sta) {
-		wpa_printf(MSG_INFO, "handle_auth_cb: STA " MACSTR " not found",
+		wpa_printf(MSG_DEBUG, "handle_auth_cb: STA " MACSTR
+			   " not found",
 			   MAC2STR(mgmt->da));
 		return;
 	}
diff --git a/src/ap/ieee802_11_shared.c b/src/ap/ieee802_11_shared.c
index a3f8609..c481399 100644
--- a/src/ap/ieee802_11_shared.c
+++ b/src/ap/ieee802_11_shared.c
@@ -178,6 +178,10 @@
 	case 1: /* Bits 8-15 */
 		if (hapd->conf->proxy_arp)
 			*pos |= 0x10; /* Bit 12 - Proxy ARP */
+		if (hapd->conf->coloc_intf_reporting) {
+			/* Bit 13 - Collocated Interference Reporting */
+			*pos |= 0x20;
+		}
 		break;
 	case 2: /* Bits 16-23 */
 		if (hapd->conf->wnm_sleep_mode)
diff --git a/src/ap/vlan_init.c b/src/ap/vlan_init.c
index 31e4fc6..01fecee 100644
--- a/src/ap/vlan_init.c
+++ b/src/ap/vlan_init.c
@@ -138,6 +138,8 @@
 	    !hapd->conf->vlan) {
 		/* dynamic vlans enabled but no (or empty) vlan_file given */
 		struct hostapd_vlan *vlan;
+		int ret;
+
 		vlan = os_zalloc(sizeof(*vlan));
 		if (vlan == NULL) {
 			wpa_printf(MSG_ERROR, "Out of memory while assigning "
@@ -146,8 +148,16 @@
 		}
 
 		vlan->vlan_id = VLAN_ID_WILDCARD;
-		os_snprintf(vlan->ifname, sizeof(vlan->ifname), "%s.#",
-			    hapd->conf->iface);
+		ret = os_snprintf(vlan->ifname, sizeof(vlan->ifname), "%s.#",
+				  hapd->conf->iface);
+		if (ret >= (int) sizeof(vlan->ifname)) {
+			wpa_printf(MSG_WARNING,
+				   "VLAN: Interface name was truncated to %s",
+				   vlan->ifname);
+		} else if (ret < 0) {
+			os_free(vlan);
+			return ret;
+		}
 		vlan->next = hapd->conf->vlan;
 		hapd->conf->vlan = vlan;
 	}
diff --git a/src/ap/wnm_ap.c b/src/ap/wnm_ap.c
index 7b43c7d..61d2f65 100644
--- a/src/ap/wnm_ap.c
+++ b/src/ap/wnm_ap.c
@@ -460,6 +460,48 @@
 }
 
 
+static void ieee802_11_rx_wnm_coloc_intf_report(struct hostapd_data *hapd,
+						const u8 *addr, const u8 *buf,
+						size_t len)
+{
+	u8 dialog_token;
+	char *hex;
+	size_t hex_len;
+
+	if (!hapd->conf->coloc_intf_reporting) {
+		wpa_printf(MSG_DEBUG,
+			   "WNM: Ignore unexpected Collocated Interference Report from "
+			   MACSTR, MAC2STR(addr));
+		return;
+	}
+
+	if (len < 1) {
+		wpa_printf(MSG_DEBUG,
+			   "WNM: Ignore too short Collocated Interference Report from "
+			   MACSTR, MAC2STR(addr));
+		return;
+	}
+	dialog_token = *buf++;
+	len--;
+
+	wpa_printf(MSG_DEBUG,
+		   "WNM: Received Collocated Interference Report frame from "
+		   MACSTR " (dialog_token=%u)",
+		   MAC2STR(addr), dialog_token);
+	wpa_hexdump(MSG_MSGDUMP, "WNM: Collocated Interference Report Elements",
+		    buf, len);
+
+	hex_len = 2 * len + 1;
+	hex = os_malloc(hex_len);
+	if (!hex)
+		return;
+	wpa_snprintf_hex(hex, hex_len, buf, len);
+	wpa_msg_ctrl(hapd->msg_ctx, MSG_INFO, COLOC_INTF_REPORT MACSTR " %d %s",
+		     MAC2STR(addr), dialog_token, hex);
+	os_free(hex);
+}
+
+
 int ieee802_11_rx_wnm_action_ap(struct hostapd_data *hapd,
 				const struct ieee80211_mgmt *mgmt, size_t len)
 {
@@ -490,6 +532,10 @@
 		ieee802_11_rx_wnm_notification_req(hapd, mgmt->sa, payload,
 						   plen);
 		return 0;
+	case WNM_COLLOCATED_INTERFERENCE_REPORT:
+		ieee802_11_rx_wnm_coloc_intf_report(hapd, mgmt->sa, payload,
+						    plen);
+		return 0;
 	}
 
 	wpa_printf(MSG_DEBUG, "WNM: Unsupported WNM Action %u from " MACSTR,
@@ -688,3 +734,40 @@
 
 	return 0;
 }
+
+
+int wnm_send_coloc_intf_req(struct hostapd_data *hapd, struct sta_info *sta,
+			    unsigned int auto_report, unsigned int timeout)
+{
+	u8 buf[100], *pos;
+	struct ieee80211_mgmt *mgmt;
+	u8 dialog_token = 1;
+
+	if (auto_report > 3 || timeout > 63)
+		return -1;
+	os_memset(buf, 0, sizeof(buf));
+	mgmt = (struct ieee80211_mgmt *) buf;
+	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
+					   WLAN_FC_STYPE_ACTION);
+	os_memcpy(mgmt->da, sta->addr, ETH_ALEN);
+	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
+	os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN);
+	mgmt->u.action.category = WLAN_ACTION_WNM;
+	mgmt->u.action.u.coloc_intf_req.action =
+		WNM_COLLOCATED_INTERFERENCE_REQ;
+	mgmt->u.action.u.coloc_intf_req.dialog_token = dialog_token;
+	mgmt->u.action.u.coloc_intf_req.req_info = auto_report | (timeout << 2);
+	pos = &mgmt->u.action.u.coloc_intf_req.req_info;
+	pos++;
+
+	wpa_printf(MSG_DEBUG, "WNM: Sending Collocated Interference Request to "
+		   MACSTR " (dialog_token=%u auto_report=%u timeout=%u)",
+		   MAC2STR(sta->addr), dialog_token, auto_report, timeout);
+	if (hostapd_drv_send_mlme(hapd, buf, pos - buf, 0) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "WNM: Failed to send Collocated Interference Request frame");
+		return -1;
+	}
+
+	return 0;
+}
diff --git a/src/ap/wnm_ap.h b/src/ap/wnm_ap.h
index 56d0f88..1806ba0 100644
--- a/src/ap/wnm_ap.h
+++ b/src/ap/wnm_ap.h
@@ -24,5 +24,7 @@
 			const u8 *nei_rep, size_t nei_rep_len,
 			const u8 *mbo_attrs, size_t mbo_len);
 void ap_sta_reset_steer_flag_timer(void *eloop_ctx, void *timeout_ctx);
+int wnm_send_coloc_intf_req(struct hostapd_data *hapd, struct sta_info *sta,
+			    unsigned int auto_report, unsigned int timeout);
 
 #endif /* WNM_AP_H */
diff --git a/src/ap/wpa_auth_ie.c b/src/ap/wpa_auth_ie.c
index 421dd5a..253fe6e 100644
--- a/src/ap/wpa_auth_ie.c
+++ b/src/ap/wpa_auth_ie.c
@@ -751,6 +751,7 @@
 
 #ifdef CONFIG_SAE
 	if (wpa_auth->conf.ieee80211w == MGMT_FRAME_PROTECTION_OPTIONAL &&
+	    wpa_auth->conf.sae_require_mfp &&
 	    wpa_key_mgmt_sae(sm->wpa_key_mgmt) &&
 	    !(data.capabilities & WPA_CAPABILITY_MFPC)) {
 		wpa_printf(MSG_DEBUG,
diff --git a/src/common/dpp.c b/src/common/dpp.c
index 74a524d..677f586 100644
--- a/src/common/dpp.c
+++ b/src/common/dpp.c
@@ -840,6 +840,7 @@
 	if (sha256_vector(1, (const u8 **) &data, &data_len,
 			  bi->pubkey_hash) < 0) {
 		wpa_printf(MSG_DEBUG, "DPP: Failed to hash public key");
+		os_free(data);
 		return -1;
 	}
 	wpa_hexdump(MSG_DEBUG, "DPP: Public key hash",
@@ -2601,6 +2602,7 @@
 	ret = 0;
 fail:
 	EC_POINT_clear_free(l);
+	EC_POINT_clear_free(sum);
 	EC_KEY_free(bI);
 	EC_KEY_free(BR);
 	EC_KEY_free(PR);
@@ -5562,6 +5564,7 @@
 		if (!conf->curve) {
 			wpa_printf(MSG_INFO, "DPP: Unsupported curve: %s",
 				   curve);
+			os_free(conf);
 			return NULL;
 		}
 	}
@@ -6219,14 +6222,14 @@
 
 		if (EC_POINT_set_affine_coordinates_GFp(group, point, x, y,
 							ctx) != 1) {
-#if OPENSSL_VERSION_NUMBER >= 0x10101000L || defined(OPENSSL_IS_BORINGSSL)
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L || defined(OPENSSL_IS_BORINGSSL)
 		/* Unlike older OpenSSL versions, OpenSSL 1.1.1 and BoringSSL
 		 * return an error from EC_POINT_set_affine_coordinates_GFp()
 		 * when the point is not on the curve. */
 			break;
-#else /* >=1.1.1 or OPENSSL_IS_BORINGSSL */
+#else /* >=1.1.0 or OPENSSL_IS_BORINGSSL */
 			goto fail;
-#endif /* >= 1.1.1 or OPENSSL_IS_BORINGSSL */
+#endif /* >= 1.1.0 or OPENSSL_IS_BORINGSSL */
 		}
 
 		if (!EC_POINT_is_on_curve(group, point, ctx))
diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h
index e03a095..762e731 100644
--- a/src/common/ieee802_11_defs.h
+++ b/src/common/ieee802_11_defs.h
@@ -922,6 +922,16 @@
 					u8 variable[];
 				} STRUCT_PACKED bss_tm_query;
 				struct {
+					u8 action; /* 11 */
+					u8 dialog_token;
+					u8 req_info;
+				} STRUCT_PACKED coloc_intf_req;
+				struct {
+					u8 action; /* 12 */
+					u8 dialog_token;
+					u8 variable[];
+				} STRUCT_PACKED coloc_intf_report;
+				struct {
 					u8 action; /* 15 */
 					u8 variable[];
 				} STRUCT_PACKED slf_prot_action;
@@ -1331,6 +1341,7 @@
 #define HS20_STYPE_ICON_REQUEST 10
 #define HS20_STYPE_ICON_BINARY_FILE 11
 #define HS20_STYPE_OPERATOR_ICON_METADATA 12
+#define HS20_STYPE_OSU_PROVIDERS_NAI_LIST 13
 
 #define HS20_DGAF_DISABLED 0x01
 #define HS20_PPS_MO_ID_PRESENT 0x02
diff --git a/src/common/qca-vendor.h b/src/common/qca-vendor.h
index 75a7cc0..7a4da46 100644
--- a/src/common/qca-vendor.h
+++ b/src/common/qca-vendor.h
@@ -463,6 +463,17 @@
  *	extendable to send more information. The newer version carries the
  *	legacy blob encapsulated within an attribute and can be extended with
  *	additional vendor attributes that can enhance the NAN command interface.
+ * @QCA_NL80211_VENDOR_SUBCMD_ROAM_SCAN_EVENT: Event to indicate scan triggered
+ *	or stopped within driver/firmware in order to initiate roaming. The
+ *	attributes used with this event are defined in enum
+ *	qca_wlan_vendor_attr_roam_scan. Some drivers may not send these events
+ *	in few cases, e.g., if the host processor is sleeping when this event
+ *	is generated in firmware.
+ *
+ * @QCA_NL80211_VENDOR_SUBCMD_PEER_CFR_CAPTURE_CFG: This command is used to
+ *	configure parameters per peer to capture Channel Frequency Response
+ *	(CFR) and enable Periodic CFR capture. The attributes for this command
+ *	are defined in enum qca_wlan_vendor_peer_cfr_capture_attr.
  */
 enum qca_nl80211_vendor_subcmds {
 	QCA_NL80211_VENDOR_SUBCMD_UNSPEC = 0,
@@ -623,6 +634,8 @@
 	/* Frame filter operations for other BSSs/unassociated STAs */
 	QCA_NL80211_VENDOR_SUBCMD_BSS_FILTER = 170,
 	QCA_NL80211_VENDOR_SUBCMD_NAN_EXT = 171,
+	QCA_NL80211_VENDOR_SUBCMD_ROAM_SCAN_EVENT = 172,
+	QCA_NL80211_VENDOR_SUBCMD_PEER_CFR_CAPTURE_CFG = 173,
 };
 
 enum qca_wlan_vendor_attr {
@@ -4938,6 +4951,10 @@
 	 * and ndp confirm.
 	 */
 	QCA_WLAN_VENDOR_ATTR_NDP_TRANSPORT_PROTOCOL = 29,
+	/* Unsigned 8-bit value indicating if NDP remote peer supports NAN NDPE.
+	 * 1:support 0:not support
+	 */
+	QCA_WLAN_VENDOR_ATTR_PEER_NDPE_SUPPORT = 30,
 
 	/* keep last */
 	QCA_WLAN_VENDOR_ATTR_NDP_PARAMS_AFTER_LAST,
@@ -5553,6 +5570,40 @@
 	 */
 	QCA_WLAN_VENDOR_ATTR_WIFI_TEST_CONFIG_OVERRIDE_MU_EDCA = 21,
 
+	/* 8-bit unsigned value to configure the support for receiving
+	 * an MPDU that contains an operating mode control subfield.
+	 * This attribute is used to configure the testbed device.
+	 * 1-enable, 0-disable.
+	 */
+	QCA_WLAN_VENDOR_ATTR_WIFI_TEST_CONFIG_HE_OM_CTRL_SUPP = 22,
+
+	/* Nested attribute values required to setup the TWT session.
+	 * enum qca_wlan_vendor_attr_twt_setup provides the necessary
+	 * information to set up the session. It contains broadcast flags,
+	 * set_up flags, trigger value, flow type, flow ID, wake interval
+	 * exponent, protection, target wake time, wake duration, wake interval
+	 * mantissa. These nested attributes are used to setup a host triggered
+	 * TWT session.
+	 */
+	QCA_WLAN_VENDOR_ATTR_WIFI_TEST_CONFIG_TWT_SETUP = 23,
+
+	/* This nested attribute is used to terminate the current TWT session.
+	 * It does not currently carry any attributes.
+	 */
+	QCA_WLAN_VENDOR_ATTR_WIFI_TEST_CONFIG_TWT_TERMINATE = 24,
+
+	/* This nested attribute is used to suspend the current TWT session.
+	 * It does not currently carry any attributes.
+	 */
+	QCA_WLAN_VENDOR_ATTR_WIFI_TEST_CONFIG_TWT_SUSPEND = 25,
+
+	/* Nested attribute values to indicate the request for resume.
+	 * This attribute is used to resume the TWT session.
+	 * enum qca_wlan_vendor_attr_twt_resume provides the necessary
+	 * parameters required to resume the TWT session.
+	 */
+	QCA_WLAN_VENDOR_ATTR_WIFI_TEST_CONFIG_TWT_RESUME = 26,
+
 	/* keep last */
 	QCA_WLAN_VENDOR_ATTR_WIFI_TEST_CONFIG_AFTER_LAST,
 	QCA_WLAN_VENDOR_ATTR_WIFI_TEST_CONFIG_MAX =
@@ -5706,4 +5757,237 @@
 		QCA_WLAN_VENDOR_ATTR_NAN_PARAMS_AFTER_LAST - 1
 };
 
+/**
+ * enum qca_wlan_vendor_attr_twt_setup: Represents attributes for
+ * TWT (Target Wake Time) setup request. These attributes are sent as part of
+ * %QCA_WLAN_VENDOR_ATTR_WIFI_TEST_CONFIG_TWT_SETUP and
+ * %QCA_NL80211_VENDOR_SUBCMD_WIFI_TEST_CONFIGURATION.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TWT_SETUP_BCAST: Flag attribute.
+ * Disable (flag attribute not present) - Individual TWT
+ * Enable (flag attribute present) - Broadcast TWT.
+ * Individual means the session is between the STA and the AP.
+ * This session is established using a separate negotiation between
+ * STA and AP.
+ * Broadcast means the session is across multiple STAs and an AP. The
+ * configuration parameters are announced in Beacon frames by the AP.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TWT_SETUP_REQ_TYPE: Required (u8).
+ * Unsigned 8-bit qca_wlan_vendor_twt_setup_req_type to
+ * specify the TWT request type
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TWT_SETUP_TRIGGER: Flag attribute
+ * Enable (flag attribute present) - TWT with trigger support.
+ * Disable (flag attribute not present) - TWT without trigger support.
+ * Trigger means the AP will send the trigger frame to allow STA to send data.
+ * Without trigger, the STA will wait for the MU EDCA timer before
+ * transmitting the data.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TWT_SETUP_FLOW_TYPE: Required (u8)
+ * 0 - Announced TWT - In this mode, STA may skip few service periods to
+ * save more power. If STA wants to wake up, it will send a PS-POLL/QoS
+ * NULL frame to AP.
+ * 1 - Unannounced TWT - The STA will wakeup during every SP.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TWT_SETUP_FLOW_ID: Optional (u8)
+ * Flow ID is the unique identifier for each TWT session.
+ * Currently this is not required and dialog ID will be set to zero.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_INTVL_EXP: Required (u8)
+ * This attribute (exp) is used along with the mantissa to derive the
+ * wake interval using the following formula:
+ * pow(2,exp) = wake_intvl_us/wake_intvl_mantis
+ * Wake interval is the interval between 2 successive SP.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TWT_SETUP_PROTECTION: Flag attribute
+ * Enable (flag attribute present) - Protection required.
+ * Disable (flag attribute not present) - Protection not required.
+ * If protection is enabled, then the AP will use protection
+ * mechanism using RTS/CTS to self to reserve the airtime.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_TIME: Optional (u32)
+ * This attribute is used as the SP offset which is the offset from
+ * TSF after which the wake happens. The units are in microseconds. If
+ * this attribute is not provided, then the value will be set to zero.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_DURATION: Required (u32)
+ * This is the duration of the service period. The units are in TU.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_INTVL_MANTISSA: Required (u32)
+ * This attribute is used to configure wake interval mantissa.
+ * The units are in TU.
+ */
+enum qca_wlan_vendor_attr_twt_setup {
+	QCA_WLAN_VENDOR_ATTR_TWT_SETUP_INVALID = 0,
+	QCA_WLAN_VENDOR_ATTR_TWT_SETUP_BCAST = 1,
+	QCA_WLAN_VENDOR_ATTR_TWT_SETUP_REQ_TYPE = 2,
+	QCA_WLAN_VENDOR_ATTR_TWT_SETUP_TRIGGER = 3,
+	QCA_WLAN_VENDOR_ATTR_TWT_SETUP_FLOW_TYPE = 4,
+	QCA_WLAN_VENDOR_ATTR_TWT_SETUP_FLOW_ID = 5,
+	QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_INTVL_EXP = 6,
+	QCA_WLAN_VENDOR_ATTR_TWT_SETUP_PROTECTION = 7,
+	QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_TIME = 8,
+	QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_DURATION = 9,
+	QCA_WLAN_VENDOR_ATTR_TWT_SETUP_WAKE_INTVL_MANTISSA = 10,
+
+	/* keep last */
+	QCA_WLAN_VENDOR_ATTR_TWT_SETUP_AFTER_LAST,
+	QCA_WLAN_VENDOR_ATTR_TWT_SETUP_MAX =
+	QCA_WLAN_VENDOR_ATTR_TWT_SETUP_AFTER_LAST - 1,
+};
+
+/**
+ * enum qca_wlan_vendor_attr_twt_resume: Represents attributes for
+ * TWT (Target Wake Time) resume request. These attributes are sent as part of
+ * %QCA_WLAN_VENDOR_ATTR_WIFI_TEST_CONFIG_TWT_RESUME and
+ * %QCA_NL80211_VENDOR_SUBCMD_WIFI_TEST_CONFIGURATION.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TWT_RESUME_NEXT_TWT: Optional (u8)
+ * This attribute is used as the SP offset which is the offset from
+ * TSF after which the wake happens. The units are in microseconds.
+ * If this attribute is not provided, then the value will be set to
+ * zero.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TWT_RESUME_NEXT_TWT_SIZE: Required (u32)
+ * This attribute represents the next TWT subfield size.
+ */
+enum qca_wlan_vendor_attr_twt_resume {
+	QCA_WLAN_VENDOR_ATTR_TWT_RESUME_INVALID = 0,
+	QCA_WLAN_VENDOR_ATTR_TWT_RESUME_NEXT_TWT = 1,
+	QCA_WLAN_VENDOR_ATTR_TWT_RESUME_NEXT_TWT_SIZE = 2,
+
+	/* keep last */
+	QCA_WLAN_VENDOR_ATTR_TWT_RESUME_AFTER_LAST,
+	QCA_WLAN_VENDOR_ATTR_TWT_RESUME_MAX =
+	QCA_WLAN_VENDOR_ATTR_TWT_RESUME_AFTER_LAST - 1,
+};
+
+/**
+ * enum qca_wlan_vendor_twt_setup_req_type - Required (u8)
+ * Represents the setup type being requested for TWT.
+ * @QCA_WLAN_VENDOR_TWT_SETUP_REQUEST: STA is not specifying all the TWT
+ * parameters but relying on AP to fill the parameters during the negotiation.
+ * @QCA_WLAN_VENDOR_TWT_SETUP_SUGGEST: STA will provide all the suggested
+ * values which the AP may accept or AP may provide alternative parameters
+ * which the STA may accept.
+ * @QCA_WLAN_VENDOR_TWT_SETUP_DEMAND: STA is not willing to accept any
+ * alternate parameters than the requested ones.
+ */
+enum qca_wlan_vendor_twt_setup_req_type {
+	QCA_WLAN_VENDOR_TWT_SETUP_REQUEST = 1,
+	QCA_WLAN_VENDOR_TWT_SETUP_SUGGEST = 2,
+	QCA_WLAN_VENDOR_TWT_SETUP_DEMAND = 3,
+};
+
+/**
+ * enum qca_wlan_roam_scan_event_type - Type of roam scan event
+ *
+ * Indicates the type of roam scan event sent by firmware/driver.
+ *
+ * @QCA_WLAN_ROAM_SCAN_TRIGGER_EVENT: Roam scan trigger event type.
+ * @QCA_WLAN_ROAM_SCAN_STOP_EVENT: Roam scan stopped event type.
+ */
+enum qca_wlan_roam_scan_event_type {
+	QCA_WLAN_ROAM_SCAN_TRIGGER_EVENT = 0,
+	QCA_WLAN_ROAM_SCAN_STOP_EVENT = 1,
+};
+
+/**
+ * enum qca_wlan_roam_scan_trigger_reason - Roam scan trigger reason
+ *
+ * Indicates the reason for triggering roam scan by firmware/driver.
+ *
+ * @QCA_WLAN_ROAM_SCAN_TRIGGER_REASON_LOW_RSSI: Due to low RSSI of current AP.
+ * @QCA_WLAN_ROAM_SCAN_TRIGGER_REASON_HIGH_PER: Due to high packet error rate.
+ */
+enum qca_wlan_roam_scan_trigger_reason {
+	QCA_WLAN_ROAM_SCAN_TRIGGER_REASON_LOW_RSSI = 0,
+	QCA_WLAN_ROAM_SCAN_TRIGGER_REASON_HIGH_PER = 1,
+};
+
+/**
+ * enum qca_wlan_vendor_attr_roam_scan - Vendor subcmd attributes to report
+ * roam scan related details from driver/firmware to user space. enum values
+ * are used for NL attributes sent with
+ * %QCA_NL80211_VENDOR_SUBCMD_ROAM_SCAN_EVENT sub command.
+ */
+enum qca_wlan_vendor_attr_roam_scan {
+	QCA_WLAN_VENDOR_ATTR_ROAM_SCAN_INVALID = 0,
+	/* Encapsulates type of roam scan event being reported. enum
+	 * qca_wlan_roam_scan_event_type describes the possible range of
+	 * values. u32 attribute.
+	 */
+	QCA_WLAN_VENDOR_ATTR_ROAM_SCAN_EVENT_TYPE = 1,
+	/* Encapsulates reason for triggering roam scan. enum
+	 * qca_wlan_roam_scan_trigger_reason describes the possible range of
+	 * values. u32 attribute.
+	 */
+	QCA_WLAN_VENDOR_ATTR_ROAM_SCAN_TRIGGER_REASON = 2,
+
+	/* keep last */
+	QCA_WLAN_VENDOR_ATTR_ROAM_SCAN_AFTER_LAST,
+	QCA_WLAN_VENDOR_ATTR_ROAM_SCAN_MAX =
+	QCA_WLAN_VENDOR_ATTR_ROAM_SCAN_AFTER_LAST - 1,
+};
+
+/**
+ * enum qca_wlan_vendor_cfr_method - QCA vendor CFR methods used by
+ * attribute QCA_WLAN_VENDOR_ATTR_PEER_CFR_METHOD as part of vendor
+ * command QCA_NL80211_VENDOR_SUBCMD_PEER_CFR_CAPTURE_CFG.
+ */
+enum qca_wlan_vendor_cfr_method {
+	/* CFR method using QOS Null frame */
+	QCA_WLAN_VENDOR_CFR_METHOD_QOS_NULL = 0,
+};
+
+/**
+ * enum qca_wlan_vendor_peer_cfr_capture_attr - Used by the vendor command
+ * QCA_NL80211_VENDOR_SUBCMD_PEER_CFR_CAPTURE_CFG to configure peer
+ * Channel Frequency Response capture parameters and enable periodic CFR
+ * capture.
+ */
+enum qca_wlan_vendor_peer_cfr_capture_attr {
+	QCA_WLAN_VENDOR_ATTR_PEER_CFR_CAPTURE_INVALID = 0,
+	/* 6-byte MAC address of the peer.
+	 * This attribute is mandatory.
+	 */
+	QCA_WLAN_VENDOR_ATTR_CFR_PEER_MAC_ADDR = 1,
+	/* Enable peer CFR Capture, flag attribute.
+	 * This attribute is mandatory to enable peer CFR capture.
+	 * If this attribute is not present, peer CFR capture is disabled.
+	 */
+	QCA_WLAN_VENDOR_ATTR_PEER_CFR_ENABLE = 2,
+	/* BW of measurement, attribute uses the values in enum nl80211_chan_width
+	 * Supported values: 20, 40, 80, 80+80, 160.
+	 * Note that all targets may not support all bandwidths.
+	 * u8 attribute. This attribute is mandatory if attribute
+	 * QCA_WLAN_VENDOR_ATTR_PEER_CFR_ENABLE is used.
+	 */
+	QCA_WLAN_VENDOR_ATTR_PEER_CFR_BANDWIDTH = 3,
+	/* Periodicity of CFR measurement in msec.
+	 * Periodicity should be a multiple of Base timer.
+	 * Current Base timer value supported is 10 msecs (default).
+	 * 0 for one shot capture. u32 attribute.
+	 * This attribute is mandatory if attribute
+	 * QCA_WLAN_VENDOR_ATTR_PEER_CFR_ENABLE is used.
+	 */
+	QCA_WLAN_VENDOR_ATTR_PEER_CFR_PERIODICITY = 4,
+	/* Method used to capture Channel Frequency Response.
+	 * Attribute uses the values defined in enum qca_wlan_vendor_cfr_method.
+	 * u8 attribute. This attribute is mandatory if attribute
+	 * QCA_WLAN_VENDOR_ATTR_PEER_CFR_ENABLE is used.
+	 */
+	QCA_WLAN_VENDOR_ATTR_PEER_CFR_METHOD = 5,
+	/* Enable periodic CFR capture, flag attribute.
+	 * This attribute is mandatory to enable Periodic CFR capture.
+	 * If this attribute is not present, periodic CFR capture is disabled.
+	 */
+	QCA_WLAN_VENDOR_ATTR_PERIODIC_CFR_CAPTURE_ENABLE = 6,
+
+	/* Keep last */
+	QCA_WLAN_VENDOR_ATTR_PEER_CFR_AFTER_LAST,
+	QCA_WLAN_VENDOR_ATTR_PEER_CFR_MAX =
+	QCA_WLAN_VENDOR_ATTR_PEER_CFR_AFTER_LAST - 1,
+};
+
 #endif /* QCA_VENDOR_H */
diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h
index 4eb7356..f65077e 100644
--- a/src/common/wpa_ctrl.h
+++ b/src/common/wpa_ctrl.h
@@ -275,6 +275,9 @@
 #define RX_HS20_ICON "RX-HS20-ICON "
 #define RX_MBO_ANQP "RX-MBO-ANQP "
 
+/* parameters: <Venue Number> <Venue URL> */
+#define RX_VENUE_URL "RX-VENUE-URL "
+
 #define HS20_SUBSCRIPTION_REMEDIATION "HS20-SUBSCRIPTION-REMEDIATION "
 #define HS20_DEAUTH_IMMINENT_NOTICE "HS20-DEAUTH-IMMINENT-NOTICE "
 #define HS20_T_C_ACCEPTANCE "HS20-T-C-ACCEPTANCE "
@@ -329,6 +332,13 @@
 /* BSS Transition Management Response frame received */
 #define BSS_TM_RESP "BSS-TM-RESP "
 
+/* Collocated Interference Request frame received;
+ * parameters: <dialog token> <automatic report enabled> <report timeout> */
+#define COLOC_INTF_REQ "COLOC-INTF-REQ "
+/* Collocated Interference Report frame received;
+ * parameters: <STA address> <dialog token> <hexdump of report elements> */
+#define COLOC_INTF_REPORT "COLOC-INTF-REPORT "
+
 /* MBO IE with cellular data connection preference received */
 #define MBO_CELL_PREFERENCE "MBO-CELL-PREFERENCE "
 
diff --git a/src/crypto/tls.h b/src/crypto/tls.h
index 585db8b..481b346 100644
--- a/src/crypto/tls.h
+++ b/src/crypto/tls.h
@@ -64,6 +64,7 @@
 		size_t hash_len;
 		const char *altsubject[TLS_MAX_ALT_SUBJECT];
 		int num_altsubject;
+		const char *serial_num;
 	} peer_cert;
 
 	struct {
@@ -253,6 +254,18 @@
 int tls_connection_established(void *tls_ctx, struct tls_connection *conn);
 
 /**
+ * tls_connection_peer_serial_num - Fetch peer certificate serial number
+ * @tls_ctx: TLS context data from tls_init()
+ * @conn: Connection context data from tls_connection_init()
+ * Returns: Allocated string buffer containing the peer certificate serial
+ * number or %NULL on error.
+ *
+ * The caller is responsible for freeing the returned buffer with os_free().
+ */
+char * tls_connection_peer_serial_num(void *tls_ctx,
+				      struct tls_connection *conn);
+
+/**
  * tls_connection_shutdown - Shutdown TLS connection
  * @tls_ctx: TLS context data from tls_init()
  * @conn: Connection context data from tls_connection_init()
diff --git a/src/crypto/tls_gnutls.c b/src/crypto/tls_gnutls.c
index 7ee3fa3..36dafd2 100644
--- a/src/crypto/tls_gnutls.c
+++ b/src/crypto/tls_gnutls.c
@@ -295,6 +295,14 @@
 }
 
 
+char * tls_connection_peer_serial_num(void *tls_ctx,
+				      struct tls_connection *conn)
+{
+	/* TODO */
+	return NULL;
+}
+
+
 int tls_connection_shutdown(void *ssl_ctx, struct tls_connection *conn)
 {
 	struct tls_global *global = ssl_ctx;
diff --git a/src/crypto/tls_internal.c b/src/crypto/tls_internal.c
index c7cb5de..d289c94 100644
--- a/src/crypto/tls_internal.c
+++ b/src/crypto/tls_internal.c
@@ -177,6 +177,14 @@
 }
 
 
+char * tls_connection_peer_serial_num(void *tls_ctx,
+				      struct tls_connection *conn)
+{
+	/* TODO */
+	return NULL;
+}
+
+
 int tls_connection_shutdown(void *tls_ctx, struct tls_connection *conn)
 {
 #ifdef CONFIG_TLS_INTERNAL_CLIENT
diff --git a/src/crypto/tls_none.c b/src/crypto/tls_none.c
index dd5681e..5d0c6bd 100644
--- a/src/crypto/tls_none.c
+++ b/src/crypto/tls_none.c
@@ -45,6 +45,13 @@
 }
 
 
+char * tls_connection_peer_serial_num(void *tls_ctx,
+				      struct tls_connection *conn)
+{
+	return NULL;
+}
+
+
 int tls_connection_shutdown(void *tls_ctx, struct tls_connection *conn)
 {
 	return -1;
diff --git a/src/crypto/tls_openssl.c b/src/crypto/tls_openssl.c
index 1aee10c..5bb14e2 100644
--- a/src/crypto/tls_openssl.c
+++ b/src/crypto/tls_openssl.c
@@ -111,6 +111,12 @@
 	return BN_num_bits(r->n);
 }
 #endif /* CONFIG_SUITEB */
+
+
+static const unsigned char * ASN1_STRING_get0_data(const ASN1_STRING *x)
+{
+	return ASN1_STRING_data((ASN1_STRING *) x);
+}
 #endif
 
 #ifdef ANDROID
@@ -1554,6 +1560,31 @@
 }
 
 
+char * tls_connection_peer_serial_num(void *tls_ctx,
+				      struct tls_connection *conn)
+{
+	ASN1_INTEGER *ser;
+	char *serial_num;
+	size_t len;
+
+	if (!conn->peer_cert)
+		return NULL;
+
+	ser = X509_get_serialNumber(conn->peer_cert);
+	if (!ser)
+		return NULL;
+
+	len = ASN1_STRING_length(ser) * 2 + 1;
+	serial_num = os_malloc(len);
+	if (!serial_num)
+		return NULL;
+	wpa_snprintf_hex_uppercase(serial_num, len,
+				   ASN1_STRING_get0_data(ser),
+				   ASN1_STRING_length(ser));
+	return serial_num;
+}
+
+
 int tls_connection_shutdown(void *ssl_ctx, struct tls_connection *conn)
 {
 	if (conn == NULL)
@@ -1842,6 +1873,8 @@
 	GENERAL_NAME *gen;
 	void *ext;
 	stack_index_t i;
+	ASN1_INTEGER *ser;
+	char serial_num[128];
 #ifdef CONFIG_SHA256
 	u8 hash[32];
 #endif /* CONFIG_SHA256 */
@@ -1870,6 +1903,14 @@
 	ev.peer_cert.depth = depth;
 	ev.peer_cert.subject = subject;
 
+	ser = X509_get_serialNumber(err_cert);
+	if (ser) {
+		wpa_snprintf_hex_uppercase(serial_num, sizeof(serial_num),
+					   ASN1_STRING_get0_data(ser),
+					   ASN1_STRING_length(ser));
+		ev.peer_cert.serial_num = serial_num;
+	}
+
 	ext = X509_get_ext_d2i(err_cert, NID_subject_alt_name, NULL, NULL);
 	for (i = 0; ext && i < sk_GENERAL_NAME_num(ext); i++) {
 		char *pos;
diff --git a/src/crypto/tls_wolfssl.c b/src/crypto/tls_wolfssl.c
index 9544e2f..cc8c704 100644
--- a/src/crypto/tls_wolfssl.c
+++ b/src/crypto/tls_wolfssl.c
@@ -347,6 +347,14 @@
 }
 
 
+char * tls_connection_peer_serial_num(void *tls_ctx,
+				      struct tls_connection *conn)
+{
+	/* TODO */
+	return NULL;
+}
+
+
 int tls_connection_shutdown(void *tls_ctx, struct tls_connection *conn)
 {
 	WOLFSSL_SESSION *session;
diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index 22a37cc..d34c679 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -1577,6 +1577,8 @@
 #define WPA_DRIVER_FLAGS_OCE_STA_CFON		0x0020000000000000ULL
 /** Driver supports MFP-optional in the connect command */
 #define WPA_DRIVER_FLAGS_MFP_OPTIONAL		0x0040000000000000ULL
+/** Driver is a self-managed regulatory device */
+#define WPA_DRIVER_FLAGS_SELF_MANAGED_REGULATORY       0x0080000000000000ULL
 	u64 flags;
 
 #define FULL_AP_CLIENT_STATE_SUPP(drv_flags) \
diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index 39a02d3..771e766 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -40,6 +40,29 @@
 #include "driver_nl80211.h"
 
 
+/* support for extack if compilation headers are too old */
+#ifndef NETLINK_EXT_ACK
+#define NETLINK_EXT_ACK 11
+enum nlmsgerr_attrs {
+	NLMSGERR_ATTR_UNUSED,
+	NLMSGERR_ATTR_MSG,
+	NLMSGERR_ATTR_OFFS,
+	NLMSGERR_ATTR_COOKIE,
+
+	__NLMSGERR_ATTR_MAX,
+	NLMSGERR_ATTR_MAX = __NLMSGERR_ATTR_MAX - 1
+};
+#endif
+#ifndef NLM_F_CAPPED
+#define NLM_F_CAPPED 0x100
+#endif
+#ifndef NLM_F_ACK_TLVS
+#define NLM_F_ACK_TLVS 0x200
+#endif
+#ifndef SOL_NETLINK
+#define SOL_NETLINK 270
+#endif
+
 #ifndef CONFIG_LIBNL20
 /*
  * libnl 1.1 has a bug, it tries to allocate socket numbers densely
@@ -302,8 +325,35 @@
 static int error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err,
 			 void *arg)
 {
+	struct nlmsghdr *nlh = (struct nlmsghdr *) err - 1;
+	int len = nlh->nlmsg_len;
+	struct nlattr *attrs;
+	struct nlattr *tb[NLMSGERR_ATTR_MAX + 1];
 	int *ret = arg;
+	int ack_len = sizeof(*nlh) + sizeof(int) + sizeof(*nlh);
+
 	*ret = err->error;
+
+	if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS))
+		return NL_SKIP;
+
+	if (!(nlh->nlmsg_flags & NLM_F_CAPPED))
+		ack_len += err->msg.nlmsg_len - sizeof(*nlh);
+
+	if (len <= ack_len)
+		return NL_STOP;
+
+	attrs = (void *) ((unsigned char *) nlh + ack_len);
+	len -= ack_len;
+
+	nla_parse(tb, NLMSGERR_ATTR_MAX, attrs, len, NULL);
+	if (tb[NLMSGERR_ATTR_MSG]) {
+		len = strnlen((char *) nla_data(tb[NLMSGERR_ATTR_MSG]),
+			      nla_len(tb[NLMSGERR_ATTR_MSG]));
+		wpa_printf(MSG_ERROR, "nl80211: kernel reports: %*s",
+			   len, (char *) nla_data(tb[NLMSGERR_ATTR_MSG]));
+	}
+
 	return NL_SKIP;
 }
 
@@ -342,7 +392,7 @@
 			 void *valid_data)
 {
 	struct nl_cb *cb;
-	int err = -ENOMEM;
+	int err = -ENOMEM, opt;
 
 	if (!msg)
 		return -ENOMEM;
@@ -351,6 +401,11 @@
 	if (!cb)
 		goto out;
 
+	/* try to set NETLINK_EXT_ACK to 1, ignoring errors */
+	opt = 1;
+	setsockopt(nl_socket_get_fd(nl_handle), SOL_NETLINK,
+		   NETLINK_EXT_ACK, &opt, sizeof(opt));
+
 	err = nl_send_auto_complete(nl_handle, msg);
 	if (err < 0)
 		goto out;
@@ -2145,6 +2200,11 @@
 	/* WNM-Sleep Mode Response */
 	if (nl80211_register_action_frame(bss, (u8 *) "\x0a\x11", 2) < 0)
 		ret = -1;
+#ifdef CONFIG_WNM
+	/* WNM - Collocated Interference Request */
+	if (nl80211_register_action_frame(bss, (u8 *) "\x0a\x0b", 2) < 0)
+		ret = -1;
+#endif /* CONFIG_WNM */
 
 #ifdef CONFIG_HS20
 	/* WNM-Notification */
@@ -6525,8 +6585,15 @@
 	struct wpa_driver_nl80211_data *drv = bss->drv;
 	char name[IFNAMSIZ + 1];
 	union wpa_event_data event;
+	int ret;
 
-	os_snprintf(name, sizeof(name), "%s.sta%d", bss->ifname, aid);
+	ret = os_snprintf(name, sizeof(name), "%s.sta%d", bss->ifname, aid);
+	if (ret >= (int) sizeof(name))
+		wpa_printf(MSG_WARNING,
+			   "nl80211: WDS interface name was truncated");
+	else if (ret < 0)
+		return ret;
+
 	if (ifname_wds)
 		os_strlcpy(ifname_wds, name, IFNAMSIZ + 1);
 
diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c
index a51b50d..7b360d2 100644
--- a/src/drivers/driver_nl80211_capa.c
+++ b/src/drivers/driver_nl80211_capa.c
@@ -818,6 +818,9 @@
 		capa->max_csa_counters =
 			nla_get_u8(tb[NL80211_ATTR_MAX_CSA_COUNTERS]);
 
+	if (tb[NL80211_ATTR_WIPHY_SELF_MANAGED_REG])
+		capa->flags |= WPA_DRIVER_FLAGS_SELF_MANAGED_REGULATORY;
+
 	return NL_SKIP;
 }
 
@@ -1918,6 +1921,13 @@
 		return -ENOMEM;
 
 	nl80211_cmd(drv, msg, 0, NL80211_CMD_GET_REG);
+	if (drv->capa.flags & WPA_DRIVER_FLAGS_SELF_MANAGED_REGULATORY) {
+		if (nla_put_u32(msg, NL80211_ATTR_WIPHY, drv->wiphy_idx)) {
+			nlmsg_free(msg);
+			return -1;
+		}
+	}
+
 	return send_and_recv_msgs(drv, msg, nl80211_get_reg, results);
 }
 
diff --git a/src/drivers/driver_nl80211_monitor.c b/src/drivers/driver_nl80211_monitor.c
index 9376d11..f25cd79 100644
--- a/src/drivers/driver_nl80211_monitor.c
+++ b/src/drivers/driver_nl80211_monitor.c
@@ -361,8 +361,17 @@
 		 */
 		snprintf(buf, IFNAMSIZ, "mon-%s", drv->first_bss->ifname + 4);
 	} else {
+		int ret;
+
 		/* Non-P2P interface with AP functionality. */
-		snprintf(buf, IFNAMSIZ, "mon.%s", drv->first_bss->ifname);
+		ret = os_snprintf(buf, IFNAMSIZ, "mon.%s",
+				  drv->first_bss->ifname);
+		if (ret >= (int) sizeof(buf))
+			wpa_printf(MSG_DEBUG,
+				   "nl80211: Monitor interface name has been truncated to %s",
+				   buf);
+		else if (ret < 0)
+			return ret;
 	}
 
 	buf[IFNAMSIZ - 1] = '\0';
diff --git a/src/eap_peer/eap.c b/src/eap_peer/eap.c
index e55e2d5..974c475 100644
--- a/src/eap_peer/eap.c
+++ b/src/eap_peer/eap.c
@@ -670,6 +670,9 @@
 }
 
 
+/* Note: If ext_session and/or ext_emsk are passed to this function, they are
+ * expected to point to allocated memory and those allocations will be freed
+ * unconditionally. */
 void eap_peer_erp_init(struct eap_sm *sm, u8 *ext_session_id,
 		       size_t ext_session_id_len, u8 *ext_emsk,
 		       size_t ext_emsk_len)
@@ -688,7 +691,7 @@
 
 	realm = eap_home_realm(sm);
 	if (!realm)
-		return;
+		goto fail;
 	realm_len = os_strlen(realm);
 	wpa_printf(MSG_DEBUG, "EAP: Realm for ERP keyName-NAI: %s", realm);
 	eap_erp_remove_keys_realm(sm, realm);
@@ -775,7 +778,10 @@
 	dl_list_add(&sm->erp_keys, &erp->list);
 	erp = NULL;
 fail:
-	bin_clear_free(emsk, emsk_len);
+	if (ext_emsk)
+		bin_clear_free(ext_emsk, ext_emsk_len);
+	else
+		bin_clear_free(emsk, emsk_len);
 	bin_clear_free(ext_session_id, ext_session_id_len);
 	bin_clear_free(erp, sizeof(*erp));
 	os_free(realm);
diff --git a/src/eap_peer/eap_pwd.c b/src/eap_peer/eap_pwd.c
index 90ac3cf..761c16a 100644
--- a/src/eap_peer/eap_pwd.c
+++ b/src/eap_peer/eap_pwd.c
@@ -696,7 +696,7 @@
 				 const struct wpabuf *reqData,
 				 const u8 *payload, size_t payload_len)
 {
-	struct crypto_hash *hash;
+	struct crypto_hash *hash = NULL;
 	u32 cs;
 	u16 grp;
 	u8 conf[SHA256_MAC_LEN], *cruft = NULL, *ptr;
@@ -783,6 +783,7 @@
 
 	/* random function fin */
 	eap_pwd_h_final(hash, conf);
+	hash = NULL;
 
 	ptr = (u8 *) payload;
 	if (os_memcmp_const(conf, ptr, SHA256_MAC_LEN)) {
@@ -836,6 +837,7 @@
 
 	/* all done */
 	eap_pwd_h_final(hash, conf);
+	hash = NULL;
 
 	if (compute_keys(data->grp, data->k,
 			 data->my_scalar, data->server_scalar, conf, ptr,
@@ -860,6 +862,10 @@
 	} else {
 		eap_pwd_state(data, SUCCESS_ON_FRAG_COMPLETION);
 	}
+
+	/* clean allocated memory */
+	if (hash)
+		eap_pwd_h_final(hash, conf);
 }
 
 
diff --git a/src/eap_server/eap.h b/src/eap_server/eap.h
index bb3641f..4fbc661 100644
--- a/src/eap_server/eap.h
+++ b/src/eap_server/eap.h
@@ -152,6 +152,7 @@
 void eap_sm_pending_cb(struct eap_sm *sm);
 int eap_sm_method_pending(struct eap_sm *sm);
 const u8 * eap_get_identity(struct eap_sm *sm, size_t *len);
+const char * eap_get_serial_num(struct eap_sm *sm);
 struct eap_eapol_interface * eap_get_interface(struct eap_sm *sm);
 void eap_server_clear_identity(struct eap_sm *sm);
 void eap_server_mschap_rx_callback(struct eap_sm *sm, const char *source,
diff --git a/src/eap_server/eap_i.h b/src/eap_server/eap_i.h
index 3d6f8d5..cf8a9f0 100644
--- a/src/eap_server/eap_i.h
+++ b/src/eap_server/eap_i.h
@@ -159,6 +159,7 @@
 	void *eap_method_priv;
 	u8 *identity;
 	size_t identity_len;
+	char *serial_num;
 	/* Whether Phase 2 method should validate identity match */
 	int require_identity_match;
 	int lastId; /* Identifier used in the last EAP-Packet */
diff --git a/src/eap_server/eap_server.c b/src/eap_server/eap_server.c
index c9da72e..38a1b5c 100644
--- a/src/eap_server/eap_server.c
+++ b/src/eap_server/eap_server.c
@@ -1920,6 +1920,7 @@
 	wpabuf_free(sm->lastReqData);
 	wpabuf_free(sm->eap_if.eapRespData);
 	os_free(sm->identity);
+	os_free(sm->serial_num);
 	os_free(sm->pac_opaque_encr_key);
 	os_free(sm->eap_fast_a_id);
 	os_free(sm->eap_fast_a_id_info);
@@ -1991,6 +1992,17 @@
 }
 
 
+/**
+ * eap_get_serial_num - Get the serial number of user certificate
+ * @sm: Pointer to EAP state machine allocated with eap_server_sm_init()
+ * Returns: Pointer to the serial number or %NULL if not available
+ */
+const char * eap_get_serial_num(struct eap_sm *sm)
+{
+	return sm->serial_num;
+}
+
+
 void eap_erp_update_identity(struct eap_sm *sm, const u8 *eap, size_t len)
 {
 #ifdef CONFIG_ERP
diff --git a/src/eap_server/eap_server_tls_common.c b/src/eap_server/eap_server_tls_common.c
index 4f9cb08..0ae7867 100644
--- a/src/eap_server/eap_server_tls_common.c
+++ b/src/eap_server/eap_server_tls_common.c
@@ -341,6 +341,11 @@
 		data->tls_v13 = os_strcmp(buf, "TLSv1.3") == 0;
 	}
 
+	if (!sm->serial_num &&
+	    tls_connection_established(sm->ssl_ctx, data->conn))
+		sm->serial_num = tls_connection_peer_serial_num(sm->ssl_ctx,
+								data->conn);
+
 	return 0;
 }
 
diff --git a/src/eapol_supp/eapol_supp_sm.c b/src/eapol_supp/eapol_supp_sm.c
index bfbc995..9f029b0 100644
--- a/src/eapol_supp/eapol_supp_sm.c
+++ b/src/eapol_supp/eapol_supp_sm.c
@@ -503,10 +503,14 @@
 			session_id = eap_proxy_get_eap_session_id(
 				sm->eap_proxy, &session_id_len);
 			emsk = eap_proxy_get_emsk(sm->eap_proxy, &emsk_len);
-			if (sm->config->erp && session_id && emsk)
+			if (sm->config->erp && session_id && emsk) {
 				eap_peer_erp_init(sm->eap, session_id,
 						  session_id_len, emsk,
 						  emsk_len);
+			} else {
+				os_free(session_id);
+				bin_clear_free(emsk, emsk_len);
+			}
 		}
 		return;
 	}
diff --git a/src/radius/radius_server.c b/src/radius/radius_server.c
index d66f766..e3afc0d 100644
--- a/src/radius/radius_server.c
+++ b/src/radius/radius_server.c
@@ -818,6 +818,54 @@
 }
 
 
+static void db_update_last_msk(struct radius_session *sess, const char *msk)
+{
+#ifdef CONFIG_RADIUS_TEST
+#ifdef CONFIG_SQLITE
+	char *sql = NULL;
+	char *id_str = NULL;
+	const u8 *id;
+	size_t id_len;
+	const char *serial_num;
+
+	if (!sess->server->db)
+		return;
+
+	serial_num = eap_get_serial_num(sess->eap);
+	if (serial_num) {
+		id_len = 5 + os_strlen(serial_num) + 1;
+		id_str = os_malloc(id_len);
+		if (!id_str)
+			return;
+		os_snprintf(id_str, id_len, "cert-%s", serial_num);
+	} else {
+		id = eap_get_identity(sess->eap, &id_len);
+		if (!id)
+			return;
+		id_str = os_malloc(id_len + 1);
+		if (!id_str)
+			return;
+		os_memcpy(id_str, id, id_len);
+		id_str[id_len] = '\0';
+	}
+
+	sql = sqlite3_mprintf("UPDATE users SET last_msk=%Q WHERE identity=%Q",
+			      msk, id_str);
+	os_free(id_str);
+	if (!sql)
+		return;
+
+	if (sqlite3_exec(sess->server->db, sql, NULL, NULL, NULL) !=
+	    SQLITE_OK) {
+		RADIUS_DEBUG("Failed to update last_msk: %s",
+			     sqlite3_errmsg(sess->server->db));
+	}
+	sqlite3_free(sql);
+#endif /* CONFIG_SQLITE */
+#endif /* CONFIG_RADIUS_TEST */
+}
+
+
 static struct radius_msg *
 radius_server_encapsulate_eap(struct radius_server_data *data,
 			      struct radius_client *client,
@@ -863,9 +911,18 @@
 	if (code == RADIUS_CODE_ACCESS_ACCEPT && sess->eap_if->eapKeyData) {
 		int len;
 #ifdef CONFIG_RADIUS_TEST
+		char buf[2 * 64 + 1];
+
+		len = sess->eap_if->eapKeyDataLen;
+		if (len > 64)
+			len = 64;
+		len = wpa_snprintf_hex(buf, sizeof(buf),
+				       sess->eap_if->eapKeyData, len);
+		buf[len] = '\0';
+
 		if (data->dump_msk_file) {
 			FILE *f;
-			char buf[2 * 64 + 1];
+
 			f = fopen(data->dump_msk_file, "a");
 			if (f) {
 				len = sess->eap_if->eapKeyDataLen;
@@ -879,6 +936,8 @@
 				fclose(f);
 			}
 		}
+
+		db_update_last_msk(sess, buf);
 #endif /* CONFIG_RADIUS_TEST */
 		if (sess->eap_if->eapKeyDataLen > 64) {
 			len = 32;
@@ -1336,10 +1395,12 @@
 
 	if (sess->eap_if->eapSuccess || sess->eap_if->eapFail)
 		is_complete = 1;
-	if (sess->eap_if->eapFail)
+	if (sess->eap_if->eapFail) {
 		srv_log(sess, "EAP authentication failed");
-	else if (sess->eap_if->eapSuccess)
+		db_update_last_msk(sess, "FAIL");
+	} else if (sess->eap_if->eapSuccess) {
 		srv_log(sess, "EAP authentication succeeded");
+	}
 
 	if (sess->eap_if->eapSuccess)
 		radius_server_hs20_t_c_check(sess, msg);
diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c
index 2b4d292..72a2d66 100644
--- a/src/rsn_supp/wpa.c
+++ b/src/rsn_supp/wpa.c
@@ -1007,7 +1007,7 @@
 	}
 
 	wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
-		"WPA: IGTK keyid %d pn %02x%02x%02x%02x%02x%02x",
+		"WPA: IGTK keyid %d pn " COMPACT_MACSTR,
 		keyidx, MAC2STR(igtk->pn));
 	wpa_hexdump_key(MSG_DEBUG, "WPA: IGTK", igtk->igtk, len);
 	if (keyidx > 4095) {
@@ -1461,7 +1461,13 @@
 	if (ie.gtk)
 		wpa_sm_set_rekey_offload(sm);
 
-	if (sm->proto == WPA_PROTO_RSN && wpa_key_mgmt_suite_b(sm->key_mgmt)) {
+	/* Add PMKSA cache entry for Suite B AKMs here since PMKID can be
+	 * calculated only after KCK has been derived. Though, do not replace an
+	 * existing PMKSA entry after each 4-way handshake (i.e., new KCK/PMKID)
+	 * to avoid unnecessary changes of PMKID while continuing to use the
+	 * same PMK. */
+	if (sm->proto == WPA_PROTO_RSN && wpa_key_mgmt_suite_b(sm->key_mgmt) &&
+	    !sm->cur_pmksa) {
 		struct rsn_pmksa_cache_entry *sa;
 
 		sa = pmksa_cache_add(sm->pmksa, sm->pmk, sm->pmk_len, NULL,
diff --git a/src/utils/browser-wpadebug.c b/src/utils/browser-wpadebug.c
index 062e6fe..dfb4b67 100644
--- a/src/utils/browser-wpadebug.c
+++ b/src/utils/browser-wpadebug.c
@@ -97,6 +97,7 @@
 	if (pid == 0) {
 		/* run the external command in the child process */
 		char *argv[14];
+		char *envp[] = { "PATH=/system/bin:/vendor/bin", NULL };
 
 		argv[0] = "browser-wpadebug";
 		argv[1] = "start";
@@ -113,8 +114,8 @@
 		argv[12] = "-3"; /* USER_CURRENT_OR_SELF */
 		argv[13] = NULL;
 
-		execv("/system/bin/am", argv);
-		wpa_printf(MSG_ERROR, "execv: %s", strerror(errno));
+		execve("/system/bin/am", argv, envp);
+		wpa_printf(MSG_ERROR, "execve: %s", strerror(errno));
 		exit(0);
 		return -1;
 	}
diff --git a/src/utils/wpa_debug.c b/src/utils/wpa_debug.c
index 62758d8..a56462b 100644
--- a/src/utils/wpa_debug.c
+++ b/src/utils/wpa_debug.c
@@ -58,6 +58,10 @@
 #ifndef CONFIG_NO_STDOUT_DEBUG
 
 #ifdef CONFIG_DEBUG_FILE
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
 static FILE *out_file = NULL;
 #endif /* CONFIG_DEBUG_FILE */
 
@@ -539,6 +543,8 @@
 int wpa_debug_open_file(const char *path)
 {
 #ifdef CONFIG_DEBUG_FILE
+	int out_fd;
+
 	if (!path)
 		return 0;
 
@@ -548,10 +554,28 @@
 		last_path = os_strdup(path);
 	}
 
-	out_file = fopen(path, "a");
+	out_fd = open(path, O_CREAT | O_APPEND | O_WRONLY,
+		      S_IRUSR | S_IWUSR | S_IRGRP);
+	if (out_fd < 0) {
+		wpa_printf(MSG_ERROR,
+			   "%s: Failed to open output file descriptor, using standard output",
+			   __func__);
+		return -1;
+	}
+
+#ifdef __linux__
+	if (fcntl(out_fd, F_SETFD, FD_CLOEXEC) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "%s: Failed to set FD_CLOEXEC - continue without: %s",
+			   __func__, strerror(errno));
+	}
+#endif /* __linux__ */
+
+	out_file = fdopen(out_fd, "a");
 	if (out_file == NULL) {
 		wpa_printf(MSG_ERROR, "wpa_debug_open_file: Failed to open "
 			   "output file, using standard output");
+		close(out_fd);
 		return -1;
 	}
 #ifndef _WIN32
diff --git a/src/utils/xml-utils.c b/src/utils/xml-utils.c
index a37a92d..dae91fe 100644
--- a/src/utils/xml-utils.c
+++ b/src/utils/xml-utils.c
@@ -246,7 +246,9 @@
 			xml_node_create_text(ctx, tnds, NULL, "Path", uri);
 
 		val = get_val(ctx, node);
-		xml_node_create_text(ctx, tnds, NULL, "Value", val ? val : "");
+		if (val || !xml_node_first_child(ctx, node))
+			xml_node_create_text(ctx, tnds, NULL, "Value",
+					     val ? val : "");
 		xml_node_get_text_free(ctx, val);
 
 		new_uri = add_path(uri, name);
diff --git a/src/wps/wps_common.c b/src/wps/wps_common.c
index 2e34721..bcae1ba 100644
--- a/src/wps/wps_common.c
+++ b/src/wps/wps_common.c
@@ -654,6 +654,7 @@
 	pub = wpabuf_zeropad(pub, 192);
 	if (pub == NULL) {
 		wpabuf_free(priv);
+		dh5_free(dh_ctx);
 		return -1;
 	}
 	wpa_hexdump_buf(MSG_DEBUG, "WPS: Generated new DH pubkey", pub);
diff --git a/wpa_supplicant/bss.c b/wpa_supplicant/bss.c
index 5aa07ea..3a41db9 100644
--- a/wpa_supplicant/bss.c
+++ b/wpa_supplicant/bss.c
@@ -103,6 +103,7 @@
 	ANQP_DUP(hs20_operating_class);
 	ANQP_DUP(hs20_osu_providers_list);
 	ANQP_DUP(hs20_operator_icon_metadata);
+	ANQP_DUP(hs20_osu_providers_nai_list);
 #endif /* CONFIG_HS20 */
 #undef ANQP_DUP
 
@@ -187,6 +188,7 @@
 	wpabuf_free(anqp->hs20_operating_class);
 	wpabuf_free(anqp->hs20_osu_providers_list);
 	wpabuf_free(anqp->hs20_operator_icon_metadata);
+	wpabuf_free(anqp->hs20_osu_providers_nai_list);
 #endif /* CONFIG_HS20 */
 
 	os_free(anqp);
diff --git a/wpa_supplicant/bss.h b/wpa_supplicant/bss.h
index 9179385..5251b2c 100644
--- a/wpa_supplicant/bss.h
+++ b/wpa_supplicant/bss.h
@@ -51,6 +51,7 @@
 	struct wpabuf *hs20_operating_class;
 	struct wpabuf *hs20_osu_providers_list;
 	struct wpabuf *hs20_operator_icon_metadata;
+	struct wpabuf *hs20_osu_providers_nai_list;
 #endif /* CONFIG_HS20 */
 };
 
diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c
index be6fc5d..5cdc059 100644
--- a/wpa_supplicant/config.c
+++ b/wpa_supplicant/config.c
@@ -2637,7 +2637,6 @@
 #ifdef CONFIG_MBO
 	os_free(config->non_pref_chan);
 #endif /* CONFIG_MBO */
-	os_free(config->p2p_device_persistent_mac_addr);
 
 	os_free(config);
 }
@@ -3156,14 +3155,16 @@
 		}
 		roaming_consortiums_len[num_roaming_consortiums] = len / 2;
 		num_roaming_consortiums++;
-		if (num_roaming_consortiums > MAX_ROAMING_CONS) {
+
+		if (!end)
+			break;
+
+		if (num_roaming_consortiums >= MAX_ROAMING_CONS) {
 			wpa_printf(MSG_INFO,
 				   "Too many roaming_consortiums OIs");
 			return -1;
 		}
 
-		if (!end)
-			break;
 		pos = end + 1;
 	}
 
@@ -4439,6 +4440,20 @@
 	return 0;
 }
 
+static int wpa_config_process_p2p_device_persistent_mac_addr(
+	const struct global_parse_data *data,
+	struct wpa_config *config, int line, const char *pos)
+{
+	if (hwaddr_aton2(pos, config->p2p_device_persistent_mac_addr) < 0) {
+		wpa_printf(MSG_ERROR,
+			   "Line %d: Invalid p2p_device_persistent_mac_addr '%s'",
+			   line, pos);
+		return -1;
+	}
+
+	return 0;
+}
+
 #endif /* CONFIG_P2P */
 
 
@@ -4680,6 +4695,9 @@
 	{ IPV4(ip_addr_start), 0 },
 	{ IPV4(ip_addr_end), 0 },
 	{ INT_RANGE(p2p_cli_probe, 0, 1), 0 },
+	{ INT(p2p_device_random_mac_addr), 0 },
+	{ FUNC(p2p_device_persistent_mac_addr), 0 },
+	{ INT(p2p_interface_random_mac_addr), 0 },
 #endif /* CONFIG_P2P */
 	{ FUNC(country), CFG_CHANGED_COUNTRY },
 	{ INT(bss_max_count), 0 },
@@ -4754,9 +4772,8 @@
 	{ INT(gas_rand_addr_lifetime), 0 },
 	{ INT_RANGE(gas_rand_mac_addr, 0, 2), 0 },
 	{ INT_RANGE(dpp_config_processing, 0, 2), 0 },
-	{ INT(p2p_device_random_mac_addr), 0 },
-	{ STR(p2p_device_persistent_mac_addr), 0 },
-	{ INT(p2p_interface_random_mac_addr), 0 },
+	{ INT_RANGE(bss_no_flush_when_down, 0, 1), 0 },
+	{ INT_RANGE(coloc_intf_reporting, 0, 1), 0 },
 	{ INT_RANGE(bss_no_flush_when_down, 0, 1), 0 },
 };
 
diff --git a/wpa_supplicant/config.h b/wpa_supplicant/config.h
index e431b9c..6eb0074 100644
--- a/wpa_supplicant/config.h
+++ b/wpa_supplicant/config.h
@@ -1471,6 +1471,15 @@
 	int dpp_config_processing;
 
 	/**
+	 * coloc_intf_reporting - Colocated interference reporting
+	 *
+	 * dot11CoLocIntfReportingActivated
+	 * 0 = disabled (false)
+	 * 1 = enabled (true)
+	 */
+	int coloc_intf_reporting;
+
+	/**
 	 * p2p_device_random_mac_addr - P2P Device MAC address policy default
 	 *
 	 * 0 = use permanent MAC address
@@ -1487,7 +1496,7 @@
 	 * and need to restore to last used MAC address.
 	 * format: aa:bb:cc:dd:ee:ff
 	 */
-	char *p2p_device_persistent_mac_addr;
+	u8 p2p_device_persistent_mac_addr[ETH_ALEN];
 
 	/**
 	 * p2p_interface_random_mac_addr - P2P Interface MAC address policy default
diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c
index 1cdfbc2..becc809 100644
--- a/wpa_supplicant/config_file.c
+++ b/wpa_supplicant/config_file.c
@@ -1511,19 +1511,21 @@
 	if (config->dpp_config_processing)
 		fprintf(f, "dpp_config_processing=%d\n",
 			config->dpp_config_processing);
+	if (config->coloc_intf_reporting)
+		fprintf(f, "coloc_intf_reporting=%d\n",
+			config->coloc_intf_reporting);
 	if (config->p2p_device_random_mac_addr)
 		fprintf(f, "p2p_device_random_mac_addr=%d\n",
 			config->p2p_device_random_mac_addr);
-	if (config->p2p_device_persistent_mac_addr)
-		fprintf(f, "p2p_device_persistent_mac_addr=%s\n",
-			config->p2p_device_persistent_mac_addr);
+	if (!is_zero_ether_addr(config->p2p_device_persistent_mac_addr))
+		fprintf(f, "p2p_device_persistent_mac_addr=" MACSTR "\n",
+			MAC2STR(config->p2p_device_persistent_mac_addr));
 	if (config->p2p_interface_random_mac_addr)
 		fprintf(f, "p2p_interface_random_mac_addr=%d\n",
 			config->p2p_interface_random_mac_addr);
 	if (config->bss_no_flush_when_down)
 		fprintf(f, "bss_no_flush_when_down=%d\n",
 			config->bss_no_flush_when_down);
-
 }
 
 #endif /* CONFIG_NO_CONFIG_WRITE */
diff --git a/wpa_supplicant/config_ssid.h b/wpa_supplicant/config_ssid.h
index 3000c43..d2a52d7 100644
--- a/wpa_supplicant/config_ssid.h
+++ b/wpa_supplicant/config_ssid.h
@@ -927,6 +927,16 @@
 	 * 1 = disable transition mode (allow connection only with OWE)
 	 */
 	int owe_only;
+
+	/**
+	 * owe_transition_bss_select_count - OWE transition BSS select count
+	 *
+	 * This is an internally used variable (i.e., not used in external
+	 * configuration) to track the number of selection attempts done for
+	 * OWE BSS in transition mode. This allows fallback to an open BSS if
+	 * the selection attempts for OWE BSS exceed the configured threshold.
+	 */
+	int owe_transition_bss_select_count;
 };
 
 #endif /* CONFIG_SSID_H */
diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c
index fe39c25..77a3133 100644
--- a/wpa_supplicant/ctrl_iface.c
+++ b/wpa_supplicant/ctrl_iface.c
@@ -750,6 +750,15 @@
 		ret = wpas_ctrl_iface_set_ric_ies(wpa_s, value);
 	} else if (os_strcasecmp(cmd, "roaming") == 0) {
 		ret = wpa_drv_roaming(wpa_s, atoi(value), NULL);
+#ifdef CONFIG_WNM
+	} else if (os_strcasecmp(cmd, "coloc_intf_elems") == 0) {
+		struct wpabuf *elems;
+
+		elems = wpabuf_parse_bin(value);
+		if (!elems)
+			return -1;
+		wnm_set_coloc_intf_elems(wpa_s, elems);
+#endif /* CONFIG_WNM */
 	} else {
 		value[-1] = '=';
 		ret = wpa_config_process_global(wpa_s->conf, cmd, -1);
@@ -4809,6 +4818,8 @@
 				   anqp->hs20_osu_providers_list);
 		pos = anqp_add_hex(pos, end, "hs20_operator_icon_metadata",
 				   anqp->hs20_operator_icon_metadata);
+		pos = anqp_add_hex(pos, end, "hs20_osu_providers_nai_list",
+				   anqp->hs20_osu_providers_nai_list);
 #endif /* CONFIG_HS20 */
 
 		dl_list_for_each(elem, &anqp->anqp_elems,
@@ -7435,6 +7446,22 @@
 						  list);
 }
 
+
+static int wpas_ctrl_iface_coloc_intf_report(struct wpa_supplicant *wpa_s,
+					     char *cmd)
+{
+	struct wpabuf *elems;
+	int ret;
+
+	elems = wpabuf_parse_bin(cmd);
+	if (!elems)
+		return -1;
+
+	ret = wnm_send_coloc_intf_report(wpa_s, 0, elems);
+	wpabuf_free(elems);
+	return ret;
+}
+
 #endif /* CONFIG_WNM */
 
 
@@ -10421,6 +10448,9 @@
 	} else if (os_strncmp(buf, "WNM_BSS_QUERY ", 14) == 0) {
 		if (wpas_ctrl_iface_wnm_bss_query(wpa_s, buf + 14))
 				reply_len = -1;
+	} else if (os_strncmp(buf, "COLOC_INTF_REPORT ", 18) == 0) {
+		if (wpas_ctrl_iface_coloc_intf_report(wpa_s, buf + 18))
+			reply_len = -1;
 #endif /* CONFIG_WNM */
 	} else if (os_strcmp(buf, "FLUSH") == 0) {
 		wpa_supplicant_ctrl_iface_flush(wpa_s);
diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index 7eb25ea..ef28ffe 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -50,6 +50,9 @@
 #include "dpp_supplicant.h"
 
 
+#define MAX_OWE_TRANSITION_BSS_SELECT_COUNT 5
+
+
 #ifndef CONFIG_NO_SCAN_PROCESSING
 static int wpas_select_network_from_last_scan(struct wpa_supplicant *wpa_s,
 					      int new_scan, int own_request);
@@ -314,6 +317,7 @@
 
 	wpas_rrm_reset(wpa_s);
 	wpa_s->wnmsleep_used = 0;
+	wnm_clear_coloc_intf_reporting(wpa_s);
 
 #ifdef CONFIG_TESTING_OPTIONS
 	wpa_s->last_tk_alg = WPA_ALG_NONE;
@@ -703,6 +707,19 @@
 #ifdef CONFIG_OWE
 	if ((ssid->key_mgmt & WPA_KEY_MGMT_OWE) && !ssid->owe_only &&
 	    !wpa_ie && !rsn_ie) {
+		if (wpa_s->owe_transition_select &&
+		    wpa_bss_get_vendor_ie(bss, OWE_IE_VENDOR_TYPE) &&
+		    ssid->owe_transition_bss_select_count + 1 <=
+		    MAX_OWE_TRANSITION_BSS_SELECT_COUNT) {
+			ssid->owe_transition_bss_select_count++;
+			if (debug_print)
+				wpa_dbg(wpa_s, MSG_DEBUG,
+					"   skip OWE transition BSS (selection count %d does not exceed %d)",
+					ssid->owe_transition_bss_select_count,
+					MAX_OWE_TRANSITION_BSS_SELECT_COUNT);
+			wpa_s->owe_transition_search = 1;
+			return 0;
+		}
 		if (debug_print)
 			wpa_dbg(wpa_s, MSG_DEBUG,
 				"   allow in OWE transition mode");
@@ -1387,8 +1404,11 @@
 
 	for (i = 0; i < wpa_s->last_scan_res_used; i++) {
 		struct wpa_bss *bss = wpa_s->last_scan_res[i];
+
+		wpa_s->owe_transition_select = 1;
 		*selected_ssid = wpa_scan_res_match(wpa_s, i, bss, group,
 						    only_first_ssid, 1);
+		wpa_s->owe_transition_select = 0;
 		if (!*selected_ssid)
 			continue;
 		wpa_dbg(wpa_s, MSG_DEBUG, "   selected BSS " MACSTR
@@ -1935,6 +1955,7 @@
 	if (wpa_s->p2p_mgmt)
 		return 0; /* no normal connection on p2p_mgmt interface */
 
+	wpa_s->owe_transition_search = 0;
 	selected = wpa_supplicant_pick_network(wpa_s, &ssid);
 
 #ifdef CONFIG_MESH
@@ -2036,6 +2057,17 @@
 				return 0;
 			}
 #endif /* CONFIG_WPS */
+#ifdef CONFIG_OWE
+			if (wpa_s->owe_transition_search) {
+				wpa_dbg(wpa_s, MSG_DEBUG,
+					"OWE: Use shorter wait during transition mode search");
+				timeout_sec = 0;
+				timeout_usec = 500000;
+				wpa_supplicant_req_new_scan(wpa_s, timeout_sec,
+							    timeout_usec);
+				return 0;
+			}
+#endif /* CONFIG_OWE */
 			if (wpa_supplicant_req_sched_scan(wpa_s))
 				wpa_supplicant_req_new_scan(wpa_s, timeout_sec,
 							    timeout_usec);
@@ -3970,6 +4002,16 @@
 	}
 #endif /* CONFIG_SAE */
 
+#ifdef CONFIG_DPP
+	if (wpa_s->current_ssid &&
+	    wpa_s->current_ssid->key_mgmt == WPA_KEY_MGMT_DPP &&
+	    !data->assoc_reject.timed_out) {
+		wpa_dbg(wpa_s, MSG_DEBUG, "DPP: Drop PMKSA cache entry");
+		wpa_sm_aborted_cached(wpa_s->wpa);
+		wpa_sm_pmksa_cache_flush(wpa_s->wpa, wpa_s->current_ssid);
+	}
+#endif /* CONFIG_DPP */
+
 #ifdef CONFIG_FILS
 	/* Update ERP next sequence number */
 	if (wpa_s->auth_alg == WPA_AUTH_ALG_FILS) {
@@ -4299,6 +4341,7 @@
 #endif /* CONFIG_AP */
 
 		wpas_p2p_update_channel_list(wpa_s, WPAS_P2P_CHANNEL_UPDATE_CS);
+		wnm_clear_coloc_intf_reporting(wpa_s);
 		break;
 #ifdef CONFIG_AP
 #ifdef NEED_AP_MLME
diff --git a/wpa_supplicant/hs20_supplicant.c b/wpa_supplicant/hs20_supplicant.c
index af28e88..814d18e 100644
--- a/wpa_supplicant/hs20_supplicant.c
+++ b/wpa_supplicant/hs20_supplicant.c
@@ -50,9 +50,12 @@
 	u8 bssid[ETH_ALEN];
 	u8 osu_ssid[SSID_MAX_LEN];
 	u8 osu_ssid_len;
+	u8 osu_ssid2[SSID_MAX_LEN];
+	u8 osu_ssid2_len;
 	char server_uri[256];
 	u32 osu_methods; /* bit 0 = OMA-DM, bit 1 = SOAP-XML SPP */
 	char osu_nai[256];
+	char osu_nai2[256];
 	struct osu_lang_string friendly_name[OSU_MAX_ITEMS];
 	size_t friendly_name_count;
 	struct osu_lang_string serv_desc[OSU_MAX_ITEMS];
@@ -676,6 +679,15 @@
 				wpabuf_alloc_copy(pos, slen);
 		}
 		break;
+	case HS20_STYPE_OSU_PROVIDERS_NAI_LIST:
+		wpa_msg(wpa_s, MSG_INFO, RX_HS20_ANQP MACSTR
+			" OSU Providers NAI List", MAC2STR(sa));
+		if (anqp) {
+			wpabuf_free(anqp->hs20_osu_providers_nai_list);
+			anqp->hs20_osu_providers_nai_list =
+				wpabuf_alloc_copy(pos, slen);
+		}
+		break;
 	default:
 		wpa_printf(MSG_DEBUG, "HS20: Unsupported subtype %u", subtype);
 		break;
@@ -755,8 +767,15 @@
 				wpa_ssid_txt(osu->osu_ssid,
 					     osu->osu_ssid_len));
 		}
+		if (osu->osu_ssid2_len) {
+			fprintf(f, "osu_ssid2=%s\n",
+				wpa_ssid_txt(osu->osu_ssid2,
+					     osu->osu_ssid2_len));
+		}
 		if (osu->osu_nai[0])
 			fprintf(f, "osu_nai=%s\n", osu->osu_nai);
+		if (osu->osu_nai2[0])
+			fprintf(f, "osu_nai2=%s\n", osu->osu_nai2);
 		for (j = 0; j < osu->friendly_name_count; j++) {
 			fprintf(f, "friendly_name=%s:%s\n",
 				osu->friendly_name[j].lang,
@@ -820,6 +839,7 @@
 
 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 *osu_ssid2, u8 osu_ssid2_len,
 			      const u8 *pos, size_t len)
 {
 	struct osu_provider *prov;
@@ -841,6 +861,9 @@
 	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;
+	if (osu_ssid2)
+		os_memcpy(prov->osu_ssid2, osu_ssid2, osu_ssid2_len);
+	prov->osu_ssid2_len = osu_ssid2_len;
 
 	/* OSU Friendly Name Length */
 	if (end - pos < 2) {
@@ -1022,18 +1045,30 @@
 	struct wpabuf *prov_anqp;
 	const u8 *pos, *end;
 	u16 len;
-	const u8 *osu_ssid;
-	u8 osu_ssid_len;
+	const u8 *osu_ssid, *osu_ssid2;
+	u8 osu_ssid_len, osu_ssid2_len;
 	u8 num_providers;
 
 	hs20_free_osu_prov(wpa_s);
 
 	dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) {
+		struct wpa_ie_data data;
+		const u8 *ie;
+
 		if (bss->anqp == NULL)
 			continue;
 		prov_anqp = bss->anqp->hs20_osu_providers_list;
 		if (prov_anqp == NULL)
 			continue;
+		ie = wpa_bss_get_ie(bss, WLAN_EID_RSN);
+		if (ie && wpa_parse_wpa_ie(ie, 2 + ie[1], &data) == 0 &&
+		    (data.key_mgmt & WPA_KEY_MGMT_OSEN)) {
+			osu_ssid2 = bss->ssid;
+			osu_ssid2_len = bss->ssid_len;
+		} else {
+			osu_ssid2 = NULL;
+			osu_ssid2_len = 0;
+		}
 		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",
@@ -1075,7 +1110,8 @@
 			if (len > (unsigned int) (end - pos))
 				break;
 			hs20_osu_add_prov(wpa_s, bss, osu_ssid,
-					  osu_ssid_len, pos, len);
+					  osu_ssid_len, osu_ssid2,
+					  osu_ssid2_len, pos, len);
 			pos += len;
 		}
 
@@ -1084,6 +1120,35 @@
 				   "extra data after OSU Providers",
 				   (int) (end - pos));
 		}
+
+		prov_anqp = bss->anqp->hs20_osu_providers_nai_list;
+		if (!prov_anqp)
+			continue;
+		wpa_printf(MSG_DEBUG,
+			   "HS 2.0: Parsing OSU Providers NAI List from "
+			   MACSTR, MAC2STR(bss->bssid));
+		wpa_hexdump_buf(MSG_DEBUG, "HS 2.0: OSU Providers NAI List",
+				prov_anqp);
+		pos = wpabuf_head(prov_anqp);
+		end = pos + wpabuf_len(prov_anqp);
+		num_providers = 0;
+		while (end - pos > 0) {
+			len = *pos++;
+			if (end - pos < len) {
+				wpa_printf(MSG_DEBUG,
+					   "HS 2.0: Not enough room for OSU_NAI");
+				break;
+			}
+			if (num_providers >= wpa_s->osu_prov_count) {
+				wpa_printf(MSG_DEBUG,
+					   "HS 2.0: Ignore unexpected OSU Provider NAI List entries");
+				break;
+			}
+			os_memcpy(wpa_s->osu_prov[num_providers].osu_nai2,
+				  pos, len);
+			pos += len;
+			num_providers++;
+		}
 	}
 
 	wpa_s->fetch_osu_icon_in_progress = 1;
diff --git a/wpa_supplicant/interworking.c b/wpa_supplicant/interworking.c
index 44a828d..ebb3a50 100644
--- a/wpa_supplicant/interworking.c
+++ b/wpa_supplicant/interworking.c
@@ -303,8 +303,10 @@
 			wpabuf_put_u8(extra, HS20_STYPE_CONNECTION_CAPABILITY);
 		if (all)
 			wpabuf_put_u8(extra, HS20_STYPE_OPERATING_CLASS);
-		if (all)
+		if (all) {
 			wpabuf_put_u8(extra, HS20_STYPE_OSU_PROVIDERS_LIST);
+			wpabuf_put_u8(extra, HS20_STYPE_OSU_PROVIDERS_NAI_LIST);
+		}
 		gas_anqp_set_element_len(extra, len_pos);
 	}
 #endif /* CONFIG_HS20 */
@@ -2844,6 +2846,31 @@
 }
 
 
+static void interworking_parse_venue_url(struct wpa_supplicant *wpa_s,
+					 const u8 *data, size_t len)
+{
+	const u8 *pos = data, *end = data + len;
+	char url[255];
+
+	while (end - pos >= 2) {
+		u8 slen, num;
+
+		slen = *pos++;
+		if (slen < 1 || slen > end - pos) {
+			wpa_printf(MSG_DEBUG,
+				   "ANQP: Truncated Venue URL Duple field");
+			return;
+		}
+
+		num = *pos++;
+		os_memcpy(url, pos, slen - 1);
+		url[slen - 1] = '\0';
+		wpa_msg(wpa_s, MSG_INFO, RX_VENUE_URL "%u %s", num, url);
+		pos += slen - 1;
+	}
+}
+
+
 static void interworking_parse_rx_anqp_resp(struct wpa_supplicant *wpa_s,
 					    struct wpa_bss *bss, const u8 *sa,
 					    u16 info_id,
@@ -2950,6 +2977,18 @@
 		}
 		break;
 #endif /* CONFIG_FILS */
+	case ANQP_VENUE_URL:
+		wpa_msg(wpa_s, MSG_INFO, RX_ANQP MACSTR " Venue URL",
+			MAC2STR(sa));
+		anqp_add_extra(wpa_s, anqp, info_id, pos, slen);
+
+		if (!wpa_sm_pmf_enabled(wpa_s->wpa)) {
+			wpa_printf(MSG_DEBUG,
+				   "ANQP: Ignore Venue URL since PMF was not enabled");
+			break;
+		}
+		interworking_parse_venue_url(wpa_s, pos, slen);
+		break;
 	case ANQP_VENDOR_SPECIFIC:
 		if (slen < 3)
 			return;
diff --git a/wpa_supplicant/p2p_supplicant.c b/wpa_supplicant/p2p_supplicant.c
index 3d3296f..3626ea3 100644
--- a/wpa_supplicant/p2p_supplicant.c
+++ b/wpa_supplicant/p2p_supplicant.c
@@ -4358,22 +4358,18 @@
 			return -EINVAL;
 		}
 
-		// store generated MAC address.
-		if (wpa_s->conf->p2p_device_persistent_mac_addr)
-			os_free(wpa_s->conf->p2p_device_persistent_mac_addr);
-		size_t mac_addr_str_len = sizeof("00:00:00:00:00:00");
-		wpa_s->conf->p2p_device_persistent_mac_addr =
-			os_zalloc(mac_addr_str_len + 1);
-		os_snprintf(wpa_s->conf->p2p_device_persistent_mac_addr,
-			mac_addr_str_len, MACSTR, MAC2STR(addr));
+		/* Store generated MAC address. */
+		os_memcpy(wpa_s->conf->p2p_device_persistent_mac_addr, addr,
+			  ETH_ALEN);
 	} else {
-		// If there are existing saved groups, restore last MAC address.
-		// if there is no last used MAC address, the last one is factory MAC.
-		if (!wpa_s->conf->p2p_device_persistent_mac_addr)
+		/* If there are existing saved groups, restore last MAC address.
+		 * if there is no last used MAC address, the last one is
+		 * factory MAC. */
+		if (is_zero_ether_addr(
+			    wpa_s->conf->p2p_device_persistent_mac_addr))
 			return 0;
-
-		if (hwaddr_aton(wpa_s->conf->p2p_device_persistent_mac_addr, addr) < 0)
-			return -EINVAL;
+		os_memcpy(addr, wpa_s->conf->p2p_device_persistent_mac_addr,
+			  ETH_ALEN);
 		wpa_msg(wpa_s, MSG_DEBUG, "Restore last used MAC address.");
 	}
 
diff --git a/wpa_supplicant/scan.c b/wpa_supplicant/scan.c
index 56d53fb..ee39e0c 100644
--- a/wpa_supplicant/scan.c
+++ b/wpa_supplicant/scan.c
@@ -677,6 +677,87 @@
 }
 
 
+static void wpa_add_scan_ssid(struct wpa_supplicant *wpa_s,
+			      struct wpa_driver_scan_params *params,
+			      size_t max_ssids, const u8 *ssid, size_t ssid_len)
+{
+	unsigned int j;
+
+	for (j = 0; j < params->num_ssids; j++) {
+		if (params->ssids[j].ssid_len == ssid_len &&
+		    params->ssids[j].ssid &&
+		    os_memcmp(params->ssids[j].ssid, ssid, ssid_len) == 0)
+			return; /* already in the list */
+	}
+
+	if (params->num_ssids + 1 > max_ssids) {
+		wpa_printf(MSG_DEBUG, "Over max scan SSIDs for manual request");
+		return;
+	}
+
+	wpa_printf(MSG_DEBUG, "Scan SSID (manual request): %s",
+		   wpa_ssid_txt(ssid, ssid_len));
+
+	params->ssids[params->num_ssids].ssid = ssid;
+	params->ssids[params->num_ssids].ssid_len = ssid_len;
+	params->num_ssids++;
+}
+
+
+static void wpa_add_owe_scan_ssid(struct wpa_supplicant *wpa_s,
+				  struct wpa_driver_scan_params *params,
+				  struct wpa_ssid *ssid, size_t max_ssids)
+{
+#ifdef CONFIG_OWE
+	struct wpa_bss *bss;
+
+	if (!(ssid->key_mgmt & WPA_KEY_MGMT_OWE))
+		return;
+
+	wpa_printf(MSG_DEBUG, "OWE: Look for transition mode AP. ssid=%s",
+		   wpa_ssid_txt(ssid->ssid, ssid->ssid_len));
+
+	dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) {
+		const u8 *owe, *pos, *end;
+		const u8 *owe_ssid;
+		size_t owe_ssid_len;
+
+		if (bss->ssid_len != ssid->ssid_len ||
+		    os_memcmp(bss->ssid, ssid->ssid, ssid->ssid_len) != 0)
+			continue;
+
+		owe = wpa_bss_get_vendor_ie(bss, OWE_IE_VENDOR_TYPE);
+		if (!owe || owe[1] < 4)
+			continue;
+
+		pos = owe + 6;
+		end = owe + 2 + owe[1];
+
+		/* Must include BSSID and ssid_len */
+		if (end - pos < ETH_ALEN + 1)
+			return;
+
+		/* Skip BSSID */
+		pos += ETH_ALEN;
+		owe_ssid_len = *pos++;
+		owe_ssid = pos;
+
+		if ((size_t) (end - pos) < owe_ssid_len ||
+		    owe_ssid_len > SSID_MAX_LEN)
+			return;
+
+		wpa_printf(MSG_DEBUG,
+			   "OWE: scan_ssids: transition mode OWE ssid=%s",
+			   wpa_ssid_txt(owe_ssid, owe_ssid_len));
+
+		wpa_add_scan_ssid(wpa_s, params, max_ssids,
+				  owe_ssid, owe_ssid_len);
+		return;
+	}
+#endif /* CONFIG_OWE */
+}
+
+
 static void wpa_set_scan_ssids(struct wpa_supplicant *wpa_s,
 			       struct wpa_driver_scan_params *params,
 			       size_t max_ssids)
@@ -691,33 +772,17 @@
 	max_ssids = max_ssids > 1 ? max_ssids - 1 : max_ssids;
 
 	for (i = 0; i < wpa_s->scan_id_count; i++) {
-		unsigned int j;
-
 		ssid = wpa_config_get_network(wpa_s->conf, wpa_s->scan_id[i]);
-		if (!ssid || !ssid->scan_ssid)
+		if (!ssid)
 			continue;
-
-		for (j = 0; j < params->num_ssids; j++) {
-			if (params->ssids[j].ssid_len == ssid->ssid_len &&
-			    params->ssids[j].ssid &&
-			    os_memcmp(params->ssids[j].ssid, ssid->ssid,
-				      ssid->ssid_len) == 0)
-				break;
-		}
-		if (j < params->num_ssids)
-			continue; /* already in the list */
-
-		if (params->num_ssids + 1 > max_ssids) {
-			wpa_printf(MSG_DEBUG,
-				   "Over max scan SSIDs for manual request");
-			break;
-		}
-
-		wpa_printf(MSG_DEBUG, "Scan SSID (manual request): %s",
-			   wpa_ssid_txt(ssid->ssid, ssid->ssid_len));
-		params->ssids[params->num_ssids].ssid = ssid->ssid;
-		params->ssids[params->num_ssids].ssid_len = ssid->ssid_len;
-		params->num_ssids++;
+		if (ssid->scan_ssid)
+			wpa_add_scan_ssid(wpa_s, params, max_ssids,
+					  ssid->ssid, ssid->ssid_len);
+		/*
+		 * Also add the SSID of the OWE BSS, to allow discovery of
+		 * transition mode APs more quickly.
+		 */
+		wpa_add_owe_scan_ssid(wpa_s, params, ssid, max_ssids);
 	}
 
 	wpa_s->scan_id_count = 0;
@@ -984,6 +1049,17 @@
 				if (params.num_ssids + 1 >= max_ssids)
 					break;
 			}
+
+			if (!wpas_network_disabled(wpa_s, ssid)) {
+				/*
+				 * Also add the SSID of the OWE BSS, to allow
+				 * discovery of transition mode APs more
+				 * quickly.
+				 */
+				wpa_add_owe_scan_ssid(wpa_s, &params, ssid,
+						      max_ssids);
+			}
+
 			ssid = ssid->next;
 			if (ssid == start)
 				break;
diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c
index 708a347..d23dc06 100644
--- a/wpa_supplicant/sme.c
+++ b/wpa_supplicant/sme.c
@@ -240,6 +240,8 @@
 	u8 ext_capab[18];
 	int ext_capab_len;
 	int skip_auth;
+	u8 *wpa_ie;
+	size_t wpa_ie_len;
 #ifdef CONFIG_MBO
 	const u8 *mbo_ie;
 #endif /* CONFIG_MBO */
@@ -391,6 +393,28 @@
 		wpa_s->sme.assoc_req_ie_len = 0;
 	}
 
+	/* In case the WPA vendor IE is used, it should be placed after all the
+	 * non-vendor IEs, as the lower layer expects the IEs to be ordered as
+	 * defined in the standard. Store the WPA IE so it can later be
+	 * inserted at the correct location.
+	 */
+	wpa_ie = NULL;
+	wpa_ie_len = 0;
+	if (wpa_s->wpa_proto == WPA_PROTO_WPA) {
+		wpa_ie = os_memdup(wpa_s->sme.assoc_req_ie,
+				   wpa_s->sme.assoc_req_ie_len);
+		if (wpa_ie) {
+			wpa_dbg(wpa_s, MSG_DEBUG, "WPA: Storing WPA IE");
+
+			wpa_ie_len = wpa_s->sme.assoc_req_ie_len;
+			wpa_s->sme.assoc_req_ie_len = 0;
+		} else {
+			wpa_msg(wpa_s, MSG_WARNING, "WPA: Failed copy WPA IE");
+			wpas_connect_work_done(wpa_s);
+			return;
+		}
+	}
+
 #ifdef CONFIG_IEEE80211R
 	ie = wpa_bss_get_ie(bss, WLAN_EID_MOBILITY_DOMAIN);
 	if (ie && ie[1] >= MOBILITY_DOMAIN_ID_LEN)
@@ -527,6 +551,26 @@
 	}
 #endif /* CONFIG_HS20 */
 
+	if (wpa_ie) {
+		size_t len;
+
+		wpa_dbg(wpa_s, MSG_DEBUG, "WPA: Reinsert WPA IE");
+
+		len = sizeof(wpa_s->sme.assoc_req_ie) -
+			wpa_s->sme.assoc_req_ie_len;
+
+		if (len > wpa_ie_len) {
+			os_memcpy(wpa_s->sme.assoc_req_ie +
+				  wpa_s->sme.assoc_req_ie_len,
+				  wpa_ie, wpa_ie_len);
+			wpa_s->sme.assoc_req_ie_len += wpa_ie_len;
+		} else {
+			wpa_dbg(wpa_s, MSG_DEBUG, "WPA: Failed to add WPA IE");
+		}
+
+		os_free(wpa_ie);
+	}
+
 	if (wpa_s->vendor_elem[VENDOR_ELEM_ASSOC_REQ]) {
 		struct wpabuf *buf = wpa_s->vendor_elem[VENDOR_ELEM_ASSOC_REQ];
 		size_t len;
diff --git a/wpa_supplicant/wnm_sta.c b/wpa_supplicant/wnm_sta.c
index 7c410e7..6b68fc9 100644
--- a/wpa_supplicant/wnm_sta.c
+++ b/wpa_supplicant/wnm_sta.c
@@ -338,6 +338,9 @@
 	wpa_s->wnm_num_neighbor_report = 0;
 	os_free(wpa_s->wnm_neighbor_report_elements);
 	wpa_s->wnm_neighbor_report_elements = NULL;
+
+	wpabuf_free(wpa_s->coloc_intf_elems);
+	wpa_s->coloc_intf_elems = NULL;
 }
 
 
@@ -1717,6 +1720,46 @@
 }
 
 
+static void ieee802_11_rx_wnm_coloc_intf_req(struct wpa_supplicant *wpa_s,
+					     const u8 *sa, const u8 *frm,
+					     int len)
+{
+	u8 dialog_token, req_info, auto_report, timeout;
+
+	if (!wpa_s->conf->coloc_intf_reporting)
+		return;
+
+	/* Dialog Token [1] | Request Info [1] */
+
+	if (len < 2)
+		return;
+	dialog_token = frm[0];
+	req_info = frm[1];
+	auto_report = req_info & 0x03;
+	timeout = req_info >> 2;
+
+	wpa_dbg(wpa_s, MSG_DEBUG,
+		"WNM: Received Collocated Interference Request (dialog_token %u auto_report %u timeout %u sa " MACSTR ")",
+		dialog_token, auto_report, timeout, MAC2STR(sa));
+
+	if (dialog_token == 0)
+		return; /* only nonzero values are used for request */
+
+	if (wpa_s->wpa_state != WPA_COMPLETED ||
+	    os_memcmp(sa, wpa_s->bssid, ETH_ALEN) != 0) {
+		wpa_dbg(wpa_s, MSG_DEBUG,
+			"WNM: Collocated Interference Request frame not from current AP - ignore it");
+		return;
+	}
+
+	wpa_msg(wpa_s, MSG_INFO, COLOC_INTF_REQ "%u %u %u",
+		dialog_token, auto_report, timeout);
+	wpa_s->coloc_intf_dialog_token = dialog_token;
+	wpa_s->coloc_intf_auto_report = auto_report;
+	wpa_s->coloc_intf_timeout = timeout;
+}
+
+
 void ieee802_11_rx_wnm_action(struct wpa_supplicant *wpa_s,
 			      const struct ieee80211_mgmt *mgmt, size_t len)
 {
@@ -1750,8 +1793,75 @@
 	case WNM_NOTIFICATION_REQ:
 		ieee802_11_rx_wnm_notif_req(wpa_s, mgmt->sa, pos, end - pos);
 		break;
+	case WNM_COLLOCATED_INTERFERENCE_REQ:
+		ieee802_11_rx_wnm_coloc_intf_req(wpa_s, mgmt->sa, pos,
+						 end - pos);
+		break;
 	default:
 		wpa_printf(MSG_ERROR, "WNM: Unknown request");
 		break;
 	}
 }
+
+
+int wnm_send_coloc_intf_report(struct wpa_supplicant *wpa_s, u8 dialog_token,
+			       const struct wpabuf *elems)
+{
+	struct wpabuf *buf;
+	int ret;
+
+	if (wpa_s->wpa_state < WPA_ASSOCIATED || !elems)
+		return -1;
+
+	wpa_printf(MSG_DEBUG, "WNM: Send Collocated Interference Report to "
+		   MACSTR " (dialog token %u)",
+		   MAC2STR(wpa_s->bssid), dialog_token);
+
+	buf = wpabuf_alloc(3 + wpabuf_len(elems));
+	if (!buf)
+		return -1;
+
+	wpabuf_put_u8(buf, WLAN_ACTION_WNM);
+	wpabuf_put_u8(buf, WNM_COLLOCATED_INTERFERENCE_REPORT);
+	wpabuf_put_u8(buf, dialog_token);
+	wpabuf_put_buf(buf, elems);
+
+	ret = wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid,
+				  wpa_s->own_addr, wpa_s->bssid,
+				  wpabuf_head_u8(buf), wpabuf_len(buf), 0);
+	wpabuf_free(buf);
+	return ret;
+}
+
+
+void wnm_set_coloc_intf_elems(struct wpa_supplicant *wpa_s,
+			      struct wpabuf *elems)
+{
+	wpabuf_free(wpa_s->coloc_intf_elems);
+	if (elems && wpabuf_len(elems) == 0) {
+		wpabuf_free(elems);
+		elems = NULL;
+	}
+	wpa_s->coloc_intf_elems = elems;
+
+	if (wpa_s->conf->coloc_intf_reporting && wpa_s->coloc_intf_elems &&
+	    wpa_s->coloc_intf_dialog_token &&
+	    (wpa_s->coloc_intf_auto_report == 1 ||
+	     wpa_s->coloc_intf_auto_report == 3)) {
+		/* TODO: Check that there has not been less than
+		 * wpa_s->coloc_intf_timeout * 200 TU from the last report.
+		 */
+		wnm_send_coloc_intf_report(wpa_s,
+					   wpa_s->coloc_intf_dialog_token,
+					   wpa_s->coloc_intf_elems);
+	}
+}
+
+
+void wnm_clear_coloc_intf_reporting(struct wpa_supplicant *wpa_s)
+{
+#ifdef CONFIG_WNM
+	wpa_s->coloc_intf_dialog_token = 0;
+	wpa_s->coloc_intf_auto_report = 0;
+#endif /* CONFIG_WNM */
+}
diff --git a/wpa_supplicant/wnm_sta.h b/wpa_supplicant/wnm_sta.h
index 02cd1cd..29625f8 100644
--- a/wpa_supplicant/wnm_sta.h
+++ b/wpa_supplicant/wnm_sta.h
@@ -65,11 +65,16 @@
 				       int cand_list);
 
 void wnm_deallocate_memory(struct wpa_supplicant *wpa_s);
+int wnm_send_coloc_intf_report(struct wpa_supplicant *wpa_s, u8 dialog_token,
+			       const struct wpabuf *elems);
+void wnm_set_coloc_intf_elems(struct wpa_supplicant *wpa_s,
+			      struct wpabuf *elems);
 
 
 #ifdef CONFIG_WNM
 
 int wnm_scan_process(struct wpa_supplicant *wpa_s, int reply_on_fail);
+void wnm_clear_coloc_intf_reporting(struct wpa_supplicant *wpa_s);
 
 #else /* CONFIG_WNM */
 
@@ -79,6 +84,10 @@
 	return 0;
 }
 
+static inline void wnm_clear_coloc_intf_reporting(struct wpa_supplicant *wpa_s)
+{
+}
+
 #endif /* CONFIG_WNM */
 
 #endif /* WNM_STA_H */
diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c
index fc13a57..4432ec3 100644
--- a/wpa_supplicant/wpa_supplicant.c
+++ b/wpa_supplicant/wpa_supplicant.c
@@ -1646,6 +1646,10 @@
 	case 0: /* Bits 0-7 */
 		break;
 	case 1: /* Bits 8-15 */
+		if (wpa_s->conf->coloc_intf_reporting) {
+			/* Bit 13 - Collocated Interference Reporting */
+			*pos |= 0x20;
+		}
 		break;
 	case 2: /* Bits 16-23 */
 #ifdef CONFIG_WNM
@@ -3076,6 +3080,11 @@
 				"MFP: require MFP");
 			params.mgmt_frame_protection =
 				MGMT_FRAME_PROTECTION_REQUIRED;
+#ifdef CONFIG_OWE
+		} else if (!rsn && (ssid->key_mgmt & WPA_KEY_MGMT_OWE) &&
+			   !ssid->owe_only) {
+			params.mgmt_frame_protection = NO_MGMT_FRAME_PROTECTION;
+#endif /* CONFIG_OWE */
 		}
 	}
 #endif /* CONFIG_IEEE80211W */
@@ -3303,6 +3312,7 @@
 		return;
 
 	ssid->disabled = 0;
+	ssid->owe_transition_bss_select_count = 0;
 	wpas_clear_temp_disabled(wpa_s, ssid, 1);
 	wpas_notify_network_enabled_changed(wpa_s, ssid);
 
@@ -3567,6 +3577,8 @@
 	wpa_s->disconnected = 0;
 	wpa_s->reassociate = 1;
 	wpa_s->last_owe_group = 0;
+	if (ssid)
+		ssid->owe_transition_bss_select_count = 0;
 
 	if (wpa_s->connect_without_scan ||
 	    wpa_supplicant_fast_associate(wpa_s) != 1) {
diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h
index d367ba0..abacaa9 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -743,6 +743,8 @@
 	unsigned int mac_addr_changed:1;
 	unsigned int added_vif:1;
 	unsigned int wnmsleep_used:1;
+	unsigned int owe_transition_select:1;
+	unsigned int owe_transition_search:1;
 
 	struct os_reltime last_mac_addr_change;
 	int last_mac_addr_style;
@@ -1059,6 +1061,10 @@
 	struct neighbor_report *wnm_neighbor_report_elements;
 	struct os_reltime wnm_cand_valid_until;
 	u8 wnm_cand_from_bss[ETH_ALEN];
+	struct wpabuf *coloc_intf_elems;
+	u8 coloc_intf_dialog_token;
+	u8 coloc_intf_auto_report;
+	u8 coloc_intf_timeout;
 #ifdef CONFIG_MBO
 	unsigned int wnm_mbo_trans_reason_present:1;
 	u8 wnm_mbo_transition_reason;