1214501Srpaulo/*
2214501Srpaulo * Control interface for shared AP commands
3346981Scy * Copyright (c) 2004-2019, Jouni Malinen <j@w1.fi>
4214501Srpaulo *
5252726Srpaulo * This software may be distributed under the terms of the BSD license.
6252726Srpaulo * See README for more details.
7214501Srpaulo */
8214501Srpaulo
9214501Srpaulo#include "utils/includes.h"
10214501Srpaulo
11214501Srpaulo#include "utils/common.h"
12252726Srpaulo#include "common/ieee802_11_defs.h"
13281806Srpaulo#include "common/sae.h"
14281806Srpaulo#include "eapol_auth/eapol_auth_sm.h"
15289549Srpaulo#include "fst/fst_ctrl_iface.h"
16214501Srpaulo#include "hostapd.h"
17214501Srpaulo#include "ieee802_1x.h"
18214501Srpaulo#include "wpa_auth.h"
19214501Srpaulo#include "ieee802_11.h"
20214501Srpaulo#include "sta_info.h"
21214501Srpaulo#include "wps_hostapd.h"
22252726Srpaulo#include "p2p_hostapd.h"
23214501Srpaulo#include "ctrl_iface_ap.h"
24252726Srpaulo#include "ap_drv_ops.h"
25337817Scy#include "mbo_ap.h"
26337817Scy#include "taxonomy.h"
27214501Srpaulo
28214501Srpaulo
29346981Scystatic size_t hostapd_write_ht_mcs_bitmask(char *buf, size_t buflen,
30346981Scy					   size_t curr_len, const u8 *mcs_set)
31346981Scy{
32346981Scy	int ret;
33346981Scy	size_t len = curr_len;
34346981Scy
35346981Scy	ret = os_snprintf(buf + len, buflen - len,
36346981Scy			  "ht_mcs_bitmask=");
37346981Scy	if (os_snprintf_error(buflen - len, ret))
38346981Scy		return len;
39346981Scy	len += ret;
40346981Scy
41346981Scy	/* 77 first bits (+ 3 reserved bits) */
42346981Scy	len += wpa_snprintf_hex(buf + len, buflen - len, mcs_set, 10);
43346981Scy
44346981Scy	ret = os_snprintf(buf + len, buflen - len, "\n");
45346981Scy	if (os_snprintf_error(buflen - len, ret))
46346981Scy		return curr_len;
47346981Scy	len += ret;
48346981Scy
49346981Scy	return len;
50346981Scy}
51346981Scy
52346981Scy
53281806Srpaulostatic int hostapd_get_sta_tx_rx(struct hostapd_data *hapd,
54281806Srpaulo				 struct sta_info *sta,
55281806Srpaulo				 char *buf, size_t buflen)
56281806Srpaulo{
57281806Srpaulo	struct hostap_sta_driver_data data;
58281806Srpaulo	int ret;
59346981Scy	int len = 0;
60281806Srpaulo
61281806Srpaulo	if (hostapd_drv_read_sta_data(hapd, &data, sta->addr) < 0)
62281806Srpaulo		return 0;
63281806Srpaulo
64281806Srpaulo	ret = os_snprintf(buf, buflen, "rx_packets=%lu\ntx_packets=%lu\n"
65346981Scy			  "rx_bytes=%llu\ntx_bytes=%llu\ninactive_msec=%lu\n"
66346981Scy			  "signal=%d\n",
67281806Srpaulo			  data.rx_packets, data.tx_packets,
68346981Scy			  data.rx_bytes, data.tx_bytes, data.inactive_msec,
69346981Scy			  data.signal);
70281806Srpaulo	if (os_snprintf_error(buflen, ret))
71281806Srpaulo		return 0;
72346981Scy	len += ret;
73346981Scy
74346981Scy	ret = os_snprintf(buf + len, buflen - len, "rx_rate_info=%lu",
75346981Scy			  data.current_rx_rate);
76346981Scy	if (os_snprintf_error(buflen - len, ret))
77346981Scy		return len;
78346981Scy	len += ret;
79346981Scy	if (data.flags & STA_DRV_DATA_RX_MCS) {
80346981Scy		ret = os_snprintf(buf + len, buflen - len, " mcs %u",
81346981Scy				  data.rx_mcs);
82346981Scy		if (!os_snprintf_error(buflen - len, ret))
83346981Scy			len += ret;
84346981Scy	}
85346981Scy	if (data.flags & STA_DRV_DATA_RX_VHT_MCS) {
86346981Scy		ret = os_snprintf(buf + len, buflen - len, " vhtmcs %u",
87346981Scy				  data.rx_vhtmcs);
88346981Scy		if (!os_snprintf_error(buflen - len, ret))
89346981Scy			len += ret;
90346981Scy	}
91346981Scy	if (data.flags & STA_DRV_DATA_RX_VHT_NSS) {
92346981Scy		ret = os_snprintf(buf + len, buflen - len, " vhtnss %u",
93346981Scy				  data.rx_vht_nss);
94346981Scy		if (!os_snprintf_error(buflen - len, ret))
95346981Scy			len += ret;
96346981Scy	}
97346981Scy	if (data.flags & STA_DRV_DATA_RX_SHORT_GI) {
98346981Scy		ret = os_snprintf(buf + len, buflen - len, " shortGI");
99346981Scy		if (!os_snprintf_error(buflen - len, ret))
100346981Scy			len += ret;
101346981Scy	}
102346981Scy	ret = os_snprintf(buf + len, buflen - len, "\n");
103346981Scy	if (!os_snprintf_error(buflen - len, ret))
104346981Scy		len += ret;
105346981Scy
106346981Scy	ret = os_snprintf(buf + len, buflen - len, "tx_rate_info=%lu",
107346981Scy			  data.current_tx_rate);
108346981Scy	if (os_snprintf_error(buflen - len, ret))
109346981Scy		return len;
110346981Scy	len += ret;
111346981Scy	if (data.flags & STA_DRV_DATA_TX_MCS) {
112346981Scy		ret = os_snprintf(buf + len, buflen - len, " mcs %u",
113346981Scy				  data.tx_mcs);
114346981Scy		if (!os_snprintf_error(buflen - len, ret))
115346981Scy			len += ret;
116346981Scy	}
117346981Scy	if (data.flags & STA_DRV_DATA_TX_VHT_MCS) {
118346981Scy		ret = os_snprintf(buf + len, buflen - len, " vhtmcs %u",
119346981Scy				  data.tx_vhtmcs);
120346981Scy		if (!os_snprintf_error(buflen - len, ret))
121346981Scy			len += ret;
122346981Scy	}
123346981Scy	if (data.flags & STA_DRV_DATA_TX_VHT_NSS) {
124346981Scy		ret = os_snprintf(buf + len, buflen - len, " vhtnss %u",
125346981Scy				  data.tx_vht_nss);
126346981Scy		if (!os_snprintf_error(buflen - len, ret))
127346981Scy			len += ret;
128346981Scy	}
129346981Scy	if (data.flags & STA_DRV_DATA_TX_SHORT_GI) {
130346981Scy		ret = os_snprintf(buf + len, buflen - len, " shortGI");
131346981Scy		if (!os_snprintf_error(buflen - len, ret))
132346981Scy			len += ret;
133346981Scy	}
134346981Scy	ret = os_snprintf(buf + len, buflen - len, "\n");
135346981Scy	if (!os_snprintf_error(buflen - len, ret))
136346981Scy		len += ret;
137346981Scy
138346981Scy	if ((sta->flags & WLAN_STA_VHT) && sta->vht_capabilities) {
139346981Scy		ret = os_snprintf(buf + len, buflen - len,
140346981Scy				  "rx_vht_mcs_map=%04x\n"
141346981Scy				  "tx_vht_mcs_map=%04x\n",
142346981Scy				  le_to_host16(sta->vht_capabilities->
143346981Scy					       vht_supported_mcs_set.rx_map),
144346981Scy				  le_to_host16(sta->vht_capabilities->
145346981Scy					       vht_supported_mcs_set.tx_map));
146346981Scy		if (!os_snprintf_error(buflen - len, ret))
147346981Scy			len += ret;
148346981Scy	}
149346981Scy
150346981Scy	if ((sta->flags & WLAN_STA_HT) && sta->ht_capabilities) {
151346981Scy		len = hostapd_write_ht_mcs_bitmask(buf, buflen, len,
152346981Scy						   sta->ht_capabilities->
153346981Scy						   supported_mcs_set);
154346981Scy	}
155346981Scy
156346981Scy	if (data.flags & STA_DRV_DATA_LAST_ACK_RSSI) {
157346981Scy		ret = os_snprintf(buf + len, buflen - len,
158346981Scy				  "last_ack_signal=%d\n", data.last_ack_rssi);
159346981Scy		if (!os_snprintf_error(buflen - len, ret))
160346981Scy			len += ret;
161346981Scy	}
162346981Scy
163346981Scy	return len;
164281806Srpaulo}
165281806Srpaulo
166281806Srpaulo
167252726Srpaulostatic int hostapd_get_sta_conn_time(struct sta_info *sta,
168252726Srpaulo				     char *buf, size_t buflen)
169252726Srpaulo{
170281806Srpaulo	struct os_reltime age;
171281806Srpaulo	int ret;
172252726Srpaulo
173252726Srpaulo	if (!sta->connected_time.sec)
174252726Srpaulo		return 0;
175252726Srpaulo
176281806Srpaulo	os_reltime_age(&sta->connected_time, &age);
177252726Srpaulo
178281806Srpaulo	ret = os_snprintf(buf, buflen, "connected_time=%u\n",
179252726Srpaulo			  (unsigned int) age.sec);
180281806Srpaulo	if (os_snprintf_error(buflen, ret))
181281806Srpaulo		return 0;
182281806Srpaulo	return ret;
183281806Srpaulo}
184252726Srpaulo
185281806Srpaulo
186281806Srpaulostatic const char * timeout_next_str(int val)
187281806Srpaulo{
188281806Srpaulo	switch (val) {
189281806Srpaulo	case STA_NULLFUNC:
190281806Srpaulo		return "NULLFUNC POLL";
191281806Srpaulo	case STA_DISASSOC:
192281806Srpaulo		return "DISASSOC";
193281806Srpaulo	case STA_DEAUTH:
194281806Srpaulo		return "DEAUTH";
195281806Srpaulo	case STA_REMOVE:
196281806Srpaulo		return "REMOVE";
197281806Srpaulo	case STA_DISASSOC_FROM_CLI:
198281806Srpaulo		return "DISASSOC_FROM_CLI";
199281806Srpaulo	}
200281806Srpaulo
201281806Srpaulo	return "?";
202252726Srpaulo}
203252726Srpaulo
204252726Srpaulo
205214501Srpaulostatic int hostapd_ctrl_iface_sta_mib(struct hostapd_data *hapd,
206214501Srpaulo				      struct sta_info *sta,
207214501Srpaulo				      char *buf, size_t buflen)
208214501Srpaulo{
209281806Srpaulo	int len, res, ret, i;
210346981Scy	const char *keyid;
211214501Srpaulo
212281806Srpaulo	if (!sta)
213281806Srpaulo		return 0;
214214501Srpaulo
215214501Srpaulo	len = 0;
216281806Srpaulo	ret = os_snprintf(buf + len, buflen - len, MACSTR "\nflags=",
217214501Srpaulo			  MAC2STR(sta->addr));
218281806Srpaulo	if (os_snprintf_error(buflen - len, ret))
219214501Srpaulo		return len;
220214501Srpaulo	len += ret;
221214501Srpaulo
222281806Srpaulo	ret = ap_sta_flags_txt(sta->flags, buf + len, buflen - len);
223281806Srpaulo	if (ret < 0)
224281806Srpaulo		return len;
225281806Srpaulo	len += ret;
226281806Srpaulo
227281806Srpaulo	ret = os_snprintf(buf + len, buflen - len, "\naid=%d\ncapability=0x%x\n"
228281806Srpaulo			  "listen_interval=%d\nsupported_rates=",
229281806Srpaulo			  sta->aid, sta->capability, sta->listen_interval);
230281806Srpaulo	if (os_snprintf_error(buflen - len, ret))
231281806Srpaulo		return len;
232281806Srpaulo	len += ret;
233281806Srpaulo
234281806Srpaulo	for (i = 0; i < sta->supported_rates_len; i++) {
235281806Srpaulo		ret = os_snprintf(buf + len, buflen - len, "%02x%s",
236281806Srpaulo				  sta->supported_rates[i],
237281806Srpaulo				  i + 1 < sta->supported_rates_len ? " " : "");
238281806Srpaulo		if (os_snprintf_error(buflen - len, ret))
239281806Srpaulo			return len;
240281806Srpaulo		len += ret;
241281806Srpaulo	}
242281806Srpaulo
243281806Srpaulo	ret = os_snprintf(buf + len, buflen - len, "\ntimeout_next=%s\n",
244281806Srpaulo			  timeout_next_str(sta->timeout_next));
245281806Srpaulo	if (os_snprintf_error(buflen - len, ret))
246281806Srpaulo		return len;
247281806Srpaulo	len += ret;
248281806Srpaulo
249214501Srpaulo	res = ieee802_11_get_mib_sta(hapd, sta, buf + len, buflen - len);
250214501Srpaulo	if (res >= 0)
251214501Srpaulo		len += res;
252214501Srpaulo	res = wpa_get_mib_sta(sta->wpa_sm, buf + len, buflen - len);
253214501Srpaulo	if (res >= 0)
254214501Srpaulo		len += res;
255214501Srpaulo	res = ieee802_1x_get_mib_sta(hapd, sta, buf + len, buflen - len);
256214501Srpaulo	if (res >= 0)
257214501Srpaulo		len += res;
258214501Srpaulo	res = hostapd_wps_get_mib_sta(hapd, sta->addr, buf + len,
259214501Srpaulo				      buflen - len);
260214501Srpaulo	if (res >= 0)
261214501Srpaulo		len += res;
262252726Srpaulo	res = hostapd_p2p_get_mib_sta(hapd, sta, buf + len, buflen - len);
263252726Srpaulo	if (res >= 0)
264252726Srpaulo		len += res;
265214501Srpaulo
266281806Srpaulo	len += hostapd_get_sta_tx_rx(hapd, sta, buf + len, buflen - len);
267281806Srpaulo	len += hostapd_get_sta_conn_time(sta, buf + len, buflen - len);
268252726Srpaulo
269281806Srpaulo#ifdef CONFIG_SAE
270281806Srpaulo	if (sta->sae && sta->sae->state == SAE_ACCEPTED) {
271281806Srpaulo		res = os_snprintf(buf + len, buflen - len, "sae_group=%d\n",
272281806Srpaulo				  sta->sae->group);
273281806Srpaulo		if (!os_snprintf_error(buflen - len, res))
274281806Srpaulo			len += res;
275281806Srpaulo	}
276281806Srpaulo#endif /* CONFIG_SAE */
277281806Srpaulo
278289549Srpaulo	if (sta->vlan_id > 0) {
279289549Srpaulo		res = os_snprintf(buf + len, buflen - len, "vlan_id=%d\n",
280289549Srpaulo				  sta->vlan_id);
281289549Srpaulo		if (!os_snprintf_error(buflen - len, res))
282289549Srpaulo			len += res;
283289549Srpaulo	}
284289549Srpaulo
285337817Scy	res = mbo_ap_get_info(sta, buf + len, buflen - len);
286337817Scy	if (res >= 0)
287337817Scy		len += res;
288337817Scy
289337817Scy	if (sta->supp_op_classes &&
290337817Scy	    buflen - len > (unsigned) (17 + 2 * sta->supp_op_classes[0])) {
291337817Scy		len += os_snprintf(buf + len, buflen - len, "supp_op_classes=");
292337817Scy		len += wpa_snprintf_hex(buf + len, buflen - len,
293337817Scy					sta->supp_op_classes + 1,
294337817Scy					sta->supp_op_classes[0]);
295337817Scy		len += os_snprintf(buf + len, buflen - len, "\n");
296337817Scy	}
297337817Scy
298346981Scy	if (sta->power_capab) {
299346981Scy		ret = os_snprintf(buf + len, buflen - len,
300346981Scy				  "min_txpower=%d\n"
301346981Scy				  "max_txpower=%d\n",
302346981Scy				  sta->min_tx_power, sta->max_tx_power);
303346981Scy		if (!os_snprintf_error(buflen - len, ret))
304346981Scy			len += ret;
305346981Scy	}
306346981Scy
307346981Scy#ifdef CONFIG_IEEE80211AC
308346981Scy	if ((sta->flags & WLAN_STA_VHT) && sta->vht_capabilities) {
309346981Scy		res = os_snprintf(buf + len, buflen - len,
310346981Scy				  "vht_caps_info=0x%08x\n",
311346981Scy				  le_to_host32(sta->vht_capabilities->
312346981Scy					       vht_capabilities_info));
313346981Scy		if (!os_snprintf_error(buflen - len, res))
314346981Scy			len += res;
315346981Scy	}
316346981Scy#endif /* CONFIG_IEEE80211AC */
317346981Scy
318346981Scy#ifdef CONFIG_IEEE80211N
319346981Scy	if ((sta->flags & WLAN_STA_HT) && sta->ht_capabilities) {
320346981Scy		res = os_snprintf(buf + len, buflen - len,
321346981Scy				  "ht_caps_info=0x%04x\n",
322346981Scy				  le_to_host16(sta->ht_capabilities->
323346981Scy					       ht_capabilities_info));
324346981Scy		if (!os_snprintf_error(buflen - len, res))
325346981Scy			len += res;
326346981Scy	}
327346981Scy#endif /* CONFIG_IEEE80211N */
328346981Scy
329346981Scy	if (sta->ext_capability &&
330346981Scy	    buflen - len > (unsigned) (11 + 2 * sta->ext_capability[0])) {
331346981Scy		len += os_snprintf(buf + len, buflen - len, "ext_capab=");
332346981Scy		len += wpa_snprintf_hex(buf + len, buflen - len,
333346981Scy					sta->ext_capability + 1,
334346981Scy					sta->ext_capability[0]);
335346981Scy		len += os_snprintf(buf + len, buflen - len, "\n");
336346981Scy	}
337346981Scy
338346981Scy	if (sta->flags & WLAN_STA_WDS && sta->ifname_wds) {
339346981Scy		ret = os_snprintf(buf + len, buflen - len,
340346981Scy				  "wds_sta_ifname=%s\n", sta->ifname_wds);
341346981Scy		if (!os_snprintf_error(buflen - len, ret))
342346981Scy			len += ret;
343346981Scy	}
344346981Scy
345346981Scy	keyid = ap_sta_wpa_get_keyid(hapd, sta);
346346981Scy	if (keyid) {
347346981Scy		ret = os_snprintf(buf + len, buflen - len, "keyid=%s\n", keyid);
348346981Scy		if (!os_snprintf_error(buflen - len, ret))
349346981Scy			len += ret;
350346981Scy	}
351346981Scy
352214501Srpaulo	return len;
353214501Srpaulo}
354214501Srpaulo
355214501Srpaulo
356214501Srpauloint hostapd_ctrl_iface_sta_first(struct hostapd_data *hapd,
357214501Srpaulo				 char *buf, size_t buflen)
358214501Srpaulo{
359214501Srpaulo	return hostapd_ctrl_iface_sta_mib(hapd, hapd->sta_list, buf, buflen);
360214501Srpaulo}
361214501Srpaulo
362214501Srpaulo
363214501Srpauloint hostapd_ctrl_iface_sta(struct hostapd_data *hapd, const char *txtaddr,
364214501Srpaulo			   char *buf, size_t buflen)
365214501Srpaulo{
366214501Srpaulo	u8 addr[ETH_ALEN];
367214501Srpaulo	int ret;
368281806Srpaulo	const char *pos;
369281806Srpaulo	struct sta_info *sta;
370214501Srpaulo
371214501Srpaulo	if (hwaddr_aton(txtaddr, addr)) {
372214501Srpaulo		ret = os_snprintf(buf, buflen, "FAIL\n");
373281806Srpaulo		if (os_snprintf_error(buflen, ret))
374214501Srpaulo			return 0;
375214501Srpaulo		return ret;
376214501Srpaulo	}
377281806Srpaulo
378281806Srpaulo	sta = ap_get_sta(hapd, addr);
379281806Srpaulo	if (sta == NULL)
380281806Srpaulo		return -1;
381281806Srpaulo
382281806Srpaulo	pos = os_strchr(txtaddr, ' ');
383281806Srpaulo	if (pos) {
384281806Srpaulo		pos++;
385281806Srpaulo
386281806Srpaulo#ifdef HOSTAPD_DUMP_STATE
387281806Srpaulo		if (os_strcmp(pos, "eapol") == 0) {
388281806Srpaulo			if (sta->eapol_sm == NULL)
389281806Srpaulo				return -1;
390281806Srpaulo			return eapol_auth_dump_state(sta->eapol_sm, buf,
391281806Srpaulo						     buflen);
392281806Srpaulo		}
393281806Srpaulo#endif /* HOSTAPD_DUMP_STATE */
394281806Srpaulo
395281806Srpaulo		return -1;
396281806Srpaulo	}
397281806Srpaulo
398289549Srpaulo	ret = hostapd_ctrl_iface_sta_mib(hapd, sta, buf, buflen);
399289549Srpaulo	ret += fst_ctrl_iface_mb_info(addr, buf + ret, buflen - ret);
400289549Srpaulo
401289549Srpaulo	return ret;
402214501Srpaulo}
403214501Srpaulo
404214501Srpaulo
405214501Srpauloint hostapd_ctrl_iface_sta_next(struct hostapd_data *hapd, const char *txtaddr,
406214501Srpaulo				char *buf, size_t buflen)
407214501Srpaulo{
408214501Srpaulo	u8 addr[ETH_ALEN];
409214501Srpaulo	struct sta_info *sta;
410214501Srpaulo	int ret;
411214501Srpaulo
412214501Srpaulo	if (hwaddr_aton(txtaddr, addr) ||
413214501Srpaulo	    (sta = ap_get_sta(hapd, addr)) == NULL) {
414214501Srpaulo		ret = os_snprintf(buf, buflen, "FAIL\n");
415281806Srpaulo		if (os_snprintf_error(buflen, ret))
416214501Srpaulo			return 0;
417214501Srpaulo		return ret;
418281806Srpaulo	}
419281806Srpaulo
420281806Srpaulo	if (!sta->next)
421281806Srpaulo		return 0;
422281806Srpaulo
423214501Srpaulo	return hostapd_ctrl_iface_sta_mib(hapd, sta->next, buf, buflen);
424214501Srpaulo}
425252726Srpaulo
426252726Srpaulo
427252726Srpaulo#ifdef CONFIG_P2P_MANAGER
428252726Srpaulostatic int p2p_manager_disconnect(struct hostapd_data *hapd, u16 stype,
429252726Srpaulo				  u8 minor_reason_code, const u8 *addr)
430252726Srpaulo{
431252726Srpaulo	struct ieee80211_mgmt *mgmt;
432252726Srpaulo	int ret;
433252726Srpaulo	u8 *pos;
434252726Srpaulo
435337817Scy	if (!hapd->drv_priv || !hapd->driver->send_frame)
436252726Srpaulo		return -1;
437252726Srpaulo
438252726Srpaulo	mgmt = os_zalloc(sizeof(*mgmt) + 100);
439252726Srpaulo	if (mgmt == NULL)
440252726Srpaulo		return -1;
441252726Srpaulo
442281806Srpaulo	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, stype);
443252726Srpaulo	wpa_dbg(hapd->msg_ctx, MSG_DEBUG, "P2P: Disconnect STA " MACSTR
444281806Srpaulo		" with minor reason code %u (stype=%u (%s))",
445281806Srpaulo		MAC2STR(addr), minor_reason_code, stype,
446337817Scy		fc2str(le_to_host16(mgmt->frame_control)));
447252726Srpaulo
448252726Srpaulo	os_memcpy(mgmt->da, addr, ETH_ALEN);
449252726Srpaulo	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
450252726Srpaulo	os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN);
451252726Srpaulo	if (stype == WLAN_FC_STYPE_DEAUTH) {
452252726Srpaulo		mgmt->u.deauth.reason_code =
453252726Srpaulo			host_to_le16(WLAN_REASON_PREV_AUTH_NOT_VALID);
454346981Scy		pos = mgmt->u.deauth.variable;
455252726Srpaulo	} else {
456252726Srpaulo		mgmt->u.disassoc.reason_code =
457252726Srpaulo			host_to_le16(WLAN_REASON_PREV_AUTH_NOT_VALID);
458346981Scy		pos = mgmt->u.disassoc.variable;
459252726Srpaulo	}
460252726Srpaulo
461252726Srpaulo	*pos++ = WLAN_EID_VENDOR_SPECIFIC;
462252726Srpaulo	*pos++ = 4 + 3 + 1;
463281806Srpaulo	WPA_PUT_BE32(pos, P2P_IE_VENDOR_TYPE);
464281806Srpaulo	pos += 4;
465252726Srpaulo
466252726Srpaulo	*pos++ = P2P_ATTR_MINOR_REASON_CODE;
467252726Srpaulo	WPA_PUT_LE16(pos, 1);
468252726Srpaulo	pos += 2;
469252726Srpaulo	*pos++ = minor_reason_code;
470252726Srpaulo
471252726Srpaulo	ret = hapd->driver->send_frame(hapd->drv_priv, (u8 *) mgmt,
472252726Srpaulo				       pos - (u8 *) mgmt, 1);
473252726Srpaulo	os_free(mgmt);
474252726Srpaulo
475252726Srpaulo	return ret < 0 ? -1 : 0;
476252726Srpaulo}
477252726Srpaulo#endif /* CONFIG_P2P_MANAGER */
478252726Srpaulo
479252726Srpaulo
480252726Srpauloint hostapd_ctrl_iface_deauthenticate(struct hostapd_data *hapd,
481252726Srpaulo				      const char *txtaddr)
482252726Srpaulo{
483252726Srpaulo	u8 addr[ETH_ALEN];
484252726Srpaulo	struct sta_info *sta;
485252726Srpaulo	const char *pos;
486281806Srpaulo	u16 reason = WLAN_REASON_PREV_AUTH_NOT_VALID;
487252726Srpaulo
488252726Srpaulo	wpa_dbg(hapd->msg_ctx, MSG_DEBUG, "CTRL_IFACE DEAUTHENTICATE %s",
489252726Srpaulo		txtaddr);
490252726Srpaulo
491252726Srpaulo	if (hwaddr_aton(txtaddr, addr))
492252726Srpaulo		return -1;
493252726Srpaulo
494281806Srpaulo	pos = os_strstr(txtaddr, " reason=");
495281806Srpaulo	if (pos)
496281806Srpaulo		reason = atoi(pos + 8);
497281806Srpaulo
498252726Srpaulo	pos = os_strstr(txtaddr, " test=");
499252726Srpaulo	if (pos) {
500252726Srpaulo		struct ieee80211_mgmt mgmt;
501252726Srpaulo		int encrypt;
502337817Scy		if (!hapd->drv_priv || !hapd->driver->send_frame)
503252726Srpaulo			return -1;
504252726Srpaulo		pos += 6;
505252726Srpaulo		encrypt = atoi(pos);
506252726Srpaulo		os_memset(&mgmt, 0, sizeof(mgmt));
507252726Srpaulo		mgmt.frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
508252726Srpaulo						  WLAN_FC_STYPE_DEAUTH);
509252726Srpaulo		os_memcpy(mgmt.da, addr, ETH_ALEN);
510252726Srpaulo		os_memcpy(mgmt.sa, hapd->own_addr, ETH_ALEN);
511252726Srpaulo		os_memcpy(mgmt.bssid, hapd->own_addr, ETH_ALEN);
512281806Srpaulo		mgmt.u.deauth.reason_code = host_to_le16(reason);
513252726Srpaulo		if (hapd->driver->send_frame(hapd->drv_priv, (u8 *) &mgmt,
514252726Srpaulo					     IEEE80211_HDRLEN +
515252726Srpaulo					     sizeof(mgmt.u.deauth),
516252726Srpaulo					     encrypt) < 0)
517252726Srpaulo			return -1;
518252726Srpaulo		return 0;
519252726Srpaulo	}
520252726Srpaulo
521252726Srpaulo#ifdef CONFIG_P2P_MANAGER
522252726Srpaulo	pos = os_strstr(txtaddr, " p2p=");
523252726Srpaulo	if (pos) {
524252726Srpaulo		return p2p_manager_disconnect(hapd, WLAN_FC_STYPE_DEAUTH,
525252726Srpaulo					      atoi(pos + 5), addr);
526252726Srpaulo	}
527252726Srpaulo#endif /* CONFIG_P2P_MANAGER */
528252726Srpaulo
529337817Scy	if (os_strstr(txtaddr, " tx=0"))
530337817Scy		hostapd_drv_sta_remove(hapd, addr);
531337817Scy	else
532337817Scy		hostapd_drv_sta_deauth(hapd, addr, reason);
533252726Srpaulo	sta = ap_get_sta(hapd, addr);
534252726Srpaulo	if (sta)
535281806Srpaulo		ap_sta_deauthenticate(hapd, sta, reason);
536252726Srpaulo	else if (addr[0] == 0xff)
537252726Srpaulo		hostapd_free_stas(hapd);
538252726Srpaulo
539252726Srpaulo	return 0;
540252726Srpaulo}
541252726Srpaulo
542252726Srpaulo
543252726Srpauloint hostapd_ctrl_iface_disassociate(struct hostapd_data *hapd,
544252726Srpaulo				    const char *txtaddr)
545252726Srpaulo{
546252726Srpaulo	u8 addr[ETH_ALEN];
547252726Srpaulo	struct sta_info *sta;
548252726Srpaulo	const char *pos;
549281806Srpaulo	u16 reason = WLAN_REASON_PREV_AUTH_NOT_VALID;
550252726Srpaulo
551252726Srpaulo	wpa_dbg(hapd->msg_ctx, MSG_DEBUG, "CTRL_IFACE DISASSOCIATE %s",
552252726Srpaulo		txtaddr);
553252726Srpaulo
554252726Srpaulo	if (hwaddr_aton(txtaddr, addr))
555252726Srpaulo		return -1;
556252726Srpaulo
557281806Srpaulo	pos = os_strstr(txtaddr, " reason=");
558281806Srpaulo	if (pos)
559281806Srpaulo		reason = atoi(pos + 8);
560281806Srpaulo
561252726Srpaulo	pos = os_strstr(txtaddr, " test=");
562252726Srpaulo	if (pos) {
563252726Srpaulo		struct ieee80211_mgmt mgmt;
564252726Srpaulo		int encrypt;
565337817Scy		if (!hapd->drv_priv || !hapd->driver->send_frame)
566252726Srpaulo			return -1;
567252726Srpaulo		pos += 6;
568252726Srpaulo		encrypt = atoi(pos);
569252726Srpaulo		os_memset(&mgmt, 0, sizeof(mgmt));
570252726Srpaulo		mgmt.frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
571252726Srpaulo						  WLAN_FC_STYPE_DISASSOC);
572252726Srpaulo		os_memcpy(mgmt.da, addr, ETH_ALEN);
573252726Srpaulo		os_memcpy(mgmt.sa, hapd->own_addr, ETH_ALEN);
574252726Srpaulo		os_memcpy(mgmt.bssid, hapd->own_addr, ETH_ALEN);
575281806Srpaulo		mgmt.u.disassoc.reason_code = host_to_le16(reason);
576252726Srpaulo		if (hapd->driver->send_frame(hapd->drv_priv, (u8 *) &mgmt,
577252726Srpaulo					     IEEE80211_HDRLEN +
578252726Srpaulo					     sizeof(mgmt.u.deauth),
579252726Srpaulo					     encrypt) < 0)
580252726Srpaulo			return -1;
581252726Srpaulo		return 0;
582252726Srpaulo	}
583252726Srpaulo
584252726Srpaulo#ifdef CONFIG_P2P_MANAGER
585252726Srpaulo	pos = os_strstr(txtaddr, " p2p=");
586252726Srpaulo	if (pos) {
587252726Srpaulo		return p2p_manager_disconnect(hapd, WLAN_FC_STYPE_DISASSOC,
588252726Srpaulo					      atoi(pos + 5), addr);
589252726Srpaulo	}
590252726Srpaulo#endif /* CONFIG_P2P_MANAGER */
591252726Srpaulo
592337817Scy	if (os_strstr(txtaddr, " tx=0"))
593337817Scy		hostapd_drv_sta_remove(hapd, addr);
594337817Scy	else
595337817Scy		hostapd_drv_sta_disassoc(hapd, addr, reason);
596252726Srpaulo	sta = ap_get_sta(hapd, addr);
597252726Srpaulo	if (sta)
598281806Srpaulo		ap_sta_disassociate(hapd, sta, reason);
599252726Srpaulo	else if (addr[0] == 0xff)
600252726Srpaulo		hostapd_free_stas(hapd);
601252726Srpaulo
602252726Srpaulo	return 0;
603252726Srpaulo}
604281806Srpaulo
605281806Srpaulo
606337817Scy#ifdef CONFIG_TAXONOMY
607337817Scyint hostapd_ctrl_iface_signature(struct hostapd_data *hapd,
608337817Scy				 const char *txtaddr,
609337817Scy				 char *buf, size_t buflen)
610337817Scy{
611337817Scy	u8 addr[ETH_ALEN];
612337817Scy	struct sta_info *sta;
613337817Scy
614337817Scy	wpa_dbg(hapd->msg_ctx, MSG_DEBUG, "CTRL_IFACE SIGNATURE %s", txtaddr);
615337817Scy
616337817Scy	if (hwaddr_aton(txtaddr, addr))
617337817Scy		return -1;
618337817Scy
619337817Scy	sta = ap_get_sta(hapd, addr);
620337817Scy	if (!sta)
621337817Scy		return -1;
622337817Scy
623337817Scy	return retrieve_sta_taxonomy(hapd, sta, buf, buflen);
624337817Scy}
625337817Scy#endif /* CONFIG_TAXONOMY */
626337817Scy
627337817Scy
628337817Scyint hostapd_ctrl_iface_poll_sta(struct hostapd_data *hapd,
629337817Scy				const char *txtaddr)
630337817Scy{
631337817Scy	u8 addr[ETH_ALEN];
632337817Scy	struct sta_info *sta;
633337817Scy
634337817Scy	wpa_dbg(hapd->msg_ctx, MSG_DEBUG, "CTRL_IFACE POLL_STA %s", txtaddr);
635337817Scy
636337817Scy	if (hwaddr_aton(txtaddr, addr))
637337817Scy		return -1;
638337817Scy
639337817Scy	sta = ap_get_sta(hapd, addr);
640337817Scy	if (!sta)
641337817Scy		return -1;
642337817Scy
643337817Scy	hostapd_drv_poll_client(hapd, hapd->own_addr, addr,
644337817Scy				sta->flags & WLAN_STA_WMM);
645337817Scy	return 0;
646337817Scy}
647337817Scy
648337817Scy
649281806Srpauloint hostapd_ctrl_iface_status(struct hostapd_data *hapd, char *buf,
650281806Srpaulo			      size_t buflen)
651281806Srpaulo{
652281806Srpaulo	struct hostapd_iface *iface = hapd->iface;
653346981Scy	struct hostapd_hw_modes *mode = iface->current_mode;
654346981Scy	int len = 0, ret, j;
655281806Srpaulo	size_t i;
656281806Srpaulo
657281806Srpaulo	ret = os_snprintf(buf + len, buflen - len,
658281806Srpaulo			  "state=%s\n"
659281806Srpaulo			  "phy=%s\n"
660281806Srpaulo			  "freq=%d\n"
661281806Srpaulo			  "num_sta_non_erp=%d\n"
662281806Srpaulo			  "num_sta_no_short_slot_time=%d\n"
663281806Srpaulo			  "num_sta_no_short_preamble=%d\n"
664281806Srpaulo			  "olbc=%d\n"
665281806Srpaulo			  "num_sta_ht_no_gf=%d\n"
666281806Srpaulo			  "num_sta_no_ht=%d\n"
667281806Srpaulo			  "num_sta_ht_20_mhz=%d\n"
668281806Srpaulo			  "num_sta_ht40_intolerant=%d\n"
669281806Srpaulo			  "olbc_ht=%d\n"
670281806Srpaulo			  "ht_op_mode=0x%x\n",
671281806Srpaulo			  hostapd_state_text(iface->state),
672281806Srpaulo			  iface->phy,
673281806Srpaulo			  iface->freq,
674281806Srpaulo			  iface->num_sta_non_erp,
675281806Srpaulo			  iface->num_sta_no_short_slot_time,
676281806Srpaulo			  iface->num_sta_no_short_preamble,
677281806Srpaulo			  iface->olbc,
678281806Srpaulo			  iface->num_sta_ht_no_gf,
679281806Srpaulo			  iface->num_sta_no_ht,
680281806Srpaulo			  iface->num_sta_ht_20mhz,
681281806Srpaulo			  iface->num_sta_ht40_intolerant,
682281806Srpaulo			  iface->olbc_ht,
683281806Srpaulo			  iface->ht_op_mode);
684281806Srpaulo	if (os_snprintf_error(buflen - len, ret))
685281806Srpaulo		return len;
686281806Srpaulo	len += ret;
687281806Srpaulo
688281806Srpaulo	if (!iface->cac_started || !iface->dfs_cac_ms) {
689281806Srpaulo		ret = os_snprintf(buf + len, buflen - len,
690281806Srpaulo				  "cac_time_seconds=%d\n"
691281806Srpaulo				  "cac_time_left_seconds=N/A\n",
692281806Srpaulo				  iface->dfs_cac_ms / 1000);
693281806Srpaulo	} else {
694281806Srpaulo		/* CAC started and CAC time set - calculate remaining time */
695281806Srpaulo		struct os_reltime now;
696281806Srpaulo		unsigned int left_time;
697281806Srpaulo
698281806Srpaulo		os_reltime_age(&iface->dfs_cac_start, &now);
699281806Srpaulo		left_time = iface->dfs_cac_ms / 1000 - now.sec;
700281806Srpaulo		ret = os_snprintf(buf + len, buflen - len,
701281806Srpaulo				  "cac_time_seconds=%u\n"
702281806Srpaulo				  "cac_time_left_seconds=%u\n",
703281806Srpaulo				  iface->dfs_cac_ms / 1000,
704281806Srpaulo				  left_time);
705281806Srpaulo	}
706281806Srpaulo	if (os_snprintf_error(buflen - len, ret))
707281806Srpaulo		return len;
708281806Srpaulo	len += ret;
709281806Srpaulo
710281806Srpaulo	ret = os_snprintf(buf + len, buflen - len,
711281806Srpaulo			  "channel=%u\n"
712281806Srpaulo			  "secondary_channel=%d\n"
713281806Srpaulo			  "ieee80211n=%d\n"
714346981Scy			  "ieee80211ac=%d\n"
715351611Scy			  "ieee80211ax=%d\n"
716346981Scy			  "beacon_int=%u\n"
717346981Scy			  "dtim_period=%d\n",
718281806Srpaulo			  iface->conf->channel,
719337817Scy			  iface->conf->ieee80211n && !hapd->conf->disable_11n ?
720337817Scy			  iface->conf->secondary_channel : 0,
721337817Scy			  iface->conf->ieee80211n && !hapd->conf->disable_11n,
722337817Scy			  iface->conf->ieee80211ac &&
723346981Scy			  !hapd->conf->disable_11ac,
724351611Scy			  iface->conf->ieee80211ax,
725346981Scy			  iface->conf->beacon_int,
726346981Scy			  hapd->conf->dtim_period);
727281806Srpaulo	if (os_snprintf_error(buflen - len, ret))
728281806Srpaulo		return len;
729281806Srpaulo	len += ret;
730337817Scy	if (iface->conf->ieee80211ac && !hapd->conf->disable_11ac) {
731337817Scy		ret = os_snprintf(buf + len, buflen - len,
732337817Scy				  "vht_oper_chwidth=%d\n"
733337817Scy				  "vht_oper_centr_freq_seg0_idx=%d\n"
734346981Scy				  "vht_oper_centr_freq_seg1_idx=%d\n"
735346981Scy				  "vht_caps_info=%08x\n",
736337817Scy				  iface->conf->vht_oper_chwidth,
737337817Scy				  iface->conf->vht_oper_centr_freq_seg0_idx,
738346981Scy				  iface->conf->vht_oper_centr_freq_seg1_idx,
739346981Scy				  iface->conf->vht_capab);
740337817Scy		if (os_snprintf_error(buflen - len, ret))
741337817Scy			return len;
742337817Scy		len += ret;
743337817Scy	}
744281806Srpaulo
745346981Scy	if (iface->conf->ieee80211ac && !hapd->conf->disable_11ac && mode) {
746346981Scy		u16 rxmap = WPA_GET_LE16(&mode->vht_mcs_set[0]);
747346981Scy		u16 txmap = WPA_GET_LE16(&mode->vht_mcs_set[4]);
748346981Scy
749346981Scy		ret = os_snprintf(buf + len, buflen - len,
750346981Scy				  "rx_vht_mcs_map=%04x\n"
751346981Scy				  "tx_vht_mcs_map=%04x\n",
752346981Scy				  rxmap, txmap);
753346981Scy		if (os_snprintf_error(buflen - len, ret))
754346981Scy			return len;
755346981Scy		len += ret;
756346981Scy	}
757346981Scy
758346981Scy	if (iface->conf->ieee80211n && !hapd->conf->disable_11n) {
759346981Scy		ret = os_snprintf(buf + len, buflen - len,
760346981Scy				  "ht_caps_info=%04x\n",
761346981Scy				  hapd->iconf->ht_capab);
762346981Scy		if (os_snprintf_error(buflen - len, ret))
763346981Scy			return len;
764346981Scy		len += ret;
765346981Scy	}
766346981Scy
767346981Scy	if (iface->conf->ieee80211n && !hapd->conf->disable_11n && mode) {
768346981Scy		len = hostapd_write_ht_mcs_bitmask(buf, buflen, len,
769346981Scy						   mode->mcs_set);
770346981Scy	}
771346981Scy
772346981Scy	if (iface->current_rates && iface->num_rates) {
773346981Scy		ret = os_snprintf(buf + len, buflen - len, "supported_rates=");
774346981Scy		if (os_snprintf_error(buflen - len, ret))
775346981Scy			return len;
776346981Scy		len += ret;
777346981Scy
778346981Scy		for (j = 0; j < iface->num_rates; j++) {
779346981Scy			ret = os_snprintf(buf + len, buflen - len, "%s%02x",
780346981Scy					  j > 0 ? " " : "",
781346981Scy					  iface->current_rates[j].rate / 5);
782346981Scy			if (os_snprintf_error(buflen - len, ret))
783346981Scy				return len;
784346981Scy			len += ret;
785346981Scy		}
786346981Scy		ret = os_snprintf(buf + len, buflen - len, "\n");
787346981Scy		if (os_snprintf_error(buflen - len, ret))
788346981Scy			return len;
789346981Scy		len += ret;
790346981Scy	}
791346981Scy
792346981Scy	for (j = 0; mode && j < mode->num_channels; j++) {
793346981Scy		if (mode->channels[j].freq == iface->freq) {
794346981Scy			ret = os_snprintf(buf + len, buflen - len,
795346981Scy					  "max_txpower=%u\n",
796346981Scy					  mode->channels[j].max_tx_power);
797346981Scy			if (os_snprintf_error(buflen - len, ret))
798346981Scy				return len;
799346981Scy			len += ret;
800346981Scy			break;
801346981Scy		}
802346981Scy	}
803346981Scy
804281806Srpaulo	for (i = 0; i < iface->num_bss; i++) {
805281806Srpaulo		struct hostapd_data *bss = iface->bss[i];
806281806Srpaulo		ret = os_snprintf(buf + len, buflen - len,
807281806Srpaulo				  "bss[%d]=%s\n"
808281806Srpaulo				  "bssid[%d]=" MACSTR "\n"
809281806Srpaulo				  "ssid[%d]=%s\n"
810281806Srpaulo				  "num_sta[%d]=%d\n",
811281806Srpaulo				  (int) i, bss->conf->iface,
812281806Srpaulo				  (int) i, MAC2STR(bss->own_addr),
813281806Srpaulo				  (int) i,
814281806Srpaulo				  wpa_ssid_txt(bss->conf->ssid.ssid,
815281806Srpaulo					       bss->conf->ssid.ssid_len),
816281806Srpaulo				  (int) i, bss->num_sta);
817281806Srpaulo		if (os_snprintf_error(buflen - len, ret))
818281806Srpaulo			return len;
819281806Srpaulo		len += ret;
820281806Srpaulo	}
821281806Srpaulo
822346981Scy	if (hapd->conf->chan_util_avg_period) {
823346981Scy		ret = os_snprintf(buf + len, buflen - len,
824346981Scy				  "chan_util_avg=%u\n",
825346981Scy				  iface->chan_util_average);
826346981Scy		if (os_snprintf_error(buflen - len, ret))
827346981Scy			return len;
828346981Scy		len += ret;
829346981Scy	}
830346981Scy
831281806Srpaulo	return len;
832281806Srpaulo}
833281806Srpaulo
834281806Srpaulo
835281806Srpauloint hostapd_parse_csa_settings(const char *pos,
836281806Srpaulo			       struct csa_settings *settings)
837281806Srpaulo{
838281806Srpaulo	char *end;
839281806Srpaulo
840281806Srpaulo	os_memset(settings, 0, sizeof(*settings));
841281806Srpaulo	settings->cs_count = strtol(pos, &end, 10);
842281806Srpaulo	if (pos == end) {
843281806Srpaulo		wpa_printf(MSG_ERROR, "chanswitch: invalid cs_count provided");
844281806Srpaulo		return -1;
845281806Srpaulo	}
846281806Srpaulo
847281806Srpaulo	settings->freq_params.freq = atoi(end);
848281806Srpaulo	if (settings->freq_params.freq == 0) {
849281806Srpaulo		wpa_printf(MSG_ERROR, "chanswitch: invalid freq provided");
850281806Srpaulo		return -1;
851281806Srpaulo	}
852281806Srpaulo
853281806Srpaulo#define SET_CSA_SETTING(str) \
854281806Srpaulo	do { \
855281806Srpaulo		const char *pos2 = os_strstr(pos, " " #str "="); \
856281806Srpaulo		if (pos2) { \
857281806Srpaulo			pos2 += sizeof(" " #str "=") - 1; \
858281806Srpaulo			settings->freq_params.str = atoi(pos2); \
859281806Srpaulo		} \
860281806Srpaulo	} while (0)
861281806Srpaulo
862281806Srpaulo	SET_CSA_SETTING(center_freq1);
863281806Srpaulo	SET_CSA_SETTING(center_freq2);
864281806Srpaulo	SET_CSA_SETTING(bandwidth);
865281806Srpaulo	SET_CSA_SETTING(sec_channel_offset);
866281806Srpaulo	settings->freq_params.ht_enabled = !!os_strstr(pos, " ht");
867281806Srpaulo	settings->freq_params.vht_enabled = !!os_strstr(pos, " vht");
868281806Srpaulo	settings->block_tx = !!os_strstr(pos, " blocktx");
869281806Srpaulo#undef SET_CSA_SETTING
870281806Srpaulo
871281806Srpaulo	return 0;
872281806Srpaulo}
873281806Srpaulo
874281806Srpaulo
875281806Srpauloint hostapd_ctrl_iface_stop_ap(struct hostapd_data *hapd)
876281806Srpaulo{
877281806Srpaulo	return hostapd_drv_stop_ap(hapd);
878281806Srpaulo}
879337817Scy
880337817Scy
881337817Scyint hostapd_ctrl_iface_pmksa_list(struct hostapd_data *hapd, char *buf,
882337817Scy				  size_t len)
883337817Scy{
884337817Scy	return wpa_auth_pmksa_list(hapd->wpa_auth, buf, len);
885337817Scy}
886337817Scy
887337817Scy
888337817Scyvoid hostapd_ctrl_iface_pmksa_flush(struct hostapd_data *hapd)
889337817Scy{
890337817Scy	wpa_auth_pmksa_flush(hapd->wpa_auth);
891337817Scy}
892346981Scy
893346981Scy
894346981Scyint hostapd_ctrl_iface_pmksa_add(struct hostapd_data *hapd, char *cmd)
895346981Scy{
896346981Scy	u8 spa[ETH_ALEN];
897346981Scy	u8 pmkid[PMKID_LEN];
898346981Scy	u8 pmk[PMK_LEN_MAX];
899346981Scy	size_t pmk_len;
900346981Scy	char *pos, *pos2;
901346981Scy	int akmp = 0, expiration = 0;
902346981Scy
903346981Scy	/*
904346981Scy	 * Entry format:
905346981Scy	 * <STA addr> <PMKID> <PMK> <expiration in seconds> <akmp>
906346981Scy	 */
907346981Scy
908346981Scy	if (hwaddr_aton(cmd, spa))
909346981Scy		return -1;
910346981Scy
911346981Scy	pos = os_strchr(cmd, ' ');
912346981Scy	if (!pos)
913346981Scy		return -1;
914346981Scy	pos++;
915346981Scy
916346981Scy	if (hexstr2bin(pos, pmkid, PMKID_LEN) < 0)
917346981Scy		return -1;
918346981Scy
919346981Scy	pos = os_strchr(pos, ' ');
920346981Scy	if (!pos)
921346981Scy		return -1;
922346981Scy	pos++;
923346981Scy
924346981Scy	pos2 = os_strchr(pos, ' ');
925346981Scy	if (!pos2)
926346981Scy		return -1;
927346981Scy	pmk_len = (pos2 - pos) / 2;
928346981Scy	if (pmk_len < PMK_LEN || pmk_len > PMK_LEN_MAX ||
929346981Scy	    hexstr2bin(pos, pmk, pmk_len) < 0)
930346981Scy		return -1;
931346981Scy
932346981Scy	pos = pos2 + 1;
933346981Scy
934346981Scy	if (sscanf(pos, "%d %d", &expiration, &akmp) != 2)
935346981Scy		return -1;
936346981Scy
937346981Scy	return wpa_auth_pmksa_add2(hapd->wpa_auth, spa, pmk, pmk_len,
938346981Scy				   pmkid, expiration, akmp);
939346981Scy}
940346981Scy
941346981Scy
942346981Scy#ifdef CONFIG_PMKSA_CACHE_EXTERNAL
943346981Scy#ifdef CONFIG_MESH
944346981Scy
945346981Scyint hostapd_ctrl_iface_pmksa_list_mesh(struct hostapd_data *hapd,
946346981Scy				       const u8 *addr, char *buf, size_t len)
947346981Scy{
948346981Scy	return wpa_auth_pmksa_list_mesh(hapd->wpa_auth, addr, buf, len);
949346981Scy}
950346981Scy
951346981Scy
952346981Scyvoid * hostapd_ctrl_iface_pmksa_create_entry(const u8 *aa, char *cmd)
953346981Scy{
954346981Scy	u8 spa[ETH_ALEN];
955346981Scy	u8 pmkid[PMKID_LEN];
956346981Scy	u8 pmk[PMK_LEN_MAX];
957346981Scy	char *pos;
958346981Scy	int expiration;
959346981Scy
960346981Scy	/*
961346981Scy	 * Entry format:
962346981Scy	 * <BSSID> <PMKID> <PMK> <expiration in seconds>
963346981Scy	 */
964346981Scy
965346981Scy	if (hwaddr_aton(cmd, spa))
966346981Scy		return NULL;
967346981Scy
968346981Scy	pos = os_strchr(cmd, ' ');
969346981Scy	if (!pos)
970346981Scy		return NULL;
971346981Scy	pos++;
972346981Scy
973346981Scy	if (hexstr2bin(pos, pmkid, PMKID_LEN) < 0)
974346981Scy		return NULL;
975346981Scy
976346981Scy	pos = os_strchr(pos, ' ');
977346981Scy	if (!pos)
978346981Scy		return NULL;
979346981Scy	pos++;
980346981Scy
981346981Scy	if (hexstr2bin(pos, pmk, PMK_LEN) < 0)
982346981Scy		return NULL;
983346981Scy
984346981Scy	pos = os_strchr(pos, ' ');
985346981Scy	if (!pos)
986346981Scy		return NULL;
987346981Scy	pos++;
988346981Scy
989346981Scy	if (sscanf(pos, "%d", &expiration) != 1)
990346981Scy		return NULL;
991346981Scy
992346981Scy	return wpa_auth_pmksa_create_entry(aa, spa, pmk, pmkid, expiration);
993346981Scy}
994346981Scy
995346981Scy#endif /* CONFIG_MESH */
996346981Scy#endif /* CONFIG_PMKSA_CACHE_EXTERNAL */
997