Cumulative patch from commit a9491695b30a7f750dc45cb563d813b03f1d4b8d

a9491695b QCA vendor command to set/get NUD statistics
f593b6c11 nl80211: Do not reset vendor_scan_cookie after scan timeout
f2bc34480 wpa_supplicant: Fix global control interface for STA/STA-FIRST/STA-NEXT
cc3dae85b hostapd: Add possibility to send debug messages to syslog
0da355235 FST: Remove WPA_ASSERT from wpas_fst_send_action_cb()
968dce9b1 FST: Silence compiler warning on WPA_ASSERT
17e20b1e2 FST: Remove a bogus WPA_ASSERT()
6a5425fd6 Increase delayed EAPOL RX frame timeout
cef8fac04 wpa_auth: Make struct wpa_auth_callbacks const
30eddf352 Fix or supress various sparse warnings
b301f54e5 IBSS/mesh: Skip VHT channel setup with vht_disabled=1
adc6a5d81 mesh: Check remote peer HT Operation element
9eb5757a8 Define helper function set_disable_ht40()
7813b7c34 nl80211: Fix a memory leak on deinit with HT enabled mesh
6b585f420 mesh: Fix crash on removing virtual mesh interface
5208160b4 FILS: Parse received FILS HLP requests
5732b770f FILS: Allow FILS HLP requests to be added
a1aa2aebc Remove unused WLAN_CIPHER_SUITE_* definitions
a042e39ac nl80211: Use RSN_CIPHER_SUITE_* instead of WLAN_CIPHER_SUITE_*
2373a3117 Define all RSN_CIPHER_SUITE_* values
89ba101e1 Remove unused WLAN_AKM_SUITE_* definitions
bf9f8a052 Use RSN_AUTH_KEY_MGMT_* instead of WLAN_AKM_SUITE_* for wpa_akm_to_suite()
3aa24db95 nl80211: Use RSN_AUTH_KEY_MGMT_* instead of WLAN_AKM_SUITE_*
a1343fa6b Remove unnecessary ifdef from RSN_AUTH_KEY_MGMT_* definitions
afe731004 Fix CONFIG_SAE build without CONFIG_SME
34e8bfd7a Skip EVENT_ACS_CHANNEL_SELECTED also without CONFIG_AP
510fc2dfc Fix AKM suite selectors for FILS and Suite B
d7d0f909f QCA vendor command to carry the reason for power save failure
53b38209f GAS: Cancel gas_query_timeout when AP responds with comeback delay
d5bd94133 MBO: Silence a compiler warning when building without CONFIG_MBO
4c4070005 QCA vendor command to enable host driver offload ACS to user space
4d77d80ed mesh: Add MESH_PMKSA_GET/ADD commands
117875db3 D-Bus: Add GroupMgmt entry into the interface Capabilities dict
3cdb4ac07 D-Bus: Add pmf to global capabilities
adf8f45f8 D-Bus: Implement Pmf property
b98706c14 RSN IBSS: Fix TK clearing on Authentication frame RX
fa67debf4 Fix duplicate Reassociation Request frame dropping
6ff92677a wext: Cancel send_rfkill timeout in deinit
fcd3d6ce3 FILS: Fix PMK and PMKID derivation from ERP
ef495c78d OpenSSL: Implement sha384_vector()
a70cd0db8 nl80211: Don't register for Beacon frames for IEEE 802.11ad AP
a2aa21a3b Assign additional vendor specific elements for early HE testing
f09095d57 wpa_supplicant: Clarify group_rekey documentation
c85dfc6f8 nl80211: Set NL80211_ATTR_IFACE_SOCKET_OWNER for connect and associate
d07f450da Sync with mac80211-next.git include/uapi/linux/nl80211.h
8f315d050 Fix country code in wpa_supplicant AP mode Country element
29065686a D-Bus: Fix BSS Mode getter for invalid DMG BSS
b2442f256 nl80211: Debug prints for TDLS_OPER command and result
2901bc272 bgscan: Remove unnecessary NULL check
9d6eaad6b bgscan: Remove unnecessary NULL check
0f9b4a0f1 bgscan: Deliver beacon loss event to bgscan modules
688556722 nl80211: More complete processing of connection quality monitor events
54736d835 Store FST parameters to configuration file
35c78f7b9 Store osu_dir to configuration file
1f539c78f Store autoscan to configuration file
58ed9e31d Store filter_rssi to configuration file
1fb1bf99d Write sec_device_type to configuration file
b4bdeadfa Make "SET" behavior more consistent for dot11RSNA parameters
e3394c0e2 Make "SET non_pref_chan .." behavior more consistent
f8c201862 Fix cert_in_cb parsing in wpa_supplicant.conf
9284418d0 Fix writing of wpa_supplicant sae_groups configuration parameter
167f78a5e Send BEACON-REQ-TX-STATUS event only for beacon reports
7ba94fc4b RRM: Use wpa_hexdump_buf() instead of wpa_hexdump()
e4ec6bbfd nl80211: Register for Link Measurement Report frames in AP mode
33468e532 RRM: Document Link Measurement Report frame construction steps
40e9a3f32 RRM: Fix beacon report scan channels for VHT 80, 80+80, 160 MHz cases
5cda35089 RRM: Move wpabuf_resize() call into wpas_rrm_report_elem()
f2058f4af RRM: Remove unnecessary cb check
89fa633af nl80211: Fix error while enabling AP mode with driver-SME

The last change 89fa633af cherrypicks a later change that resolves
an issue with starting up AP mode.

Bug: 34681709
Test: Wifi Suite
Change-Id: Iba8c5824009d6480fb736b274d69243c77aef7fe
diff --git a/src/ap/ctrl_iface_ap.c b/src/ap/ctrl_iface_ap.c
index 3680fda..a760e3a 100644
--- a/src/ap/ctrl_iface_ap.c
+++ b/src/ap/ctrl_iface_ap.c
@@ -639,3 +639,60 @@
 {
 	wpa_auth_pmksa_flush(hapd->wpa_auth);
 }
