1252190Srpaulo/*
2252190Srpaulo * Generic advertisement service (GAS) server
3252190Srpaulo * Copyright (c) 2011-2012, Qualcomm Atheros, Inc.
4252190Srpaulo *
5252190Srpaulo * This software may be distributed under the terms of the BSD license.
6252190Srpaulo * See README for more details.
7252190Srpaulo */
8252190Srpaulo
9252190Srpaulo#include "includes.h"
10252190Srpaulo
11252190Srpaulo#include "common.h"
12252190Srpaulo#include "common/ieee802_11_defs.h"
13252190Srpaulo#include "common/gas.h"
14252190Srpaulo#include "utils/eloop.h"
15252190Srpaulo#include "hostapd.h"
16252190Srpaulo#include "ap_config.h"
17252190Srpaulo#include "ap_drv_ops.h"
18252190Srpaulo#include "sta_info.h"
19252190Srpaulo#include "gas_serv.h"
20252190Srpaulo
21252190Srpaulo
22252190Srpaulostatic struct gas_dialog_info *
23252190Srpaulogas_dialog_create(struct hostapd_data *hapd, const u8 *addr, u8 dialog_token)
24252190Srpaulo{
25252190Srpaulo	struct sta_info *sta;
26252190Srpaulo	struct gas_dialog_info *dia = NULL;
27252190Srpaulo	int i, j;
28252190Srpaulo
29252190Srpaulo	sta = ap_get_sta(hapd, addr);
30252190Srpaulo	if (!sta) {
31252190Srpaulo		/*
32252190Srpaulo		 * We need a STA entry to be able to maintain state for
33252190Srpaulo		 * the GAS query.
34252190Srpaulo		 */
35252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: Add a temporary STA entry for "
36252190Srpaulo			   "GAS query");
37252190Srpaulo		sta = ap_sta_add(hapd, addr);
38252190Srpaulo		if (!sta) {
39252190Srpaulo			wpa_printf(MSG_DEBUG, "Failed to add STA " MACSTR
40252190Srpaulo				   " for GAS query", MAC2STR(addr));
41252190Srpaulo			return NULL;
42252190Srpaulo		}
43252190Srpaulo		sta->flags |= WLAN_STA_GAS;
44252190Srpaulo		/*
45252190Srpaulo		 * The default inactivity is 300 seconds. We don't need
46252190Srpaulo		 * it to be that long.
47252190Srpaulo		 */
48252190Srpaulo		ap_sta_session_timeout(hapd, sta, 5);
49252190Srpaulo	}
50252190Srpaulo
51252190Srpaulo	if (sta->gas_dialog == NULL) {
52252190Srpaulo		sta->gas_dialog = os_zalloc(GAS_DIALOG_MAX *
53252190Srpaulo					    sizeof(struct gas_dialog_info));
54252190Srpaulo		if (sta->gas_dialog == NULL)
55252190Srpaulo			return NULL;
56252190Srpaulo	}
57252190Srpaulo
58252190Srpaulo	for (i = sta->gas_dialog_next, j = 0; j < GAS_DIALOG_MAX; i++, j++) {
59252190Srpaulo		if (i == GAS_DIALOG_MAX)
60252190Srpaulo			i = 0;
61252190Srpaulo		if (sta->gas_dialog[i].valid)
62252190Srpaulo			continue;
63252190Srpaulo		dia = &sta->gas_dialog[i];
64252190Srpaulo		dia->valid = 1;
65252190Srpaulo		dia->index = i;
66252190Srpaulo		dia->dialog_token = dialog_token;
67252190Srpaulo		sta->gas_dialog_next = (++i == GAS_DIALOG_MAX) ? 0 : i;
68252190Srpaulo		return dia;
69252190Srpaulo	}
70252190Srpaulo
71252190Srpaulo	wpa_msg(hapd->msg_ctx, MSG_ERROR, "ANQP: Could not create dialog for "
72252190Srpaulo		MACSTR " dialog_token %u. Consider increasing "
73252190Srpaulo		"GAS_DIALOG_MAX.", MAC2STR(addr), dialog_token);
74252190Srpaulo
75252190Srpaulo	return NULL;
76252190Srpaulo}
77252190Srpaulo
78252190Srpaulo
79252190Srpaulostruct gas_dialog_info *
80252190Srpaulogas_serv_dialog_find(struct hostapd_data *hapd, const u8 *addr,
81252190Srpaulo		     u8 dialog_token)
82252190Srpaulo{
83252190Srpaulo	struct sta_info *sta;
84252190Srpaulo	int i;
85252190Srpaulo
86252190Srpaulo	sta = ap_get_sta(hapd, addr);
87252190Srpaulo	if (!sta) {
88252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: could not find STA " MACSTR,
89252190Srpaulo			   MAC2STR(addr));
90252190Srpaulo		return NULL;
91252190Srpaulo	}
92252190Srpaulo	for (i = 0; sta->gas_dialog && i < GAS_DIALOG_MAX; i++) {
93252190Srpaulo		if (sta->gas_dialog[i].dialog_token != dialog_token ||
94252190Srpaulo		    !sta->gas_dialog[i].valid)
95252190Srpaulo			continue;
96252190Srpaulo		return &sta->gas_dialog[i];
97252190Srpaulo	}
98252190Srpaulo	wpa_printf(MSG_DEBUG, "ANQP: Could not find dialog for "
99252190Srpaulo		   MACSTR " dialog_token %u", MAC2STR(addr), dialog_token);
100252190Srpaulo	return NULL;
101252190Srpaulo}
102252190Srpaulo
103252190Srpaulo
104252190Srpaulovoid gas_serv_dialog_clear(struct gas_dialog_info *dia)
105252190Srpaulo{
106252190Srpaulo	wpabuf_free(dia->sd_resp);
107252190Srpaulo	os_memset(dia, 0, sizeof(*dia));
108252190Srpaulo}
109252190Srpaulo
110252190Srpaulo
111252190Srpaulostatic void gas_serv_free_dialogs(struct hostapd_data *hapd,
112252190Srpaulo				  const u8 *sta_addr)
113252190Srpaulo{
114252190Srpaulo	struct sta_info *sta;
115252190Srpaulo	int i;
116252190Srpaulo
117252190Srpaulo	sta = ap_get_sta(hapd, sta_addr);
118252190Srpaulo	if (sta == NULL || sta->gas_dialog == NULL)
119252190Srpaulo		return;
120252190Srpaulo
121252190Srpaulo	for (i = 0; i < GAS_DIALOG_MAX; i++) {
122252190Srpaulo		if (sta->gas_dialog[i].valid)
123252190Srpaulo			return;
124252190Srpaulo	}
125252190Srpaulo
126252190Srpaulo	os_free(sta->gas_dialog);
127252190Srpaulo	sta->gas_dialog = NULL;
128252190Srpaulo}
129252190Srpaulo
130252190Srpaulo
131252190Srpaulo#ifdef CONFIG_HS20
132252190Srpaulostatic void anqp_add_hs_capab_list(struct hostapd_data *hapd,
133252190Srpaulo				   struct wpabuf *buf)
134252190Srpaulo{
135252190Srpaulo	u8 *len;
136252190Srpaulo
137252190Srpaulo	len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC);
138252190Srpaulo	wpabuf_put_be24(buf, OUI_WFA);
139252190Srpaulo	wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE);
140252190Srpaulo	wpabuf_put_u8(buf, HS20_STYPE_CAPABILITY_LIST);
141252190Srpaulo	wpabuf_put_u8(buf, 0); /* Reserved */
142252190Srpaulo	wpabuf_put_u8(buf, HS20_STYPE_CAPABILITY_LIST);
143252190Srpaulo	if (hapd->conf->hs20_oper_friendly_name)
144252190Srpaulo		wpabuf_put_u8(buf, HS20_STYPE_OPERATOR_FRIENDLY_NAME);
145252190Srpaulo	if (hapd->conf->hs20_wan_metrics)
146252190Srpaulo		wpabuf_put_u8(buf, HS20_STYPE_WAN_METRICS);
147252190Srpaulo	if (hapd->conf->hs20_connection_capability)
148252190Srpaulo		wpabuf_put_u8(buf, HS20_STYPE_CONNECTION_CAPABILITY);
149252190Srpaulo	if (hapd->conf->nai_realm_data)
150252190Srpaulo		wpabuf_put_u8(buf, HS20_STYPE_NAI_HOME_REALM_QUERY);
151252190Srpaulo	if (hapd->conf->hs20_operating_class)
152252190Srpaulo		wpabuf_put_u8(buf, HS20_STYPE_OPERATING_CLASS);
153252190Srpaulo	gas_anqp_set_element_len(buf, len);
154252190Srpaulo}
155252190Srpaulo#endif /* CONFIG_HS20 */
156252190Srpaulo
157252190Srpaulo
158252190Srpaulostatic void anqp_add_capab_list(struct hostapd_data *hapd,
159252190Srpaulo				struct wpabuf *buf)
160252190Srpaulo{
161252190Srpaulo	u8 *len;
162252190Srpaulo
163252190Srpaulo	len = gas_anqp_add_element(buf, ANQP_CAPABILITY_LIST);
164252190Srpaulo	wpabuf_put_le16(buf, ANQP_CAPABILITY_LIST);
165252190Srpaulo	if (hapd->conf->venue_name)
166252190Srpaulo		wpabuf_put_le16(buf, ANQP_VENUE_NAME);
167252190Srpaulo	if (hapd->conf->network_auth_type)
168252190Srpaulo		wpabuf_put_le16(buf, ANQP_NETWORK_AUTH_TYPE);
169252190Srpaulo	if (hapd->conf->roaming_consortium)
170252190Srpaulo		wpabuf_put_le16(buf, ANQP_ROAMING_CONSORTIUM);
171252190Srpaulo	if (hapd->conf->ipaddr_type_configured)
172252190Srpaulo		wpabuf_put_le16(buf, ANQP_IP_ADDR_TYPE_AVAILABILITY);
173252190Srpaulo	if (hapd->conf->nai_realm_data)
174252190Srpaulo		wpabuf_put_le16(buf, ANQP_NAI_REALM);
175252190Srpaulo	if (hapd->conf->anqp_3gpp_cell_net)
176252190Srpaulo		wpabuf_put_le16(buf, ANQP_3GPP_CELLULAR_NETWORK);
177252190Srpaulo	if (hapd->conf->domain_name)
178252190Srpaulo		wpabuf_put_le16(buf, ANQP_DOMAIN_NAME);
179252190Srpaulo#ifdef CONFIG_HS20
180252190Srpaulo	anqp_add_hs_capab_list(hapd, buf);
181252190Srpaulo#endif /* CONFIG_HS20 */
182252190Srpaulo	gas_anqp_set_element_len(buf, len);
183252190Srpaulo}
184252190Srpaulo
185252190Srpaulo
186252190Srpaulostatic void anqp_add_venue_name(struct hostapd_data *hapd, struct wpabuf *buf)
187252190Srpaulo{
188252190Srpaulo	if (hapd->conf->venue_name) {
189252190Srpaulo		u8 *len;
190252190Srpaulo		unsigned int i;
191252190Srpaulo		len = gas_anqp_add_element(buf, ANQP_VENUE_NAME);
192252190Srpaulo		wpabuf_put_u8(buf, hapd->conf->venue_group);
193252190Srpaulo		wpabuf_put_u8(buf, hapd->conf->venue_type);
194252190Srpaulo		for (i = 0; i < hapd->conf->venue_name_count; i++) {
195252190Srpaulo			struct hostapd_lang_string *vn;
196252190Srpaulo			vn = &hapd->conf->venue_name[i];
197252190Srpaulo			wpabuf_put_u8(buf, 3 + vn->name_len);
198252190Srpaulo			wpabuf_put_data(buf, vn->lang, 3);
199252190Srpaulo			wpabuf_put_data(buf, vn->name, vn->name_len);
200252190Srpaulo		}
201252190Srpaulo		gas_anqp_set_element_len(buf, len);
202252190Srpaulo	}
203252190Srpaulo}
204252190Srpaulo
205252190Srpaulo
206252190Srpaulostatic void anqp_add_network_auth_type(struct hostapd_data *hapd,
207252190Srpaulo				       struct wpabuf *buf)
208252190Srpaulo{
209252190Srpaulo	if (hapd->conf->network_auth_type) {
210252190Srpaulo		wpabuf_put_le16(buf, ANQP_NETWORK_AUTH_TYPE);
211252190Srpaulo		wpabuf_put_le16(buf, hapd->conf->network_auth_type_len);
212252190Srpaulo		wpabuf_put_data(buf, hapd->conf->network_auth_type,
213252190Srpaulo				hapd->conf->network_auth_type_len);
214252190Srpaulo	}
215252190Srpaulo}
216252190Srpaulo
217252190Srpaulo
218252190Srpaulostatic void anqp_add_roaming_consortium(struct hostapd_data *hapd,
219252190Srpaulo					struct wpabuf *buf)
220252190Srpaulo{
221252190Srpaulo	unsigned int i;
222252190Srpaulo	u8 *len;
223252190Srpaulo
224252190Srpaulo	len = gas_anqp_add_element(buf, ANQP_ROAMING_CONSORTIUM);
225252190Srpaulo	for (i = 0; i < hapd->conf->roaming_consortium_count; i++) {
226252190Srpaulo		struct hostapd_roaming_consortium *rc;
227252190Srpaulo		rc = &hapd->conf->roaming_consortium[i];
228252190Srpaulo		wpabuf_put_u8(buf, rc->len);
229252190Srpaulo		wpabuf_put_data(buf, rc->oi, rc->len);
230252190Srpaulo	}
231252190Srpaulo	gas_anqp_set_element_len(buf, len);
232252190Srpaulo}
233252190Srpaulo
234252190Srpaulo
235252190Srpaulostatic void anqp_add_ip_addr_type_availability(struct hostapd_data *hapd,
236252190Srpaulo					       struct wpabuf *buf)
237252190Srpaulo{
238252190Srpaulo	if (hapd->conf->ipaddr_type_configured) {
239252190Srpaulo		wpabuf_put_le16(buf, ANQP_IP_ADDR_TYPE_AVAILABILITY);
240252190Srpaulo		wpabuf_put_le16(buf, 1);
241252190Srpaulo		wpabuf_put_u8(buf, hapd->conf->ipaddr_type_availability);
242252190Srpaulo	}
243252190Srpaulo}
244252190Srpaulo
245252190Srpaulo
246252190Srpaulostatic void anqp_add_nai_realm_eap(struct wpabuf *buf,
247252190Srpaulo				   struct hostapd_nai_realm_data *realm)
248252190Srpaulo{
249252190Srpaulo	unsigned int i, j;
250252190Srpaulo
251252190Srpaulo	wpabuf_put_u8(buf, realm->eap_method_count);
252252190Srpaulo
253252190Srpaulo	for (i = 0; i < realm->eap_method_count; i++) {
254252190Srpaulo		struct hostapd_nai_realm_eap *eap = &realm->eap_method[i];
255252190Srpaulo		wpabuf_put_u8(buf, 2 + (3 * eap->num_auths));
256252190Srpaulo		wpabuf_put_u8(buf, eap->eap_method);
257252190Srpaulo		wpabuf_put_u8(buf, eap->num_auths);
258252190Srpaulo		for (j = 0; j < eap->num_auths; j++) {
259252190Srpaulo			wpabuf_put_u8(buf, eap->auth_id[j]);
260252190Srpaulo			wpabuf_put_u8(buf, 1);
261252190Srpaulo			wpabuf_put_u8(buf, eap->auth_val[j]);
262252190Srpaulo		}
263252190Srpaulo	}
264252190Srpaulo}
265252190Srpaulo
266252190Srpaulo
267252190Srpaulostatic void anqp_add_nai_realm_data(struct wpabuf *buf,
268252190Srpaulo				    struct hostapd_nai_realm_data *realm,
269252190Srpaulo				    unsigned int realm_idx)
270252190Srpaulo{
271252190Srpaulo	u8 *realm_data_len;
272252190Srpaulo
273252190Srpaulo	wpa_printf(MSG_DEBUG, "realm=%s, len=%d", realm->realm[realm_idx],
274252190Srpaulo		   (int) os_strlen(realm->realm[realm_idx]));
275252190Srpaulo	realm_data_len = wpabuf_put(buf, 2);
276252190Srpaulo	wpabuf_put_u8(buf, realm->encoding);
277252190Srpaulo	wpabuf_put_u8(buf, os_strlen(realm->realm[realm_idx]));
278252190Srpaulo	wpabuf_put_str(buf, realm->realm[realm_idx]);
279252190Srpaulo	anqp_add_nai_realm_eap(buf, realm);
280252190Srpaulo	gas_anqp_set_element_len(buf, realm_data_len);
281252190Srpaulo}
282252190Srpaulo
283252190Srpaulo
284252190Srpaulostatic int hs20_add_nai_home_realm_matches(struct hostapd_data *hapd,
285252190Srpaulo					   struct wpabuf *buf,
286252190Srpaulo					   const u8 *home_realm,
287252190Srpaulo					   size_t home_realm_len)
288252190Srpaulo{
289252190Srpaulo	unsigned int i, j, k;
290252190Srpaulo	u8 num_realms, num_matching = 0, encoding, realm_len, *realm_list_len;
291252190Srpaulo	struct hostapd_nai_realm_data *realm;
292252190Srpaulo	const u8 *pos, *realm_name, *end;
293252190Srpaulo	struct {
294252190Srpaulo		unsigned int realm_data_idx;
295252190Srpaulo		unsigned int realm_idx;
296252190Srpaulo	} matches[10];
297252190Srpaulo
298252190Srpaulo	pos = home_realm;
299252190Srpaulo	end = pos + home_realm_len;
300252190Srpaulo	if (pos + 1 > end) {
301252190Srpaulo		wpa_hexdump(MSG_DEBUG, "Too short NAI Home Realm Query",
302252190Srpaulo			    home_realm, home_realm_len);
303252190Srpaulo		return -1;
304252190Srpaulo	}
305252190Srpaulo	num_realms = *pos++;
306252190Srpaulo
307252190Srpaulo	for (i = 0; i < num_realms && num_matching < 10; i++) {
308252190Srpaulo		if (pos + 2 > end) {
309252190Srpaulo			wpa_hexdump(MSG_DEBUG,
310252190Srpaulo				    "Truncated NAI Home Realm Query",
311252190Srpaulo				    home_realm, home_realm_len);
312252190Srpaulo			return -1;
313252190Srpaulo		}
314252190Srpaulo		encoding = *pos++;
315252190Srpaulo		realm_len = *pos++;
316252190Srpaulo		if (pos + realm_len > end) {
317252190Srpaulo			wpa_hexdump(MSG_DEBUG,
318252190Srpaulo				    "Truncated NAI Home Realm Query",
319252190Srpaulo				    home_realm, home_realm_len);
320252190Srpaulo			return -1;
321252190Srpaulo		}
322252190Srpaulo		realm_name = pos;
323252190Srpaulo		for (j = 0; j < hapd->conf->nai_realm_count &&
324252190Srpaulo			     num_matching < 10; j++) {
325252190Srpaulo			const u8 *rpos, *rend;
326252190Srpaulo			realm = &hapd->conf->nai_realm_data[j];
327252190Srpaulo			if (encoding != realm->encoding)
328252190Srpaulo				continue;
329252190Srpaulo
330252190Srpaulo			rpos = realm_name;
331252190Srpaulo			while (rpos < realm_name + realm_len &&
332252190Srpaulo			       num_matching < 10) {
333252190Srpaulo				for (rend = rpos;
334252190Srpaulo				     rend < realm_name + realm_len; rend++) {
335252190Srpaulo					if (*rend == ';')
336252190Srpaulo						break;
337252190Srpaulo				}
338252190Srpaulo				for (k = 0; k < MAX_NAI_REALMS &&
339252190Srpaulo					     realm->realm[k] &&
340252190Srpaulo					     num_matching < 10; k++) {
341252190Srpaulo					if ((int) os_strlen(realm->realm[k]) !=
342252190Srpaulo					    rend - rpos ||
343252190Srpaulo					    os_strncmp((char *) rpos,
344252190Srpaulo						       realm->realm[k],
345252190Srpaulo						       rend - rpos) != 0)
346252190Srpaulo						continue;
347252190Srpaulo					matches[num_matching].realm_data_idx =
348252190Srpaulo						j;
349252190Srpaulo					matches[num_matching].realm_idx = k;
350252190Srpaulo					num_matching++;
351252190Srpaulo				}
352252190Srpaulo				rpos = rend + 1;
353252190Srpaulo			}
354252190Srpaulo		}
355252190Srpaulo		pos += realm_len;
356252190Srpaulo	}
357252190Srpaulo
358252190Srpaulo	realm_list_len = gas_anqp_add_element(buf, ANQP_NAI_REALM);
359252190Srpaulo	wpabuf_put_le16(buf, num_matching);
360252190Srpaulo
361252190Srpaulo	/*
362252190Srpaulo	 * There are two ways to format. 1. each realm in a NAI Realm Data unit
363252190Srpaulo	 * 2. all realms that share the same EAP methods in a NAI Realm Data
364252190Srpaulo	 * unit. The first format is likely to be bigger in size than the
365252190Srpaulo	 * second, but may be easier to parse and process by the receiver.
366252190Srpaulo	 */
367252190Srpaulo	for (i = 0; i < num_matching; i++) {
368252190Srpaulo		wpa_printf(MSG_DEBUG, "realm_idx %d, realm_data_idx %d",
369252190Srpaulo			   matches[i].realm_data_idx, matches[i].realm_idx);
370252190Srpaulo		realm = &hapd->conf->nai_realm_data[matches[i].realm_data_idx];
371252190Srpaulo		anqp_add_nai_realm_data(buf, realm, matches[i].realm_idx);
372252190Srpaulo	}
373252190Srpaulo	gas_anqp_set_element_len(buf, realm_list_len);
374252190Srpaulo	return 0;
375252190Srpaulo}
376252190Srpaulo
377252190Srpaulo
378252190Srpaulostatic void anqp_add_nai_realm(struct hostapd_data *hapd, struct wpabuf *buf,
379252190Srpaulo			       const u8 *home_realm, size_t home_realm_len,
380252190Srpaulo			       int nai_realm, int nai_home_realm)
381252190Srpaulo{
382252190Srpaulo	if (nai_realm && hapd->conf->nai_realm_data) {
383252190Srpaulo		u8 *len;
384252190Srpaulo		unsigned int i, j;
385252190Srpaulo		len = gas_anqp_add_element(buf, ANQP_NAI_REALM);
386252190Srpaulo		wpabuf_put_le16(buf, hapd->conf->nai_realm_count);
387252190Srpaulo		for (i = 0; i < hapd->conf->nai_realm_count; i++) {
388252190Srpaulo			u8 *realm_data_len, *realm_len;
389252190Srpaulo			struct hostapd_nai_realm_data *realm;
390252190Srpaulo
391252190Srpaulo			realm = &hapd->conf->nai_realm_data[i];
392252190Srpaulo			realm_data_len = wpabuf_put(buf, 2);
393252190Srpaulo			wpabuf_put_u8(buf, realm->encoding);
394252190Srpaulo			realm_len = wpabuf_put(buf, 1);
395252190Srpaulo			for (j = 0; realm->realm[j]; j++) {
396252190Srpaulo				if (j > 0)
397252190Srpaulo					wpabuf_put_u8(buf, ';');
398252190Srpaulo				wpabuf_put_str(buf, realm->realm[j]);
399252190Srpaulo			}
400252190Srpaulo			*realm_len = (u8 *) wpabuf_put(buf, 0) - realm_len - 1;
401252190Srpaulo			anqp_add_nai_realm_eap(buf, realm);
402252190Srpaulo			gas_anqp_set_element_len(buf, realm_data_len);
403252190Srpaulo		}
404252190Srpaulo		gas_anqp_set_element_len(buf, len);
405252190Srpaulo	} else if (nai_home_realm && hapd->conf->nai_realm_data) {
406252190Srpaulo		hs20_add_nai_home_realm_matches(hapd, buf, home_realm,
407252190Srpaulo						home_realm_len);
408252190Srpaulo	}
409252190Srpaulo}
410252190Srpaulo
411252190Srpaulo
412252190Srpaulostatic void anqp_add_3gpp_cellular_network(struct hostapd_data *hapd,
413252190Srpaulo					   struct wpabuf *buf)
414252190Srpaulo{
415252190Srpaulo	if (hapd->conf->anqp_3gpp_cell_net) {
416252190Srpaulo		wpabuf_put_le16(buf, ANQP_3GPP_CELLULAR_NETWORK);
417252190Srpaulo		wpabuf_put_le16(buf,
418252190Srpaulo				hapd->conf->anqp_3gpp_cell_net_len);
419252190Srpaulo		wpabuf_put_data(buf, hapd->conf->anqp_3gpp_cell_net,
420252190Srpaulo				hapd->conf->anqp_3gpp_cell_net_len);
421252190Srpaulo	}
422252190Srpaulo}
423252190Srpaulo
424252190Srpaulo
425252190Srpaulostatic void anqp_add_domain_name(struct hostapd_data *hapd, struct wpabuf *buf)
426252190Srpaulo{
427252190Srpaulo	if (hapd->conf->domain_name) {
428252190Srpaulo		wpabuf_put_le16(buf, ANQP_DOMAIN_NAME);
429252190Srpaulo		wpabuf_put_le16(buf, hapd->conf->domain_name_len);
430252190Srpaulo		wpabuf_put_data(buf, hapd->conf->domain_name,
431252190Srpaulo				hapd->conf->domain_name_len);
432252190Srpaulo	}
433252190Srpaulo}
434252190Srpaulo
435252190Srpaulo
436252190Srpaulo#ifdef CONFIG_HS20
437252190Srpaulo
438252190Srpaulostatic void anqp_add_operator_friendly_name(struct hostapd_data *hapd,
439252190Srpaulo					    struct wpabuf *buf)
440252190Srpaulo{
441252190Srpaulo	if (hapd->conf->hs20_oper_friendly_name) {
442252190Srpaulo		u8 *len;
443252190Srpaulo		unsigned int i;
444252190Srpaulo		len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC);
445252190Srpaulo		wpabuf_put_be24(buf, OUI_WFA);
446252190Srpaulo		wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE);
447252190Srpaulo		wpabuf_put_u8(buf, HS20_STYPE_OPERATOR_FRIENDLY_NAME);
448252190Srpaulo		wpabuf_put_u8(buf, 0); /* Reserved */
449252190Srpaulo		for (i = 0; i < hapd->conf->hs20_oper_friendly_name_count; i++)
450252190Srpaulo		{
451252190Srpaulo			struct hostapd_lang_string *vn;
452252190Srpaulo			vn = &hapd->conf->hs20_oper_friendly_name[i];
453252190Srpaulo			wpabuf_put_u8(buf, 3 + vn->name_len);
454252190Srpaulo			wpabuf_put_data(buf, vn->lang, 3);
455252190Srpaulo			wpabuf_put_data(buf, vn->name, vn->name_len);
456252190Srpaulo		}
457252190Srpaulo		gas_anqp_set_element_len(buf, len);
458252190Srpaulo	}
459252190Srpaulo}
460252190Srpaulo
461252190Srpaulo
462252190Srpaulostatic void anqp_add_wan_metrics(struct hostapd_data *hapd,
463252190Srpaulo				 struct wpabuf *buf)
464252190Srpaulo{
465252190Srpaulo	if (hapd->conf->hs20_wan_metrics) {
466252190Srpaulo		u8 *len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC);
467252190Srpaulo		wpabuf_put_be24(buf, OUI_WFA);
468252190Srpaulo		wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE);
469252190Srpaulo		wpabuf_put_u8(buf, HS20_STYPE_WAN_METRICS);
470252190Srpaulo		wpabuf_put_u8(buf, 0); /* Reserved */
471252190Srpaulo		wpabuf_put_data(buf, hapd->conf->hs20_wan_metrics, 13);
472252190Srpaulo		gas_anqp_set_element_len(buf, len);
473252190Srpaulo	}
474252190Srpaulo}
475252190Srpaulo
476252190Srpaulo
477252190Srpaulostatic void anqp_add_connection_capability(struct hostapd_data *hapd,
478252190Srpaulo					   struct wpabuf *buf)
479252190Srpaulo{
480252190Srpaulo	if (hapd->conf->hs20_connection_capability) {
481252190Srpaulo		u8 *len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC);
482252190Srpaulo		wpabuf_put_be24(buf, OUI_WFA);
483252190Srpaulo		wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE);
484252190Srpaulo		wpabuf_put_u8(buf, HS20_STYPE_CONNECTION_CAPABILITY);
485252190Srpaulo		wpabuf_put_u8(buf, 0); /* Reserved */
486252190Srpaulo		wpabuf_put_data(buf, hapd->conf->hs20_connection_capability,
487252190Srpaulo				hapd->conf->hs20_connection_capability_len);
488252190Srpaulo		gas_anqp_set_element_len(buf, len);
489252190Srpaulo	}
490252190Srpaulo}
491252190Srpaulo
492252190Srpaulo
493252190Srpaulostatic void anqp_add_operating_class(struct hostapd_data *hapd,
494252190Srpaulo				     struct wpabuf *buf)
495252190Srpaulo{
496252190Srpaulo	if (hapd->conf->hs20_operating_class) {
497252190Srpaulo		u8 *len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC);
498252190Srpaulo		wpabuf_put_be24(buf, OUI_WFA);
499252190Srpaulo		wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE);
500252190Srpaulo		wpabuf_put_u8(buf, HS20_STYPE_OPERATING_CLASS);
501252190Srpaulo		wpabuf_put_u8(buf, 0); /* Reserved */
502252190Srpaulo		wpabuf_put_data(buf, hapd->conf->hs20_operating_class,
503252190Srpaulo				hapd->conf->hs20_operating_class_len);
504252190Srpaulo		gas_anqp_set_element_len(buf, len);
505252190Srpaulo	}
506252190Srpaulo}
507252190Srpaulo
508252190Srpaulo#endif /* CONFIG_HS20 */
509252190Srpaulo
510252190Srpaulo
511252190Srpaulostatic struct wpabuf *
512252190Srpaulogas_serv_build_gas_resp_payload(struct hostapd_data *hapd,
513252190Srpaulo				unsigned int request,
514252190Srpaulo				struct gas_dialog_info *di,
515252190Srpaulo				const u8 *home_realm, size_t home_realm_len)
516252190Srpaulo{
517252190Srpaulo	struct wpabuf *buf;
518252190Srpaulo
519252190Srpaulo	buf = wpabuf_alloc(1400);
520252190Srpaulo	if (buf == NULL)
521252190Srpaulo		return NULL;
522252190Srpaulo
523252190Srpaulo	if (request & ANQP_REQ_CAPABILITY_LIST)
524252190Srpaulo		anqp_add_capab_list(hapd, buf);
525252190Srpaulo	if (request & ANQP_REQ_VENUE_NAME)
526252190Srpaulo		anqp_add_venue_name(hapd, buf);
527252190Srpaulo	if (request & ANQP_REQ_NETWORK_AUTH_TYPE)
528252190Srpaulo		anqp_add_network_auth_type(hapd, buf);
529252190Srpaulo	if (request & ANQP_REQ_ROAMING_CONSORTIUM)
530252190Srpaulo		anqp_add_roaming_consortium(hapd, buf);
531252190Srpaulo	if (request & ANQP_REQ_IP_ADDR_TYPE_AVAILABILITY)
532252190Srpaulo		anqp_add_ip_addr_type_availability(hapd, buf);
533252190Srpaulo	if (request & (ANQP_REQ_NAI_REALM | ANQP_REQ_NAI_HOME_REALM))
534252190Srpaulo		anqp_add_nai_realm(hapd, buf, home_realm, home_realm_len,
535252190Srpaulo				   request & ANQP_REQ_NAI_REALM,
536252190Srpaulo				   request & ANQP_REQ_NAI_HOME_REALM);
537252190Srpaulo	if (request & ANQP_REQ_3GPP_CELLULAR_NETWORK)
538252190Srpaulo		anqp_add_3gpp_cellular_network(hapd, buf);
539252190Srpaulo	if (request & ANQP_REQ_DOMAIN_NAME)
540252190Srpaulo		anqp_add_domain_name(hapd, buf);
541252190Srpaulo
542252190Srpaulo#ifdef CONFIG_HS20
543252190Srpaulo	if (request & ANQP_REQ_HS_CAPABILITY_LIST)
544252190Srpaulo		anqp_add_hs_capab_list(hapd, buf);
545252190Srpaulo	if (request & ANQP_REQ_OPERATOR_FRIENDLY_NAME)
546252190Srpaulo		anqp_add_operator_friendly_name(hapd, buf);
547252190Srpaulo	if (request & ANQP_REQ_WAN_METRICS)
548252190Srpaulo		anqp_add_wan_metrics(hapd, buf);
549252190Srpaulo	if (request & ANQP_REQ_CONNECTION_CAPABILITY)
550252190Srpaulo		anqp_add_connection_capability(hapd, buf);
551252190Srpaulo	if (request & ANQP_REQ_OPERATING_CLASS)
552252190Srpaulo		anqp_add_operating_class(hapd, buf);
553252190Srpaulo#endif /* CONFIG_HS20 */
554252190Srpaulo
555252190Srpaulo	return buf;
556252190Srpaulo}
557252190Srpaulo
558252190Srpaulo
559252190Srpaulostatic void gas_serv_clear_cached_ies(void *eloop_data, void *user_ctx)
560252190Srpaulo{
561252190Srpaulo	struct gas_dialog_info *dia = eloop_data;
562252190Srpaulo
563252190Srpaulo	wpa_printf(MSG_DEBUG, "GAS: Timeout triggered, clearing dialog for "
564252190Srpaulo		   "dialog token %d", dia->dialog_token);
565252190Srpaulo
566252190Srpaulo	gas_serv_dialog_clear(dia);
567252190Srpaulo}
568252190Srpaulo
569252190Srpaulo
570252190Srpaulostruct anqp_query_info {
571252190Srpaulo	unsigned int request;
572252190Srpaulo	unsigned int remote_request;
573252190Srpaulo	const u8 *home_realm_query;
574252190Srpaulo	size_t home_realm_query_len;
575252190Srpaulo	u16 remote_delay;
576252190Srpaulo};
577252190Srpaulo
578252190Srpaulo
579252190Srpaulostatic void set_anqp_req(unsigned int bit, const char *name, int local,
580252190Srpaulo			 unsigned int remote, u16 remote_delay,
581252190Srpaulo			 struct anqp_query_info *qi)
582252190Srpaulo{
583252190Srpaulo	qi->request |= bit;
584252190Srpaulo	if (local) {
585252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: %s (local)", name);
586252190Srpaulo	} else if (bit & remote) {
587252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: %s (remote)", name);
588252190Srpaulo		qi->remote_request |= bit;
589252190Srpaulo		if (remote_delay > qi->remote_delay)
590252190Srpaulo			qi->remote_delay = remote_delay;
591252190Srpaulo	} else {
592252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: %s not available", name);
593252190Srpaulo	}
594252190Srpaulo}
595252190Srpaulo
596252190Srpaulo
597252190Srpaulostatic void rx_anqp_query_list_id(struct hostapd_data *hapd, u16 info_id,
598252190Srpaulo				  struct anqp_query_info *qi)
599252190Srpaulo{
600252190Srpaulo	switch (info_id) {
601252190Srpaulo	case ANQP_CAPABILITY_LIST:
602252190Srpaulo		set_anqp_req(ANQP_REQ_CAPABILITY_LIST, "Capability List", 1, 0,
603252190Srpaulo			     0, qi);
604252190Srpaulo		break;
605252190Srpaulo	case ANQP_VENUE_NAME:
606252190Srpaulo		set_anqp_req(ANQP_REQ_VENUE_NAME, "Venue Name",
607252190Srpaulo			     hapd->conf->venue_name != NULL, 0, 0, qi);
608252190Srpaulo		break;
609252190Srpaulo	case ANQP_NETWORK_AUTH_TYPE:
610252190Srpaulo		set_anqp_req(ANQP_REQ_NETWORK_AUTH_TYPE, "Network Auth Type",
611252190Srpaulo			     hapd->conf->network_auth_type != NULL,
612252190Srpaulo			     0, 0, qi);
613252190Srpaulo		break;
614252190Srpaulo	case ANQP_ROAMING_CONSORTIUM:
615252190Srpaulo		set_anqp_req(ANQP_REQ_ROAMING_CONSORTIUM, "Roaming Consortium",
616252190Srpaulo			     hapd->conf->roaming_consortium != NULL, 0, 0, qi);
617252190Srpaulo		break;
618252190Srpaulo	case ANQP_IP_ADDR_TYPE_AVAILABILITY:
619252190Srpaulo		set_anqp_req(ANQP_REQ_IP_ADDR_TYPE_AVAILABILITY,
620252190Srpaulo			     "IP Addr Type Availability",
621252190Srpaulo			     hapd->conf->ipaddr_type_configured,
622252190Srpaulo			     0, 0, qi);
623252190Srpaulo		break;
624252190Srpaulo	case ANQP_NAI_REALM:
625252190Srpaulo		set_anqp_req(ANQP_REQ_NAI_REALM, "NAI Realm",
626252190Srpaulo			     hapd->conf->nai_realm_data != NULL,
627252190Srpaulo			     0, 0, qi);
628252190Srpaulo		break;
629252190Srpaulo	case ANQP_3GPP_CELLULAR_NETWORK:
630252190Srpaulo		set_anqp_req(ANQP_REQ_3GPP_CELLULAR_NETWORK,
631252190Srpaulo			     "3GPP Cellular Network",
632252190Srpaulo			     hapd->conf->anqp_3gpp_cell_net != NULL,
633252190Srpaulo			     0, 0, qi);
634252190Srpaulo		break;
635252190Srpaulo	case ANQP_DOMAIN_NAME:
636252190Srpaulo		set_anqp_req(ANQP_REQ_DOMAIN_NAME, "Domain Name",
637252190Srpaulo			     hapd->conf->domain_name != NULL,
638252190Srpaulo			     0, 0, qi);
639252190Srpaulo		break;
640252190Srpaulo	default:
641252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: Unsupported Info Id %u",
642252190Srpaulo			   info_id);
643252190Srpaulo		break;
644252190Srpaulo	}
645252190Srpaulo}
646252190Srpaulo
647252190Srpaulo
648252190Srpaulostatic void rx_anqp_query_list(struct hostapd_data *hapd,
649252190Srpaulo			       const u8 *pos, const u8 *end,
650252190Srpaulo			       struct anqp_query_info *qi)
651252190Srpaulo{
652252190Srpaulo	wpa_printf(MSG_DEBUG, "ANQP: %u Info IDs requested in Query list",
653252190Srpaulo		   (unsigned int) (end - pos) / 2);
654252190Srpaulo
655252190Srpaulo	while (pos + 2 <= end) {
656252190Srpaulo		rx_anqp_query_list_id(hapd, WPA_GET_LE16(pos), qi);
657252190Srpaulo		pos += 2;
658252190Srpaulo	}
659252190Srpaulo}
660252190Srpaulo
661252190Srpaulo
662252190Srpaulo#ifdef CONFIG_HS20
663252190Srpaulo
664252190Srpaulostatic void rx_anqp_hs_query_list(struct hostapd_data *hapd, u8 subtype,
665252190Srpaulo				  struct anqp_query_info *qi)
666252190Srpaulo{
667252190Srpaulo	switch (subtype) {
668252190Srpaulo	case HS20_STYPE_CAPABILITY_LIST:
669252190Srpaulo		set_anqp_req(ANQP_REQ_HS_CAPABILITY_LIST, "HS Capability List",
670252190Srpaulo			     1, 0, 0, qi);
671252190Srpaulo		break;
672252190Srpaulo	case HS20_STYPE_OPERATOR_FRIENDLY_NAME:
673252190Srpaulo		set_anqp_req(ANQP_REQ_OPERATOR_FRIENDLY_NAME,
674252190Srpaulo			     "Operator Friendly Name",
675252190Srpaulo			     hapd->conf->hs20_oper_friendly_name != NULL,
676252190Srpaulo			     0, 0, qi);
677252190Srpaulo		break;
678252190Srpaulo	case HS20_STYPE_WAN_METRICS:
679252190Srpaulo		set_anqp_req(ANQP_REQ_WAN_METRICS, "WAN Metrics",
680252190Srpaulo			     hapd->conf->hs20_wan_metrics != NULL,
681252190Srpaulo			     0, 0, qi);
682252190Srpaulo		break;
683252190Srpaulo	case HS20_STYPE_CONNECTION_CAPABILITY:
684252190Srpaulo		set_anqp_req(ANQP_REQ_CONNECTION_CAPABILITY,
685252190Srpaulo			     "Connection Capability",
686252190Srpaulo			     hapd->conf->hs20_connection_capability != NULL,
687252190Srpaulo			     0, 0, qi);
688252190Srpaulo		break;
689252190Srpaulo	case HS20_STYPE_OPERATING_CLASS:
690252190Srpaulo		set_anqp_req(ANQP_REQ_OPERATING_CLASS, "Operating Class",
691252190Srpaulo			     hapd->conf->hs20_operating_class != NULL,
692252190Srpaulo			     0, 0, qi);
693252190Srpaulo		break;
694252190Srpaulo	default:
695252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: Unsupported HS 2.0 subtype %u",
696252190Srpaulo			   subtype);
697252190Srpaulo		break;
698252190Srpaulo	}
699252190Srpaulo}
700252190Srpaulo
701252190Srpaulo
702252190Srpaulostatic void rx_anqp_hs_nai_home_realm(struct hostapd_data *hapd,
703252190Srpaulo				      const u8 *pos, const u8 *end,
704252190Srpaulo				      struct anqp_query_info *qi)
705252190Srpaulo{
706252190Srpaulo	qi->request |= ANQP_REQ_NAI_HOME_REALM;
707252190Srpaulo	qi->home_realm_query = pos;
708252190Srpaulo	qi->home_realm_query_len = end - pos;
709252190Srpaulo	if (hapd->conf->nai_realm_data != NULL) {
710252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: HS 2.0 NAI Home Realm Query "
711252190Srpaulo			   "(local)");
712252190Srpaulo	} else {
713252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: HS 2.0 NAI Home Realm Query not "
714252190Srpaulo			   "available");
715252190Srpaulo	}
716252190Srpaulo}
717252190Srpaulo
718252190Srpaulo
719252190Srpaulostatic void rx_anqp_vendor_specific(struct hostapd_data *hapd,
720252190Srpaulo				    const u8 *pos, const u8 *end,
721252190Srpaulo				    struct anqp_query_info *qi)
722252190Srpaulo{
723252190Srpaulo	u32 oui;
724252190Srpaulo	u8 subtype;
725252190Srpaulo
726252190Srpaulo	if (pos + 4 > end) {
727252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: Too short vendor specific ANQP "
728252190Srpaulo			   "Query element");
729252190Srpaulo		return;
730252190Srpaulo	}
731252190Srpaulo
732252190Srpaulo	oui = WPA_GET_BE24(pos);
733252190Srpaulo	pos += 3;
734252190Srpaulo	if (oui != OUI_WFA) {
735252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: Unsupported vendor OUI %06x",
736252190Srpaulo			   oui);
737252190Srpaulo		return;
738252190Srpaulo	}
739252190Srpaulo
740252190Srpaulo	if (*pos != HS20_ANQP_OUI_TYPE) {
741252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: Unsupported WFA vendor type %u",
742252190Srpaulo			   *pos);
743252190Srpaulo		return;
744252190Srpaulo	}
745252190Srpaulo	pos++;
746252190Srpaulo
747252190Srpaulo	if (pos + 1 >= end)
748252190Srpaulo		return;
749252190Srpaulo
750252190Srpaulo	subtype = *pos++;
751252190Srpaulo	pos++; /* Reserved */
752252190Srpaulo	switch (subtype) {
753252190Srpaulo	case HS20_STYPE_QUERY_LIST:
754252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: HS 2.0 Query List");
755252190Srpaulo		while (pos < end) {
756252190Srpaulo			rx_anqp_hs_query_list(hapd, *pos, qi);
757252190Srpaulo			pos++;
758252190Srpaulo		}
759252190Srpaulo		break;
760252190Srpaulo	case HS20_STYPE_NAI_HOME_REALM_QUERY:
761252190Srpaulo		rx_anqp_hs_nai_home_realm(hapd, pos, end, qi);
762252190Srpaulo		break;
763252190Srpaulo	default:
764252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: Unsupported HS 2.0 query subtype "
765252190Srpaulo			   "%u", subtype);
766252190Srpaulo		break;
767252190Srpaulo	}
768252190Srpaulo}
769252190Srpaulo
770252190Srpaulo#endif /* CONFIG_HS20 */
771252190Srpaulo
772252190Srpaulo
773252190Srpaulostatic void gas_serv_req_local_processing(struct hostapd_data *hapd,
774252190Srpaulo					  const u8 *sa, u8 dialog_token,
775252190Srpaulo					  struct anqp_query_info *qi)
776252190Srpaulo{
777252190Srpaulo	struct wpabuf *buf, *tx_buf;
778252190Srpaulo
779252190Srpaulo	buf = gas_serv_build_gas_resp_payload(hapd, qi->request, NULL,
780252190Srpaulo					      qi->home_realm_query,
781252190Srpaulo					      qi->home_realm_query_len);
782252190Srpaulo	wpa_hexdump_buf(MSG_MSGDUMP, "ANQP: Locally generated ANQP responses",
783252190Srpaulo			buf);
784252190Srpaulo	if (!buf)
785252190Srpaulo		return;
786252190Srpaulo
787252190Srpaulo	if (wpabuf_len(buf) > hapd->gas_frag_limit ||
788252190Srpaulo	    hapd->conf->gas_comeback_delay) {
789252190Srpaulo		struct gas_dialog_info *di;
790252190Srpaulo		u16 comeback_delay = 1;
791252190Srpaulo
792252190Srpaulo		if (hapd->conf->gas_comeback_delay) {
793252190Srpaulo			/* Testing - allow overriding of the delay value */
794252190Srpaulo			comeback_delay = hapd->conf->gas_comeback_delay;
795252190Srpaulo		}
796252190Srpaulo
797252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: Too long response to fit in "
798252190Srpaulo			   "initial response - use GAS comeback");
799252190Srpaulo		di = gas_dialog_create(hapd, sa, dialog_token);
800252190Srpaulo		if (!di) {
801252190Srpaulo			wpa_printf(MSG_INFO, "ANQP: Could not create dialog "
802252190Srpaulo				   "for " MACSTR " (dialog token %u)",
803252190Srpaulo				   MAC2STR(sa), dialog_token);
804252190Srpaulo			wpabuf_free(buf);
805252190Srpaulo			return;
806252190Srpaulo		}
807252190Srpaulo		di->sd_resp = buf;
808252190Srpaulo		di->sd_resp_pos = 0;
809252190Srpaulo		tx_buf = gas_anqp_build_initial_resp_buf(
810252190Srpaulo			dialog_token, WLAN_STATUS_SUCCESS, comeback_delay,
811252190Srpaulo			NULL);
812252190Srpaulo	} else {
813252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: Initial response (no comeback)");
814252190Srpaulo		tx_buf = gas_anqp_build_initial_resp_buf(
815252190Srpaulo			dialog_token, WLAN_STATUS_SUCCESS, 0, buf);
816252190Srpaulo		wpabuf_free(buf);
817252190Srpaulo	}
818252190Srpaulo	if (!tx_buf)
819252190Srpaulo		return;
820252190Srpaulo
821252190Srpaulo	hostapd_drv_send_action(hapd, hapd->iface->freq, 0, sa,
822252190Srpaulo				wpabuf_head(tx_buf), wpabuf_len(tx_buf));
823252190Srpaulo	wpabuf_free(tx_buf);
824252190Srpaulo}
825252190Srpaulo
826252190Srpaulo
827252190Srpaulostatic void gas_serv_rx_gas_initial_req(struct hostapd_data *hapd,
828252190Srpaulo					const u8 *sa,
829252190Srpaulo					const u8 *data, size_t len)
830252190Srpaulo{
831252190Srpaulo	const u8 *pos = data;
832252190Srpaulo	const u8 *end = data + len;
833252190Srpaulo	const u8 *next;
834252190Srpaulo	u8 dialog_token;
835252190Srpaulo	u16 slen;
836252190Srpaulo	struct anqp_query_info qi;
837252190Srpaulo	const u8 *adv_proto;
838252190Srpaulo
839252190Srpaulo	if (len < 1 + 2)
840252190Srpaulo		return;
841252190Srpaulo
842252190Srpaulo	os_memset(&qi, 0, sizeof(qi));
843252190Srpaulo
844252190Srpaulo	dialog_token = *pos++;
845252190Srpaulo	wpa_msg(hapd->msg_ctx, MSG_DEBUG,
846252190Srpaulo		"GAS: GAS Initial Request from " MACSTR " (dialog token %u) ",
847252190Srpaulo		MAC2STR(sa), dialog_token);
848252190Srpaulo
849252190Srpaulo	if (*pos != WLAN_EID_ADV_PROTO) {
850252190Srpaulo		wpa_msg(hapd->msg_ctx, MSG_DEBUG,
851252190Srpaulo			"GAS: Unexpected IE in GAS Initial Request: %u", *pos);
852252190Srpaulo		return;
853252190Srpaulo	}
854252190Srpaulo	adv_proto = pos++;
855252190Srpaulo
856252190Srpaulo	slen = *pos++;
857252190Srpaulo	next = pos + slen;
858252190Srpaulo	if (next > end || slen < 2) {
859252190Srpaulo		wpa_msg(hapd->msg_ctx, MSG_DEBUG,
860252190Srpaulo			"GAS: Invalid IE in GAS Initial Request");
861252190Srpaulo		return;
862252190Srpaulo	}
863252190Srpaulo	pos++; /* skip QueryRespLenLimit and PAME-BI */
864252190Srpaulo
865252190Srpaulo	if (*pos != ACCESS_NETWORK_QUERY_PROTOCOL) {
866252190Srpaulo		struct wpabuf *buf;
867252190Srpaulo		wpa_msg(hapd->msg_ctx, MSG_DEBUG,
868252190Srpaulo			"GAS: Unsupported GAS advertisement protocol id %u",
869252190Srpaulo			*pos);
870252190Srpaulo		if (sa[0] & 0x01)
871252190Srpaulo			return; /* Invalid source address - drop silently */
872252190Srpaulo		buf = gas_build_initial_resp(
873252190Srpaulo			dialog_token, WLAN_STATUS_GAS_ADV_PROTO_NOT_SUPPORTED,
874252190Srpaulo			0, 2 + slen + 2);
875252190Srpaulo		if (buf == NULL)
876252190Srpaulo			return;
877252190Srpaulo		wpabuf_put_data(buf, adv_proto, 2 + slen);
878252190Srpaulo		wpabuf_put_le16(buf, 0); /* Query Response Length */
879252190Srpaulo		hostapd_drv_send_action(hapd, hapd->iface->freq, 0, sa,
880252190Srpaulo					wpabuf_head(buf), wpabuf_len(buf));
881252190Srpaulo		wpabuf_free(buf);
882252190Srpaulo		return;
883252190Srpaulo	}
884252190Srpaulo
885252190Srpaulo	pos = next;
886252190Srpaulo	/* Query Request */
887252190Srpaulo	if (pos + 2 > end)
888252190Srpaulo		return;
889252190Srpaulo	slen = WPA_GET_LE16(pos);
890252190Srpaulo	pos += 2;
891252190Srpaulo	if (pos + slen > end)
892252190Srpaulo		return;
893252190Srpaulo	end = pos + slen;
894252190Srpaulo
895252190Srpaulo	/* ANQP Query Request */
896252190Srpaulo	while (pos < end) {
897252190Srpaulo		u16 info_id, elen;
898252190Srpaulo
899252190Srpaulo		if (pos + 4 > end)
900252190Srpaulo			return;
901252190Srpaulo
902252190Srpaulo		info_id = WPA_GET_LE16(pos);
903252190Srpaulo		pos += 2;
904252190Srpaulo		elen = WPA_GET_LE16(pos);
905252190Srpaulo		pos += 2;
906252190Srpaulo
907252190Srpaulo		if (pos + elen > end) {
908252190Srpaulo			wpa_printf(MSG_DEBUG, "ANQP: Invalid Query Request");
909252190Srpaulo			return;
910252190Srpaulo		}
911252190Srpaulo
912252190Srpaulo		switch (info_id) {
913252190Srpaulo		case ANQP_QUERY_LIST:
914252190Srpaulo			rx_anqp_query_list(hapd, pos, pos + elen, &qi);
915252190Srpaulo			break;
916252190Srpaulo#ifdef CONFIG_HS20
917252190Srpaulo		case ANQP_VENDOR_SPECIFIC:
918252190Srpaulo			rx_anqp_vendor_specific(hapd, pos, pos + elen, &qi);
919252190Srpaulo			break;
920252190Srpaulo#endif /* CONFIG_HS20 */
921252190Srpaulo		default:
922252190Srpaulo			wpa_printf(MSG_DEBUG, "ANQP: Unsupported Query "
923252190Srpaulo				   "Request element %u", info_id);
924252190Srpaulo			break;
925252190Srpaulo		}
926252190Srpaulo
927252190Srpaulo		pos += elen;
928252190Srpaulo	}
929252190Srpaulo
930252190Srpaulo	gas_serv_req_local_processing(hapd, sa, dialog_token, &qi);
931252190Srpaulo}
932252190Srpaulo
933252190Srpaulo
934252190Srpaulovoid gas_serv_tx_gas_response(struct hostapd_data *hapd, const u8 *dst,
935252190Srpaulo			      struct gas_dialog_info *dialog)
936252190Srpaulo{
937252190Srpaulo	struct wpabuf *buf, *tx_buf;
938252190Srpaulo	u8 dialog_token = dialog->dialog_token;
939252190Srpaulo	size_t frag_len;
940252190Srpaulo
941252190Srpaulo	if (dialog->sd_resp == NULL) {
942252190Srpaulo		buf = gas_serv_build_gas_resp_payload(hapd,
943252190Srpaulo						      dialog->all_requested,
944252190Srpaulo						      dialog, NULL, 0);
945252190Srpaulo		wpa_hexdump_buf(MSG_MSGDUMP, "ANQP: Generated ANQP responses",
946252190Srpaulo			buf);
947252190Srpaulo		if (!buf)
948252190Srpaulo			goto tx_gas_response_done;
949252190Srpaulo		dialog->sd_resp = buf;
950252190Srpaulo		dialog->sd_resp_pos = 0;
951252190Srpaulo	}
952252190Srpaulo	frag_len = wpabuf_len(dialog->sd_resp) - dialog->sd_resp_pos;
953252190Srpaulo	if (frag_len > hapd->gas_frag_limit || dialog->comeback_delay ||
954252190Srpaulo	    hapd->conf->gas_comeback_delay) {
955252190Srpaulo		u16 comeback_delay_tus = dialog->comeback_delay +
956252190Srpaulo			GAS_SERV_COMEBACK_DELAY_FUDGE;
957252190Srpaulo		u32 comeback_delay_secs, comeback_delay_usecs;
958252190Srpaulo
959252190Srpaulo		if (hapd->conf->gas_comeback_delay) {
960252190Srpaulo			/* Testing - allow overriding of the delay value */
961252190Srpaulo			comeback_delay_tus = hapd->conf->gas_comeback_delay;
962252190Srpaulo		}
963252190Srpaulo
964252190Srpaulo		wpa_printf(MSG_DEBUG, "GAS: Response frag_len %u (frag limit "
965252190Srpaulo			   "%u) and comeback delay %u, "
966252190Srpaulo			   "requesting comebacks", (unsigned int) frag_len,
967252190Srpaulo			   (unsigned int) hapd->gas_frag_limit,
968252190Srpaulo			   dialog->comeback_delay);
969252190Srpaulo		tx_buf = gas_anqp_build_initial_resp_buf(dialog_token,
970252190Srpaulo							 WLAN_STATUS_SUCCESS,
971252190Srpaulo							 comeback_delay_tus,
972252190Srpaulo							 NULL);
973252190Srpaulo		if (tx_buf) {
974252190Srpaulo			wpa_msg(hapd->msg_ctx, MSG_DEBUG,
975252190Srpaulo				"GAS: Tx GAS Initial Resp (comeback = 10TU)");
976252190Srpaulo			hostapd_drv_send_action(hapd, hapd->iface->freq, 0,
977252190Srpaulo						dst,
978252190Srpaulo						wpabuf_head(tx_buf),
979252190Srpaulo						wpabuf_len(tx_buf));
980252190Srpaulo		}
981252190Srpaulo		wpabuf_free(tx_buf);
982252190Srpaulo
983252190Srpaulo		/* start a timer of 1.5 * comeback-delay */
984252190Srpaulo		comeback_delay_tus = comeback_delay_tus +
985252190Srpaulo			(comeback_delay_tus / 2);
986252190Srpaulo		comeback_delay_secs = (comeback_delay_tus * 1024) / 1000000;
987252190Srpaulo		comeback_delay_usecs = (comeback_delay_tus * 1024) -
988252190Srpaulo			(comeback_delay_secs * 1000000);
989252190Srpaulo		eloop_register_timeout(comeback_delay_secs,
990252190Srpaulo				       comeback_delay_usecs,
991252190Srpaulo				       gas_serv_clear_cached_ies, dialog,
992252190Srpaulo				       NULL);
993252190Srpaulo		goto tx_gas_response_done;
994252190Srpaulo	}
995252190Srpaulo
996252190Srpaulo	buf = wpabuf_alloc_copy(wpabuf_head_u8(dialog->sd_resp) +
997252190Srpaulo				dialog->sd_resp_pos, frag_len);
998252190Srpaulo	if (buf == NULL) {
999252190Srpaulo		wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Buffer allocation "
1000252190Srpaulo			"failed");
1001252190Srpaulo		goto tx_gas_response_done;
1002252190Srpaulo	}
1003252190Srpaulo	tx_buf = gas_anqp_build_initial_resp_buf(dialog_token,
1004252190Srpaulo						 WLAN_STATUS_SUCCESS, 0, buf);
1005252190Srpaulo	wpabuf_free(buf);
1006252190Srpaulo	if (tx_buf == NULL)
1007252190Srpaulo		goto tx_gas_response_done;
1008252190Srpaulo	wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Tx GAS Initial "
1009252190Srpaulo		"Response (frag_id %d frag_len %d)",
1010252190Srpaulo		dialog->sd_frag_id, (int) frag_len);
1011252190Srpaulo	dialog->sd_frag_id++;
1012252190Srpaulo
1013252190Srpaulo	hostapd_drv_send_action(hapd, hapd->iface->freq, 0, dst,
1014252190Srpaulo				wpabuf_head(tx_buf), wpabuf_len(tx_buf));
1015252190Srpaulo	wpabuf_free(tx_buf);
1016252190Srpaulotx_gas_response_done:
1017252190Srpaulo	gas_serv_clear_cached_ies(dialog, NULL);
1018252190Srpaulo}
1019252190Srpaulo
1020252190Srpaulo
1021252190Srpaulostatic void gas_serv_rx_gas_comeback_req(struct hostapd_data *hapd,
1022252190Srpaulo					 const u8 *sa,
1023252190Srpaulo					 const u8 *data, size_t len)
1024252190Srpaulo{
1025252190Srpaulo	struct gas_dialog_info *dialog;
1026252190Srpaulo	struct wpabuf *buf, *tx_buf;
1027252190Srpaulo	u8 dialog_token;
1028252190Srpaulo	size_t frag_len;
1029252190Srpaulo	int more = 0;
1030252190Srpaulo
1031252190Srpaulo	wpa_hexdump(MSG_DEBUG, "GAS: RX GAS Comeback Request", data, len);
1032252190Srpaulo	if (len < 1)
1033252190Srpaulo		return;
1034252190Srpaulo	dialog_token = *data;
1035252190Srpaulo	wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Dialog Token: %u",
1036252190Srpaulo		dialog_token);
1037252190Srpaulo
1038252190Srpaulo	dialog = gas_serv_dialog_find(hapd, sa, dialog_token);
1039252190Srpaulo	if (!dialog) {
1040252190Srpaulo		wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: No pending SD "
1041252190Srpaulo			"response fragment for " MACSTR " dialog token %u",
1042252190Srpaulo			MAC2STR(sa), dialog_token);
1043252190Srpaulo
1044252190Srpaulo		if (sa[0] & 0x01)
1045252190Srpaulo			return; /* Invalid source address - drop silently */
1046252190Srpaulo		tx_buf = gas_anqp_build_comeback_resp_buf(
1047252190Srpaulo			dialog_token, WLAN_STATUS_NO_OUTSTANDING_GAS_REQ, 0, 0,
1048252190Srpaulo			0, NULL);
1049252190Srpaulo		if (tx_buf == NULL)
1050252190Srpaulo			return;
1051252190Srpaulo		goto send_resp;
1052252190Srpaulo	}
1053252190Srpaulo
1054252190Srpaulo	if (dialog->sd_resp == NULL) {
1055252190Srpaulo		wpa_printf(MSG_DEBUG, "GAS: Remote request 0x%x received 0x%x",
1056252190Srpaulo			   dialog->requested, dialog->received);
1057252190Srpaulo		if ((dialog->requested & dialog->received) !=
1058252190Srpaulo		    dialog->requested) {
1059252190Srpaulo			wpa_printf(MSG_DEBUG, "GAS: Did not receive response "
1060252190Srpaulo				   "from remote processing");
1061252190Srpaulo			gas_serv_dialog_clear(dialog);
1062252190Srpaulo			tx_buf = gas_anqp_build_comeback_resp_buf(
1063252190Srpaulo				dialog_token,
1064252190Srpaulo				WLAN_STATUS_GAS_RESP_NOT_RECEIVED, 0, 0, 0,
1065252190Srpaulo				NULL);
1066252190Srpaulo			if (tx_buf == NULL)
1067252190Srpaulo				return;
1068252190Srpaulo			goto send_resp;
1069252190Srpaulo		}
1070252190Srpaulo
1071252190Srpaulo		buf = gas_serv_build_gas_resp_payload(hapd,
1072252190Srpaulo						      dialog->all_requested,
1073252190Srpaulo						      dialog, NULL, 0);
1074252190Srpaulo		wpa_hexdump_buf(MSG_MSGDUMP, "ANQP: Generated ANQP responses",
1075252190Srpaulo			buf);
1076252190Srpaulo		if (!buf)
1077252190Srpaulo			goto rx_gas_comeback_req_done;
1078252190Srpaulo		dialog->sd_resp = buf;
1079252190Srpaulo		dialog->sd_resp_pos = 0;
1080252190Srpaulo	}
1081252190Srpaulo	frag_len = wpabuf_len(dialog->sd_resp) - dialog->sd_resp_pos;
1082252190Srpaulo	if (frag_len > hapd->gas_frag_limit) {
1083252190Srpaulo		frag_len = hapd->gas_frag_limit;
1084252190Srpaulo		more = 1;
1085252190Srpaulo	}
1086252190Srpaulo	wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: resp frag_len %u",
1087252190Srpaulo		(unsigned int) frag_len);
1088252190Srpaulo	buf = wpabuf_alloc_copy(wpabuf_head_u8(dialog->sd_resp) +
1089252190Srpaulo				dialog->sd_resp_pos, frag_len);
1090252190Srpaulo	if (buf == NULL) {
1091252190Srpaulo		wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Failed to allocate "
1092252190Srpaulo			"buffer");
1093252190Srpaulo		goto rx_gas_comeback_req_done;
1094252190Srpaulo	}
1095252190Srpaulo	tx_buf = gas_anqp_build_comeback_resp_buf(dialog_token,
1096252190Srpaulo						  WLAN_STATUS_SUCCESS,
1097252190Srpaulo						  dialog->sd_frag_id,
1098252190Srpaulo						  more, 0, buf);
1099252190Srpaulo	wpabuf_free(buf);
1100252190Srpaulo	if (tx_buf == NULL)
1101252190Srpaulo		goto rx_gas_comeback_req_done;
1102252190Srpaulo	wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Tx GAS Comeback Response "
1103252190Srpaulo		"(frag_id %d more=%d frag_len=%d)",
1104252190Srpaulo		dialog->sd_frag_id, more, (int) frag_len);
1105252190Srpaulo	dialog->sd_frag_id++;
1106252190Srpaulo	dialog->sd_resp_pos += frag_len;
1107252190Srpaulo
1108252190Srpaulo	if (more) {
1109252190Srpaulo		wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: %d more bytes remain "
1110252190Srpaulo			"to be sent",
1111252190Srpaulo			(int) (wpabuf_len(dialog->sd_resp) -
1112252190Srpaulo			       dialog->sd_resp_pos));
1113252190Srpaulo	} else {
1114252190Srpaulo		wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: All fragments of "
1115252190Srpaulo			"SD response sent");
1116252190Srpaulo		gas_serv_dialog_clear(dialog);
1117252190Srpaulo		gas_serv_free_dialogs(hapd, sa);
1118252190Srpaulo	}
1119252190Srpaulo
1120252190Srpaulosend_resp:
1121252190Srpaulo	hostapd_drv_send_action(hapd, hapd->iface->freq, 0, sa,
1122252190Srpaulo				wpabuf_head(tx_buf), wpabuf_len(tx_buf));
1123252190Srpaulo	wpabuf_free(tx_buf);
1124252190Srpaulo	return;
1125252190Srpaulo
1126252190Srpaulorx_gas_comeback_req_done:
1127252190Srpaulo	gas_serv_clear_cached_ies(dialog, NULL);
1128252190Srpaulo}
1129252190Srpaulo
1130252190Srpaulo
1131252190Srpaulostatic void gas_serv_rx_public_action(void *ctx, const u8 *buf, size_t len,
1132252190Srpaulo				      int freq)
1133252190Srpaulo{
1134252190Srpaulo	struct hostapd_data *hapd = ctx;
1135252190Srpaulo	const struct ieee80211_mgmt *mgmt;
1136252190Srpaulo	size_t hdr_len;
1137252190Srpaulo	const u8 *sa, *data;
1138252190Srpaulo
1139252190Srpaulo	mgmt = (const struct ieee80211_mgmt *) buf;
1140252190Srpaulo	hdr_len = (const u8 *) &mgmt->u.action.u.vs_public_action.action - buf;
1141252190Srpaulo	if (hdr_len > len)
1142252190Srpaulo		return;
1143252190Srpaulo	if (mgmt->u.action.category != WLAN_ACTION_PUBLIC)
1144252190Srpaulo		return;
1145252190Srpaulo	sa = mgmt->sa;
1146252190Srpaulo	len -= hdr_len;
1147252190Srpaulo	data = &mgmt->u.action.u.public_action.action;
1148252190Srpaulo	switch (data[0]) {
1149252190Srpaulo	case WLAN_PA_GAS_INITIAL_REQ:
1150252190Srpaulo		gas_serv_rx_gas_initial_req(hapd, sa, data + 1, len - 1);
1151252190Srpaulo		break;
1152252190Srpaulo	case WLAN_PA_GAS_COMEBACK_REQ:
1153252190Srpaulo		gas_serv_rx_gas_comeback_req(hapd, sa, data + 1, len - 1);
1154252190Srpaulo		break;
1155252190Srpaulo	}
1156252190Srpaulo}
1157252190Srpaulo
1158252190Srpaulo
1159252190Srpauloint gas_serv_init(struct hostapd_data *hapd)
1160252190Srpaulo{
1161252190Srpaulo	hapd->public_action_cb = gas_serv_rx_public_action;
1162252190Srpaulo	hapd->public_action_cb_ctx = hapd;
1163252190Srpaulo	hapd->gas_frag_limit = 1400;
1164252190Srpaulo	if (hapd->conf->gas_frag_limit > 0)
1165252190Srpaulo		hapd->gas_frag_limit = hapd->conf->gas_frag_limit;
1166252190Srpaulo	return 0;
1167252190Srpaulo}
1168252190Srpaulo
1169252190Srpaulo
1170252190Srpaulovoid gas_serv_deinit(struct hostapd_data *hapd)
1171252190Srpaulo{
1172252190Srpaulo}
1173