+
+
+#ifdef CONFIG_PMKSA_CACHE_EXTERNAL
+#ifdef CONFIG_MESH
+
+int hostapd_ctrl_iface_pmksa_list_mesh(struct hostapd_data *hapd,
+				       const u8 *addr, char *buf, size_t len)
+{
+	return wpa_auth_pmksa_list_mesh(hapd->wpa_auth, addr, buf, len);
+}
+
+
+void * hostapd_ctrl_iface_pmksa_create_entry(const u8 *aa, char *cmd)
+{
+	u8 spa[ETH_ALEN];
+	u8 pmkid[PMKID_LEN];
+	u8 pmk[PMK_LEN_MAX];
+	char *pos;
+	int expiration;
+
+	/*
+	 * Entry format:
+	 * <BSSID> <PMKID> <PMK> <expiration in seconds>
+	 */
+
+	if (hwaddr_aton(cmd, spa))
+		return NULL;
+
+	pos = os_strchr(cmd, ' ');
+	if (!pos)
+		return NULL;
+	pos++;
+
+	if (hexstr2bin(pos, pmkid, PMKID_LEN) < 0)
+		return NULL;
+
+	pos = os_strchr(pos, ' ');
+	if (!pos)
+		return NULL;
+	pos++;
+
+	if (hexstr2bin(pos, pmk, PMK_LEN) < 0)
+		return NULL;
+
+	pos = os_strchr(pos, ' ');
+	if (!pos)
+		return NULL;
+	pos++;
+
+	if (sscanf(pos, "%d", &expiration) != 1)
+		return NULL;
+
+	return wpa_auth_pmksa_create_entry(aa, spa, pmk, pmkid, expiration);
+}
+
+#endif /* CONFIG_MESH */
+#endif /* CONFIG_PMKSA_CACHE_EXTERNAL */
diff --git a/src/ap/ctrl_iface_ap.h b/src/ap/ctrl_iface_ap.h
index 4f99680..3b61cac 100644
--- a/src/ap/ctrl_iface_ap.h
+++ b/src/ap/ctrl_iface_ap.h
@@ -32,5 +32,8 @@
 int hostapd_ctrl_iface_pmksa_list(struct hostapd_data *hapd, char *buf,
 				  size_t len);
 void hostapd_ctrl_iface_pmksa_flush(struct hostapd_data *hapd);
+int hostapd_ctrl_iface_pmksa_list_mesh(struct hostapd_data *hapd,
+				       const u8 *addr, char *buf, size_t len);
+void * hostapd_ctrl_iface_pmksa_create_entry(const u8 *aa, char *cmd);
 
 #endif /* CTRL_IFACE_AP_H */
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index 09850ef..cceeee0 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -1166,6 +1166,7 @@
 	u8 *ie_buf = NULL;
 	const u8 *pmk = NULL;
 	size_t pmk_len = 0;
+	u8 pmk_buf[PMK_LEN_MAX];
 
 	if (resp != WLAN_STATUS_SUCCESS)
 		goto fail;
@@ -1234,8 +1235,16 @@
 		wpabuf_put_u8(data, WLAN_EID_EXT_FILS_WRAPPED_DATA);
 		wpabuf_put_buf(data, erp_resp);
 
-		pmk = msk;
-		pmk_len = msk_len > PMK_LEN ? PMK_LEN : msk_len;
+		if (fils_rmsk_to_pmk(wpa_auth_sta_key_mgmt(sta->wpa_sm),
+				     msk, msk_len, sta->fils_snonce, fils_nonce,
+				     NULL, 0, pmk_buf, &pmk_len)) {
+			wpa_printf(MSG_DEBUG, "FILS: Failed to derive PMK");
+			resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+			wpabuf_free(data);
+			data = NULL;
+			goto fail;
+		}
+		pmk = pmk_buf;
 	} else if (pmksa) {
 		pmk = pmksa->pmk;
 		pmk_len = pmksa->pmk_len;
@@ -2398,6 +2407,85 @@
 }
 
 
+#ifdef CONFIG_FILS
+
+static void fils_process_hlp_req(struct hostapd_data *hapd,
+				 struct sta_info *sta,
+				 const u8 *pos, size_t len)
+{
+	const u8 *pkt, *end;
+
+	wpa_printf(MSG_DEBUG, "FILS: HLP request from " MACSTR " (dst=" MACSTR
+		   " src=" MACSTR " len=%u)",
+		   MAC2STR(sta->addr), MAC2STR(pos), MAC2STR(pos + ETH_ALEN),
+		   (unsigned int) len);
+	if (os_memcmp(sta->addr, pos + ETH_ALEN, ETH_ALEN) != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "FILS: Ignore HLP request with unexpected source address"
+			   MACSTR, MAC2STR(pos + ETH_ALEN));
+		return;
+	}
+
+	end = pos + len;
+	pkt = pos + 2 * ETH_ALEN;
+	if (end - pkt >= 6 &&
+	    os_memcmp(pkt, "\xaa\xaa\x03\x00\x00\x00", 6) == 0)
+		pkt += 6; /* Remove SNAP/LLC header */
+	wpa_hexdump(MSG_MSGDUMP, "FILS: HLP request packet", pkt, end - pkt);
+}
+
+
+static void fils_process_hlp(struct hostapd_data *hapd, struct sta_info *sta,
+			     const u8 *pos, int left)
+{
+	const u8 *end = pos + left;
+	u8 *tmp, *tmp_pos;
+
+	/* Check if there are any FILS HLP Container elements */
+	while (end - pos >= 2) {
+		if (2 + pos[1] > end - pos)
+			return;
+		if (pos[0] == WLAN_EID_EXTENSION &&
+		    pos[1] >= 1 + 2 * ETH_ALEN &&
+		    pos[2] == WLAN_EID_EXT_FILS_HLP_CONTAINER)
+			break;
+		pos += 2 + pos[1];
+	}
+	if (end - pos < 2)
+		return; /* No FILS HLP Container elements */
+
+	tmp = os_malloc(end - pos);
+	if (!tmp)
+		return;
+
+	while (end - pos >= 2) {
+		if (2 + pos[1] > end - pos ||
+		    pos[0] != WLAN_EID_EXTENSION ||
+		    pos[1] < 1 + 2 * ETH_ALEN ||
+		    pos[2] != WLAN_EID_EXT_FILS_HLP_CONTAINER)
+			break;
+		tmp_pos = tmp;
+		os_memcpy(tmp_pos, pos + 3, pos[1] - 1);
+		tmp_pos += pos[1] - 1;
+		pos += 2 + pos[1];
+
+		/* Add possible fragments */
+		while (end - pos >= 2 && pos[0] == WLAN_EID_FRAGMENT &&
+		       2 + pos[1] <= end - pos) {
+			os_memcpy(tmp_pos, pos + 2, pos[1]);
+			tmp_pos += pos[1];
+			pos += 2 + pos[1];
+		}
+
+		fils_process_hlp_req(hapd, sta, tmp, tmp_pos - tmp);
+	}
+
+	os_free(tmp);
+}
+
+#endif /* CONFIG_FILS */
+
+
 static void handle_assoc(struct hostapd_data *hapd,
 			 const struct ieee80211_mgmt *mgmt, size_t len,
 			 int reassoc)
@@ -2518,8 +2606,8 @@
 	if ((fc & WLAN_FC_RETRY) &&
 	    sta->last_seq_ctrl != WLAN_INVALID_MGMT_SEQ &&
 	    sta->last_seq_ctrl == seq_ctrl &&
-	    sta->last_subtype == reassoc ? WLAN_FC_STYPE_REASSOC_REQ :
-	    WLAN_FC_STYPE_ASSOC_REQ) {
+	    sta->last_subtype == (reassoc ? WLAN_FC_STYPE_REASSOC_REQ :
+				  WLAN_FC_STYPE_ASSOC_REQ)) {
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_DEBUG,
 			       "Drop repeated association frame seq_ctrl=0x%x",
@@ -2669,6 +2757,13 @@
 
 	sta->pending_wds_enable = 0;
 
+#ifdef CONFIG_FILS
+	if (sta->auth_alg == WLAN_AUTH_FILS_SK ||
+	    sta->auth_alg == WLAN_AUTH_FILS_SK_PFS ||
+	    sta->auth_alg == WLAN_AUTH_FILS_PK)
+		fils_process_hlp(hapd, sta, pos, left);
+#endif /* CONFIG_FILS */
+
  fail:
 	/*
 	 * In case of a successful response, add the station to the driver.
@@ -3409,6 +3504,7 @@
 			     size_t len, int ok)
 {
 	struct sta_info *sta;
+	const struct rrm_measurement_report_element *report;
 
 	if (is_multicast_ether_addr(mgmt->da))
 		return;
@@ -3419,10 +3515,15 @@
 		return;
 	}
 
-	if (len < 24 + 2)
+	if (len < 24 + 5 + sizeof(*report))
 		return;
+	report = (const struct rrm_measurement_report_element *)
+		&mgmt->u.action.u.rrm.variable[2];
 	if (mgmt->u.action.category == WLAN_ACTION_RADIO_MEASUREMENT &&
-	    mgmt->u.action.u.rrm.action == WLAN_RRM_RADIO_MEASUREMENT_REQUEST)
+	    mgmt->u.action.u.rrm.action == WLAN_RRM_RADIO_MEASUREMENT_REQUEST &&
+	    report->eid == WLAN_EID_MEASURE_REQUEST &&
+	    report->len >= 3 &&
+	    report->type == MEASURE_TYPE_BEACON)
 		hostapd_rrm_beacon_req_tx_status(hapd, mgmt, len, ok);
 }
 
diff --git a/src/ap/pmksa_cache_auth.c b/src/ap/pmksa_cache_auth.c
index d610e7e..bce5abf 100644
--- a/src/ap/pmksa_cache_auth.c
+++ b/src/ap/pmksa_cache_auth.c
@@ -282,7 +282,42 @@
 		     const u8 *aa, const u8 *spa, int session_timeout,
 		     struct eapol_state_machine *eapol, int akmp)
 {
-	struct rsn_pmksa_cache_entry *entry, *pos;
+	struct rsn_pmksa_cache_entry *entry;
+
+	entry = pmksa_cache_auth_create_entry(pmk, pmk_len, pmkid, kck, kck_len,
+					      aa, spa, session_timeout, eapol,
+					      akmp);
+
+	if (pmksa_cache_auth_add_entry(pmksa, entry) < 0)
+		return NULL;
+
+	return entry;
+}
+
+
+/**
+ * pmksa_cache_auth_create_entry - Create a PMKSA cache entry
+ * @pmk: The new pairwise master key
+ * @pmk_len: PMK length in bytes, usually PMK_LEN (32)
+ * @pmkid: Calculated PMKID
+ * @kck: Key confirmation key or %NULL if not yet derived
+ * @kck_len: KCK length in bytes
+ * @aa: Authenticator address
+ * @spa: Supplicant address
+ * @session_timeout: Session timeout
+ * @eapol: Pointer to EAPOL state machine data
+ * @akmp: WPA_KEY_MGMT_* used in key derivation
+ * Returns: Pointer to the added PMKSA cache entry or %NULL on error
+ *
+ * This function creates a PMKSA entry.
+ */
+struct rsn_pmksa_cache_entry *
+pmksa_cache_auth_create_entry(const u8 *pmk, size_t pmk_len, const u8 *pmkid,
+			      const u8 *kck, size_t kck_len, const u8 *aa,
+			      const u8 *spa, int session_timeout,
+			      struct eapol_state_machine *eapol, int akmp)
+{
+	struct rsn_pmksa_cache_entry *entry;
 	struct os_reltime now;
 
 	if (pmk_len > PMK_LEN_MAX)
@@ -315,9 +350,30 @@
 	os_memcpy(entry->spa, spa, ETH_ALEN);
 	pmksa_cache_from_eapol_data(entry, eapol);
 
+	return entry;
+}
+
+
+/**
+ * pmksa_cache_auth_add_entry - Add a PMKSA cache entry
+ * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init()
+ * @entry: Pointer to PMKSA cache entry
+ *
+ * This function adds PMKSA cache entry to the PMKSA cache. If an old entry is
+ * already in the cache for the same Supplicant, this entry will be replaced
+ * with the new entry. PMKID will be calculated based on the PMK.
+ */
+int pmksa_cache_auth_add_entry(struct rsn_pmksa_cache *pmksa,
+			       struct rsn_pmksa_cache_entry *entry)
+{
+	struct rsn_pmksa_cache_entry *pos;
+
+	if (entry == NULL)
+		return -1;
+
 	/* Replace an old entry for the same STA (if found) with the new entry
 	 */
-	pos = pmksa_cache_auth_get(pmksa, spa, NULL);
+	pos = pmksa_cache_auth_get(pmksa, entry->spa, NULL);
 	if (pos)
 		pmksa_cache_free_entry(pmksa, pos);
 
@@ -331,7 +387,7 @@
 
 	pmksa_cache_link_entry(pmksa, entry);
 
-	return entry;
+	return 0;
 }
 
 
@@ -605,3 +661,70 @@
 	}
 	return pos - buf;
 }
+
+
+#ifdef CONFIG_PMKSA_CACHE_EXTERNAL
+#ifdef CONFIG_MESH
+
+/**
+ * pmksa_cache_auth_list_mesh - Dump text list of entries in PMKSA cache
+ * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init()
+ * @addr: MAC address of the peer (NULL means any)
+ * @buf: Buffer for the list
+ * @len: Length of the buffer
+ * Returns: Number of bytes written to buffer
+ *
+ * This function is used to generate a text format representation of the
+ * current PMKSA cache contents for the ctrl_iface PMKSA_GET command to store
+ * in external storage.
+ */
+int pmksa_cache_auth_list_mesh(struct rsn_pmksa_cache *pmksa, const u8 *addr,
+			       char *buf, size_t len)
+{
+	int ret;
+	char *pos, *end;
+	struct rsn_pmksa_cache_entry *entry;
+	struct os_reltime now;
+
+	pos = buf;
+	end = buf + len;
+	os_get_reltime(&now);
+
+
+	/*
+	 * Entry format:
+	 * <BSSID> <PMKID> <PMK> <expiration in seconds>
+	 */
+	for (entry = pmksa->pmksa; entry; entry = entry->next) {
+		if (addr && os_memcmp(entry->spa, addr, ETH_ALEN) != 0)
+			continue;
+
+		ret = os_snprintf(pos, end - pos, MACSTR " ",
+				  MAC2STR(entry->spa));
+		if (os_snprintf_error(end - pos, ret))
+			return 0;
+		pos += ret;
+
+		pos += wpa_snprintf_hex(pos, end - pos, entry->pmkid,
+					PMKID_LEN);
+
+		ret = os_snprintf(pos, end - pos, " ");
+		if (os_snprintf_error(end - pos, ret))
+			return 0;
+		pos += ret;
+
+		pos += wpa_snprintf_hex(pos, end - pos, entry->pmk,
+					entry->pmk_len);
+
+		ret = os_snprintf(pos, end - pos, " %d\n",
+				  (int) (entry->expiration - now.sec));
+		if (os_snprintf_error(end - pos, ret))
+			return 0;
+		pos += ret;
+	}
+
+	return pos - buf;
+}
+
+#endif /* CONFIG_MESH */
+#endif /* CONFIG_PMKSA_CACHE_EXTERNAL */
diff --git a/src/ap/pmksa_cache_auth.h b/src/ap/pmksa_cache_auth.h
index d8d9c5a..bd1b672 100644
--- a/src/ap/pmksa_cache_auth.h
+++ b/src/ap/pmksa_cache_auth.h
@@ -53,6 +53,13 @@
 		     const u8 *aa, const u8 *spa, int session_timeout,
 		     struct eapol_state_machine *eapol, int akmp);
 struct rsn_pmksa_cache_entry *
+pmksa_cache_auth_create_entry(const u8 *pmk, size_t pmk_len, const u8 *pmkid,
+			      const u8 *kck, size_t kck_len, const u8 *aa,
+			      const u8 *spa, int session_timeout,
+			      struct eapol_state_machine *eapol, int akmp);
+int pmksa_cache_auth_add_entry(struct rsn_pmksa_cache *pmksa,
+			       struct rsn_pmksa_cache_entry *entry);
+struct rsn_pmksa_cache_entry *
 pmksa_cache_add_okc(struct rsn_pmksa_cache *pmksa,
 		    const struct rsn_pmksa_cache_entry *old_entry,
 		    const u8 *aa, const u8 *pmkid);
@@ -65,5 +72,7 @@
 					   struct radius_das_attrs *attr);
 int pmksa_cache_auth_list(struct rsn_pmksa_cache *pmksa, char *buf, size_t len);
 void pmksa_cache_auth_flush(struct rsn_pmksa_cache *pmksa);
+int pmksa_cache_auth_list_mesh(struct rsn_pmksa_cache *pmksa, const u8 *addr,
+			       char *buf, size_t len);
 
 #endif /* PMKSA_CACHE_H */
diff --git a/src/ap/taxonomy.c b/src/ap/taxonomy.c
index cea8b72..ae157a7 100644
--- a/src/ap/taxonomy.c
+++ b/src/ap/taxonomy.c
@@ -21,6 +21,7 @@
 #include "common/wpa_ctrl.h"
 #include "hostapd.h"
 #include "sta_info.h"
+#include "taxonomy.h"
 
 
 /* Copy a string with no funny schtuff allowed; only alphanumerics. */
diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c
index 43e3558..69e3a5d 100644
--- a/src/ap/wpa_auth.c
+++ b/src/ap/wpa_auth.c
@@ -75,8 +75,8 @@
 static inline int wpa_auth_mic_failure_report(
 	struct wpa_authenticator *wpa_auth, const u8 *addr)
 {
-	if (wpa_auth->cb.mic_failure_report)
-		return wpa_auth->cb.mic_failure_report(wpa_auth->cb.ctx, addr);
+	if (wpa_auth->cb->mic_failure_report)
+		return wpa_auth->cb->mic_failure_report(wpa_auth->cb_ctx, addr);
 	return 0;
 }
 
@@ -84,8 +84,8 @@
 static inline void wpa_auth_psk_failure_report(
 	struct wpa_authenticator *wpa_auth, const u8 *addr)
 {
-	if (wpa_auth->cb.psk_failure_report)
-		wpa_auth->cb.psk_failure_report(wpa_auth->cb.ctx, addr);
+	if (wpa_auth->cb->psk_failure_report)
+		wpa_auth->cb->psk_failure_report(wpa_auth->cb_ctx, addr);
 }
 
 
@@ -93,17 +93,17 @@
 				      const u8 *addr, wpa_eapol_variable var,
 				      int value)
 {
-	if (wpa_auth->cb.set_eapol)
-		wpa_auth->cb.set_eapol(wpa_auth->cb.ctx, addr, var, value);
+	if (wpa_auth->cb->set_eapol)
+		wpa_auth->cb->set_eapol(wpa_auth->cb_ctx, addr, var, value);
 }
 
 
 static inline int wpa_auth_get_eapol(struct wpa_authenticator *wpa_auth,
 				     const u8 *addr, wpa_eapol_variable var)
 {
-	if (wpa_auth->cb.get_eapol == NULL)
+	if (wpa_auth->cb->get_eapol == NULL)
 		return -1;
-	return wpa_auth->cb.get_eapol(wpa_auth->cb.ctx, addr, var);
+	return wpa_auth->cb->get_eapol(wpa_auth->cb_ctx, addr, var);
 }
 
 
@@ -112,19 +112,19 @@
 					  const u8 *p2p_dev_addr,
 					  const u8 *prev_psk)
 {
-	if (wpa_auth->cb.get_psk == NULL)
+	if (wpa_auth->cb->get_psk == NULL)
 		return NULL;
-	return wpa_auth->cb.get_psk(wpa_auth->cb.ctx, addr, p2p_dev_addr,
-				    prev_psk);
+	return wpa_auth->cb->get_psk(wpa_auth->cb_ctx, addr, p2p_dev_addr,
+				     prev_psk);
 }
 
 
 static inline int wpa_auth_get_msk(struct wpa_authenticator *wpa_auth,
 				   const u8 *addr, u8 *msk, size_t *len)
 {
-	if (wpa_auth->cb.get_msk == NULL)
+	if (wpa_auth->cb->get_msk == NULL)
 		return -1;
-	return wpa_auth->cb.get_msk(wpa_auth->cb.ctx, addr, msk, len);
+	return wpa_auth->cb->get_msk(wpa_auth->cb_ctx, addr, msk, len);
 }
 
 
@@ -133,19 +133,19 @@
 				   enum wpa_alg alg, const u8 *addr, int idx,
 				   u8 *key, size_t key_len)
 {
-	if (wpa_auth->cb.set_key == NULL)
+	if (wpa_auth->cb->set_key == NULL)
 		return -1;
-	return wpa_auth->cb.set_key(wpa_auth->cb.ctx, vlan_id, alg, addr, idx,
-				    key, key_len);
+	return wpa_auth->cb->set_key(wpa_auth->cb_ctx, vlan_id, alg, addr, idx,
+				     key, key_len);
 }
 
 
 static inline int wpa_auth_get_seqnum(struct wpa_authenticator *wpa_auth,
 				      const u8 *addr, int idx, u8 *seq)
 {
-	if (wpa_auth->cb.get_seqnum == NULL)
+	if (wpa_auth->cb->get_seqnum == NULL)
 		return -1;
-	return wpa_auth->cb.get_seqnum(wpa_auth->cb.ctx, addr, idx, seq);
+	return wpa_auth->cb->get_seqnum(wpa_auth->cb_ctx, addr, idx, seq);
 }
 
 
@@ -153,10 +153,10 @@
 wpa_auth_send_eapol(struct wpa_authenticator *wpa_auth, const u8 *addr,
 		    const u8 *data, size_t data_len, int encrypt)
 {
-	if (wpa_auth->cb.send_eapol == NULL)
+	if (wpa_auth->cb->send_eapol == NULL)
 		return -1;
-	return wpa_auth->cb.send_eapol(wpa_auth->cb.ctx, addr, data, data_len,
-				       encrypt);
+	return wpa_auth->cb->send_eapol(wpa_auth->cb_ctx, addr, data, data_len,
+					encrypt);
 }
 
 
@@ -164,9 +164,9 @@
 static inline int wpa_auth_start_ampe(struct wpa_authenticator *wpa_auth,
 				      const u8 *addr)
 {
-	if (wpa_auth->cb.start_ampe == NULL)
+	if (wpa_auth->cb->start_ampe == NULL)
 		return -1;
-	return wpa_auth->cb.start_ampe(wpa_auth->cb.ctx, addr);
+	return wpa_auth->cb->start_ampe(wpa_auth->cb_ctx, addr);
 }
 #endif /* CONFIG_MESH */
 
@@ -175,9 +175,9 @@
 			  int (*cb)(struct wpa_state_machine *sm, void *ctx),
 			  void *cb_ctx)
 {
-	if (wpa_auth->cb.for_each_sta == NULL)
+	if (wpa_auth->cb->for_each_sta == NULL)
 		return 0;
-	return wpa_auth->cb.for_each_sta(wpa_auth->cb.ctx, cb, cb_ctx);
+	return wpa_auth->cb->for_each_sta(wpa_auth->cb_ctx, cb, cb_ctx);
 }
 
 
@@ -185,18 +185,18 @@
 			   int (*cb)(struct wpa_authenticator *a, void *ctx),
 			   void *cb_ctx)
 {
-	if (wpa_auth->cb.for_each_auth == NULL)
+	if (wpa_auth->cb->for_each_auth == NULL)
 		return 0;
-	return wpa_auth->cb.for_each_auth(wpa_auth->cb.ctx, cb, cb_ctx);
+	return wpa_auth->cb->for_each_auth(wpa_auth->cb_ctx, cb, cb_ctx);
 }
 
 
 void wpa_auth_logger(struct wpa_authenticator *wpa_auth, const u8 *addr,
 		     logger_level level, const char *txt)
 {
-	if (wpa_auth->cb.logger == NULL)
+	if (wpa_auth->cb->logger == NULL)
 		return;
-	wpa_auth->cb.logger(wpa_auth->cb.ctx, addr, level, txt);
+	wpa_auth->cb->logger(wpa_auth->cb_ctx, addr, level, txt);
 }
 
 
@@ -207,7 +207,7 @@
 	int maxlen;
 	va_list ap;
 
-	if (wpa_auth->cb.logger == NULL)
+	if (wpa_auth->cb->logger == NULL)
 		return;
 
 	maxlen = os_strlen(fmt) + 100;
@@ -228,11 +228,11 @@
 static void wpa_sta_disconnect(struct wpa_authenticator *wpa_auth,
 			       const u8 *addr)
 {
-	if (wpa_auth->cb.disconnect == NULL)
+	if (wpa_auth->cb->disconnect == NULL)
 		return;
 	wpa_printf(MSG_DEBUG, "wpa_sta_disconnect STA " MACSTR, MAC2STR(addr));
-	wpa_auth->cb.disconnect(wpa_auth->cb.ctx, addr,
-				WLAN_REASON_PREV_AUTH_NOT_VALID);
+	wpa_auth->cb->disconnect(wpa_auth->cb_ctx, addr,
+				 WLAN_REASON_PREV_AUTH_NOT_VALID);
 }
 
 
@@ -416,7 +416,8 @@
  */
 struct wpa_authenticator * wpa_init(const u8 *addr,
 				    struct wpa_auth_config *conf,
-				    struct wpa_auth_callbacks *cb)
+				    const struct wpa_auth_callbacks *cb,
+				    void *cb_ctx)
 {
 	struct wpa_authenticator *wpa_auth;
 
@@ -425,7 +426,8 @@
 		return NULL;
 	os_memcpy(wpa_auth->addr, addr, ETH_ALEN);
 	os_memcpy(&wpa_auth->conf, conf, sizeof(*conf));
-	os_memcpy(&wpa_auth->cb, cb, sizeof(*cb));
+	wpa_auth->cb = cb;
+	wpa_auth->cb_ctx = cb_ctx;
 
 	if (wpa_auth_gen_wpa_ie(wpa_auth)) {
 		wpa_printf(MSG_ERROR, "Could not generate WPA IE.");
@@ -1949,7 +1951,7 @@
 #endif /* CONFIG_IEEE80211R_AP */
 	} else {
 		wpa_printf(MSG_DEBUG, "WPA: Could not get PMK, get_msk: %p",
-			   sm->wpa_auth->cb.get_msk);
+			   sm->wpa_auth->cb->get_msk);
 		sm->Disconnect = TRUE;
 		return;
 	}
@@ -3850,6 +3852,58 @@
 }
 
 
+#ifdef CONFIG_PMKSA_CACHE_EXTERNAL
+#ifdef CONFIG_MESH
+
+int wpa_auth_pmksa_list_mesh(struct wpa_authenticator *wpa_auth, const u8 *addr,
+			     char *buf, size_t len)
+{
+	if (!wpa_auth || !wpa_auth->pmksa)
+		return 0;
+
+	return pmksa_cache_auth_list_mesh(wpa_auth->pmksa, addr, buf, len);
+}
+
+
+struct rsn_pmksa_cache_entry *
+wpa_auth_pmksa_create_entry(const u8 *aa, const u8 *spa, const u8 *pmk,
+			    const u8 *pmkid, int expiration)
+{
+	struct rsn_pmksa_cache_entry *entry;
+	struct os_reltime now;
+
+	entry = pmksa_cache_auth_create_entry(pmk, PMK_LEN, pmkid, NULL, 0, aa,
+					      spa, 0, NULL, WPA_KEY_MGMT_SAE);
+	if (!entry)
+		return NULL;
+
+	os_get_reltime(&now);
+	entry->expiration = now.sec + expiration;
+	return entry;
+}
+
+
+int wpa_auth_pmksa_add_entry(struct wpa_authenticator *wpa_auth,
+			     struct rsn_pmksa_cache_entry *entry)
+{
+	int ret;
+
+	if (!wpa_auth || !wpa_auth->pmksa)
+		return -1;
+
+	ret = pmksa_cache_auth_add_entry(wpa_auth->pmksa, entry);
+	if (ret < 0)
+		wpa_printf(MSG_DEBUG,
+			   "RSN: Failed to store external PMKSA cache for "
+			   MACSTR, MAC2STR(entry->spa));
+
+	return ret;
+}
+
+#endif /* CONFIG_MESH */
+#endif /* CONFIG_PMKSA_CACHE_EXTERNAL */
+
+
 struct rsn_pmksa_cache_entry *
 wpa_auth_pmksa_get(struct wpa_authenticator *wpa_auth, const u8 *sta_addr,
 		   const u8 *pmkid)
diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h
index 743f2e6..a44b030 100644
--- a/src/ap/wpa_auth.h
+++ b/src/ap/wpa_auth.h
@@ -198,7 +198,6 @@
 } wpa_eapol_variable;
 
 struct wpa_auth_callbacks {
-	void *ctx;
 	void (*logger)(void *ctx, const u8 *addr, logger_level level,
 		       const char *txt);
 	void (*disconnect)(void *ctx, const u8 *addr, u16 reason);
@@ -235,7 +234,8 @@
 
 struct wpa_authenticator * wpa_init(const u8 *addr,
 				    struct wpa_auth_config *conf,
-				    struct wpa_auth_callbacks *cb);
+				    const struct wpa_auth_callbacks *cb,
+				    void *cb_ctx);
 int wpa_init_keys(struct wpa_authenticator *wpa_auth);
 void wpa_deinit(struct wpa_authenticator *wpa_auth);
 int wpa_reconfig(struct wpa_authenticator *wpa_auth,
@@ -302,6 +302,13 @@
 int wpa_auth_pmksa_list(struct wpa_authenticator *wpa_auth, char *buf,
 			size_t len);
 void wpa_auth_pmksa_flush(struct wpa_authenticator *wpa_auth);
+int wpa_auth_pmksa_list_mesh(struct wpa_authenticator *wpa_auth, const u8 *addr,
+			     char *buf, size_t len);
+struct rsn_pmksa_cache_entry *
+wpa_auth_pmksa_create_entry(const u8 *aa, const u8 *spa, const u8 *pmk,
+			    const u8 *pmkid, int expiration);
+int wpa_auth_pmksa_add_entry(struct wpa_authenticator *wpa_auth,
+			     struct rsn_pmksa_cache_entry *entry);
 struct rsn_pmksa_cache_entry *
 wpa_auth_pmksa_get(struct wpa_authenticator *wpa_auth, const u8 *sta_addr,
 		   const u8 *pmkid);
diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c
index 7ab371f..1fe3c2b 100644
--- a/src/ap/wpa_auth_ft.c
+++ b/src/ap/wpa_auth_ft.c
@@ -33,21 +33,21 @@
 static int wpa_ft_rrb_send(struct wpa_authenticator *wpa_auth, const u8 *dst,
 			   const u8 *data, size_t data_len)
 {
-	if (wpa_auth->cb.send_ether == NULL)
+	if (wpa_auth->cb->send_ether == NULL)
 		return -1;
 	wpa_printf(MSG_DEBUG, "FT: RRB send to " MACSTR, MAC2STR(dst));
-	return wpa_auth->cb.send_ether(wpa_auth->cb.ctx, dst, ETH_P_RRB,
-				       data, data_len);
+	return wpa_auth->cb->send_ether(wpa_auth->cb_ctx, dst, ETH_P_RRB,
+					data, data_len);
 }
 
 
 static int wpa_ft_action_send(struct wpa_authenticator *wpa_auth,
 			      const u8 *dst, const u8 *data, size_t data_len)
 {
-	if (wpa_auth->cb.send_ft_action == NULL)
+	if (wpa_auth->cb->send_ft_action == NULL)
 		return -1;
-	return wpa_auth->cb.send_ft_action(wpa_auth->cb.ctx, dst,
-					   data, data_len);
+	return wpa_auth->cb->send_ft_action(wpa_auth->cb_ctx, dst,
+					    data, data_len);
 }
 
 
@@ -55,19 +55,19 @@
 				 const u8 *addr, const u8 *p2p_dev_addr,
 				 const u8 *prev_psk)
 {
-	if (wpa_auth->cb.get_psk == NULL)
+	if (wpa_auth->cb->get_psk == NULL)
 		return NULL;
-	return wpa_auth->cb.get_psk(wpa_auth->cb.ctx, addr, p2p_dev_addr,
-				    prev_psk);
+	return wpa_auth->cb->get_psk(wpa_auth->cb_ctx, addr, p2p_dev_addr,
+				     prev_psk);
 }
 
 
 static struct wpa_state_machine *
 wpa_ft_add_sta(struct wpa_authenticator *wpa_auth, const u8 *sta_addr)
 {
-	if (wpa_auth->cb.add_sta == NULL)
+	if (wpa_auth->cb->add_sta == NULL)
 		return NULL;
-	return wpa_auth->cb.add_sta(wpa_auth->cb.ctx, sta_addr);
+	return wpa_auth->cb->add_sta(wpa_auth->cb_ctx, sta_addr);
 }
 
 
@@ -75,12 +75,12 @@
 			    const u8 *sta_addr,
 			    u8 *tspec_ie, size_t tspec_ielen)
 {
-	if (wpa_auth->cb.add_tspec == NULL) {
+	if (wpa_auth->cb->add_tspec == NULL) {
 		wpa_printf(MSG_DEBUG, "FT: add_tspec is not initialized");
 		return -1;
 	}
-	return wpa_auth->cb.add_tspec(wpa_auth->cb.ctx, sta_addr, tspec_ie,
-				      tspec_ielen);
+	return wpa_auth->cb->add_tspec(wpa_auth->cb_ctx, sta_addr, tspec_ie,
+				       tspec_ielen);
 }
 
 
@@ -418,9 +418,9 @@
 static inline int wpa_auth_get_seqnum(struct wpa_authenticator *wpa_auth,
 				      const u8 *addr, int idx, u8 *seq)
 {
-	if (wpa_auth->cb.get_seqnum == NULL)
+	if (wpa_auth->cb->get_seqnum == NULL)
 		return -1;
-	return wpa_auth->cb.get_seqnum(wpa_auth->cb.ctx, addr, idx, seq);
+	return wpa_auth->cb->get_seqnum(wpa_auth->cb_ctx, addr, idx, seq);
 }
 
 
@@ -773,10 +773,10 @@
 				   enum wpa_alg alg, const u8 *addr, int idx,
 				   u8 *key, size_t key_len)
 {
-	if (wpa_auth->cb.set_key == NULL)
+	if (wpa_auth->cb->set_key == NULL)
 		return -1;
-	return wpa_auth->cb.set_key(wpa_auth->cb.ctx, vlan_id, alg, addr, idx,
-				    key, key_len);
+	return wpa_auth->cb->set_key(wpa_auth->cb_ctx, vlan_id, alg, addr, idx,
+				     key, key_len);
 }
 
 
diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c
index aabac36..22518a1 100644
--- a/src/ap/wpa_auth_glue.c
+++ b/src/ap/wpa_auth_glue.c
@@ -595,7 +595,27 @@
 int hostapd_setup_wpa(struct hostapd_data *hapd)
 {
 	struct wpa_auth_config _conf;
-	struct wpa_auth_callbacks cb;
+	static const struct wpa_auth_callbacks cb = {
+		.logger = hostapd_wpa_auth_logger,
+		.disconnect = hostapd_wpa_auth_disconnect,
+		.mic_failure_report = hostapd_wpa_auth_mic_failure_report,
+		.psk_failure_report = hostapd_wpa_auth_psk_failure_report,
+		.set_eapol = hostapd_wpa_auth_set_eapol,
+		.get_eapol = hostapd_wpa_auth_get_eapol,
+		.get_psk = hostapd_wpa_auth_get_psk,
+		.get_msk = hostapd_wpa_auth_get_msk,
+		.set_key = hostapd_wpa_auth_set_key,
+		.get_seqnum = hostapd_wpa_auth_get_seqnum,
+		.send_eapol = hostapd_wpa_auth_send_eapol,
+		.for_each_sta = hostapd_wpa_auth_for_each_sta,
+		.for_each_auth = hostapd_wpa_auth_for_each_auth,
+		.send_ether = hostapd_wpa_auth_send_ether,
+#ifdef CONFIG_IEEE80211R_AP
+		.send_ft_action = hostapd_wpa_auth_send_ft_action,
+		.add_sta = hostapd_wpa_auth_add_sta,
+		.add_tspec = hostapd_wpa_auth_add_tspec,
+#endif /* CONFIG_IEEE80211R_AP */
+	};
 	const u8 *wpa_ie;
 	size_t wpa_ie_len;
 
@@ -604,28 +624,7 @@
 		_conf.tx_status = 1;
 	if (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_AP_MLME)
 		_conf.ap_mlme = 1;
-	os_memset(&cb, 0, sizeof(cb));
-	cb.ctx = hapd;
-	cb.logger = hostapd_wpa_auth_logger;
-	cb.disconnect = hostapd_wpa_auth_disconnect;
-	cb.mic_failure_report = hostapd_wpa_auth_mic_failure_report;
-	cb.psk_failure_report = hostapd_wpa_auth_psk_failure_report;
-	cb.set_eapol = hostapd_wpa_auth_set_eapol;
-	cb.get_eapol = hostapd_wpa_auth_get_eapol;
-	cb.get_psk = hostapd_wpa_auth_get_psk;
-	cb.get_msk = hostapd_wpa_auth_get_msk;
-	cb.set_key = hostapd_wpa_auth_set_key;
-	cb.get_seqnum = hostapd_wpa_auth_get_seqnum;
-	cb.send_eapol = hostapd_wpa_auth_send_eapol;
-	cb.for_each_sta = hostapd_wpa_auth_for_each_sta;
-	cb.for_each_auth = hostapd_wpa_auth_for_each_auth;
-	cb.send_ether = hostapd_wpa_auth_send_ether;
-#ifdef CONFIG_IEEE80211R_AP
-	cb.send_ft_action = hostapd_wpa_auth_send_ft_action;
-	cb.add_sta = hostapd_wpa_auth_add_sta;
-	cb.add_tspec = hostapd_wpa_auth_add_tspec;
-#endif /* CONFIG_IEEE80211R_AP */
-	hapd->wpa_auth = wpa_init(hapd->own_addr, &_conf, &cb);
+	hapd->wpa_auth = wpa_init(hapd->own_addr, &_conf, &cb, hapd);
 	if (hapd->wpa_auth == NULL) {
 		wpa_printf(MSG_ERROR, "WPA initialization failed.");
 		return -1;
diff --git a/src/ap/wpa_auth_i.h b/src/ap/wpa_auth_i.h
index 0c5a457..065a624 100644
--- a/src/ap/wpa_auth_i.h
+++ b/src/ap/wpa_auth_i.h
@@ -203,7 +203,8 @@
 	struct wpa_stsl_negotiation *stsl_negotiations;
 
 	struct wpa_auth_config conf;
-	struct wpa_auth_callbacks cb;
+	const struct wpa_auth_callbacks *cb;
+	void *cb_ctx;
 
 	u8 *wpa_ie;
 	size_t wpa_ie_len;