1324714Scy/*
2324714Scy * hostapd / Client taxonomy
3324714Scy * Copyright (c) 2015 Google, Inc.
4324714Scy *
5324714Scy * This software may be distributed under the terms of the BSD license.
6324714Scy * See README for more details.
7324714Scy *
8324714Scy * Parse a series of IEs, as in Probe Request or (Re)Association Request frames,
9324714Scy * and render them to a descriptive string. The tag number of standard options
10324714Scy * is written to the string, while the vendor ID and subtag are written for
11324714Scy * vendor options.
12324714Scy *
13324714Scy * Example strings:
14324714Scy * 0,1,50,45,221(00904c,51)
15324714Scy * 0,1,33,36,48,45,221(00904c,51),221(0050f2,2)
16324714Scy */
17324714Scy
18324714Scy#include "utils/includes.h"
19324714Scy
20324714Scy#include "utils/common.h"
21324714Scy#include "common/wpa_ctrl.h"
22324714Scy#include "hostapd.h"
23324714Scy#include "sta_info.h"
24346981Scy#include "taxonomy.h"
25324714Scy
26324714Scy
27324714Scy/* Copy a string with no funny schtuff allowed; only alphanumerics. */
28324714Scystatic void no_mischief_strncpy(char *dst, const char *src, size_t n)
29324714Scy{
30324714Scy	size_t i;
31324714Scy
32324714Scy	for (i = 0; i < n; i++) {
33324714Scy		unsigned char s = src[i];
34324714Scy		int is_lower = s >= 'a' && s <= 'z';
35324714Scy		int is_upper = s >= 'A' && s <= 'Z';
36324714Scy		int is_digit = s >= '0' && s <= '9';
37324714Scy
38324714Scy		if (is_lower || is_upper || is_digit) {
39324714Scy			/* TODO: if any manufacturer uses Unicode within the
40324714Scy			 * WPS header, it will get mangled here. */
41324714Scy			dst[i] = s;
42324714Scy		} else {
43324714Scy			/* Note that even spaces will be transformed to
44324714Scy			 * underscores, so 'Nexus 7' will turn into 'Nexus_7'.
45324714Scy			 * This is deliberate, to make the string easier to
46324714Scy			 * parse. */
47324714Scy			dst[i] = '_';
48324714Scy		}
49324714Scy	}
50324714Scy}
51324714Scy
52324714Scy
53324714Scystatic int get_wps_name(char *name, size_t name_len,
54324714Scy			const u8 *data, size_t data_len)
55324714Scy{
56324714Scy	/* Inside the WPS IE are a series of attributes, using two byte IDs
57324714Scy	 * and two byte lengths. We're looking for the model name, if
58324714Scy	 * present. */
59324714Scy	while (data_len >= 4) {
60324714Scy		u16 id, elen;
61324714Scy
62324714Scy		id = WPA_GET_BE16(data);
63324714Scy		elen = WPA_GET_BE16(data + 2);
64324714Scy		data += 4;
65324714Scy		data_len -= 4;
66324714Scy
67324714Scy		if (elen > data_len)
68324714Scy			return 0;
69324714Scy
70324714Scy		if (id == 0x1023) {
71324714Scy			/* Model name, like 'Nexus 7' */
72324714Scy			size_t n = (elen < name_len) ? elen : name_len;
73324714Scy			no_mischief_strncpy(name, (const char *) data, n);
74324714Scy			return n;
75324714Scy		}
76324714Scy
77324714Scy		data += elen;
78324714Scy		data_len -= elen;
79324714Scy	}
80324714Scy
81324714Scy	return 0;
82324714Scy}
83324714Scy
84324714Scy
85324714Scystatic void ie_to_string(char *fstr, size_t fstr_len, const struct wpabuf *ies)
86324714Scy{
87324714Scy	char *fpos = fstr;
88324714Scy	char *fend = fstr + fstr_len;
89324714Scy	char htcap[7 + 4 + 1]; /* ",htcap:" + %04hx + trailing NUL */
90324714Scy	char htagg[7 + 2 + 1]; /* ",htagg:" + %02hx + trailing NUL */
91324714Scy	char htmcs[7 + 8 + 1]; /* ",htmcs:" + %08x + trailing NUL */
92324714Scy	char vhtcap[8 + 8 + 1]; /* ",vhtcap:" + %08x + trailing NUL */
93324714Scy	char vhtrxmcs[10 + 8 + 1]; /* ",vhtrxmcs:" + %08x + trailing NUL */
94324714Scy	char vhttxmcs[10 + 8 + 1]; /* ",vhttxmcs:" + %08x + trailing NUL */
95324714Scy#define MAX_EXTCAP	254
96324714Scy	char extcap[8 + 2 * MAX_EXTCAP + 1]; /* ",extcap:" + hex + trailing NUL
97324714Scy					      */
98324714Scy	char txpow[7 + 4 + 1]; /* ",txpow:" + %04hx + trailing NUL */
99324714Scy#define WPS_NAME_LEN		32
100324714Scy	char wps[WPS_NAME_LEN + 5 + 1]; /* room to prepend ",wps:" + trailing
101324714Scy					 * NUL */
102324714Scy	int num = 0;
103324714Scy	const u8 *ie;
104324714Scy	size_t ie_len;
105324714Scy	int ret;
106324714Scy
107324714Scy	os_memset(htcap, 0, sizeof(htcap));
108324714Scy	os_memset(htagg, 0, sizeof(htagg));
109324714Scy	os_memset(htmcs, 0, sizeof(htmcs));
110324714Scy	os_memset(vhtcap, 0, sizeof(vhtcap));
111324714Scy	os_memset(vhtrxmcs, 0, sizeof(vhtrxmcs));
112324714Scy	os_memset(vhttxmcs, 0, sizeof(vhttxmcs));
113324714Scy	os_memset(extcap, 0, sizeof(extcap));
114324714Scy	os_memset(txpow, 0, sizeof(txpow));
115324714Scy	os_memset(wps, 0, sizeof(wps));
116324714Scy	*fpos = '\0';
117324714Scy
118324714Scy	if (!ies)
119324714Scy		return;
120324714Scy	ie = wpabuf_head(ies);
121324714Scy	ie_len = wpabuf_len(ies);
122324714Scy
123324714Scy	while (ie_len >= 2) {
124324714Scy		u8 id, elen;
125324714Scy		char *sep = (num++ == 0) ? "" : ",";
126324714Scy
127324714Scy		id = *ie++;
128324714Scy		elen = *ie++;
129324714Scy		ie_len -= 2;
130324714Scy
131324714Scy		if (elen > ie_len)
132324714Scy			break;
133324714Scy
134324714Scy		if (id == WLAN_EID_VENDOR_SPECIFIC && elen >= 4) {
135324714Scy			/* Vendor specific */
136324714Scy			if (WPA_GET_BE32(ie) == WPS_IE_VENDOR_TYPE) {
137324714Scy				/* WPS */
138324714Scy				char model_name[WPS_NAME_LEN + 1];
139324714Scy				const u8 *data = &ie[4];
140324714Scy				size_t data_len = elen - 4;
141324714Scy
142324714Scy				os_memset(model_name, 0, sizeof(model_name));
143324714Scy				if (get_wps_name(model_name, WPS_NAME_LEN, data,
144324714Scy						 data_len)) {
145324714Scy					os_snprintf(wps, sizeof(wps),
146324714Scy						    ",wps:%s", model_name);
147324714Scy				}
148324714Scy			}
149324714Scy
150324714Scy			ret = os_snprintf(fpos, fend - fpos,
151324714Scy					  "%s%d(%02x%02x%02x,%d)",
152324714Scy					  sep, id, ie[0], ie[1], ie[2], ie[3]);
153324714Scy		} else {
154324714Scy			if (id == WLAN_EID_HT_CAP && elen >= 2) {
155324714Scy				/* HT Capabilities (802.11n) */
156324714Scy				os_snprintf(htcap, sizeof(htcap),
157324714Scy					    ",htcap:%04hx",
158324714Scy					    WPA_GET_LE16(ie));
159324714Scy			}
160324714Scy			if (id == WLAN_EID_HT_CAP && elen >= 3) {
161324714Scy				/* HT Capabilities (802.11n), A-MPDU information
162324714Scy				 */
163324714Scy				os_snprintf(htagg, sizeof(htagg),
164324714Scy					    ",htagg:%02hx", (u16) ie[2]);
165324714Scy			}
166324714Scy			if (id == WLAN_EID_HT_CAP && elen >= 7) {
167324714Scy				/* HT Capabilities (802.11n), MCS information */
168324714Scy				os_snprintf(htmcs, sizeof(htmcs),
169324714Scy					    ",htmcs:%08hx",
170324714Scy					    (u16) WPA_GET_LE32(ie + 3));
171324714Scy			}
172324714Scy			if (id == WLAN_EID_VHT_CAP && elen >= 4) {
173324714Scy				/* VHT Capabilities (802.11ac) */
174324714Scy				os_snprintf(vhtcap, sizeof(vhtcap),
175324714Scy					    ",vhtcap:%08x",
176324714Scy					    WPA_GET_LE32(ie));
177324714Scy			}
178324714Scy			if (id == WLAN_EID_VHT_CAP && elen >= 8) {
179324714Scy				/* VHT Capabilities (802.11ac), RX MCS
180324714Scy				 * information */
181324714Scy				os_snprintf(vhtrxmcs, sizeof(vhtrxmcs),
182324714Scy					    ",vhtrxmcs:%08x",
183324714Scy					    WPA_GET_LE32(ie + 4));
184324714Scy			}
185324714Scy			if (id == WLAN_EID_VHT_CAP && elen >= 12) {
186324714Scy				/* VHT Capabilities (802.11ac), TX MCS
187324714Scy				 * information */
188324714Scy				os_snprintf(vhttxmcs, sizeof(vhttxmcs),
189324714Scy					    ",vhttxmcs:%08x",
190324714Scy					    WPA_GET_LE32(ie + 8));
191324714Scy			}
192324714Scy			if (id == WLAN_EID_EXT_CAPAB) {
193324714Scy				/* Extended Capabilities */
194324714Scy				int i;
195324714Scy				int len = (elen < MAX_EXTCAP) ? elen :
196324714Scy					MAX_EXTCAP;
197324714Scy				char *p = extcap;
198324714Scy
199324714Scy				p += os_snprintf(extcap, sizeof(extcap),
200324714Scy						 ",extcap:");
201324714Scy				for (i = 0; i < len; i++) {
202324714Scy					int lim;
203324714Scy
204324714Scy					lim = sizeof(extcap) -
205324714Scy						os_strlen(extcap);
206324714Scy					if (lim <= 0)
207324714Scy						break;
208324714Scy					p += os_snprintf(p, lim, "%02x",
209324714Scy							 *(ie + i));
210324714Scy				}
211324714Scy			}
212324714Scy			if (id == WLAN_EID_PWR_CAPABILITY && elen == 2) {
213324714Scy				/* TX Power */
214324714Scy				os_snprintf(txpow, sizeof(txpow),
215324714Scy					    ",txpow:%04hx",
216324714Scy					    WPA_GET_LE16(ie));
217324714Scy			}
218324714Scy
219324714Scy			ret = os_snprintf(fpos, fend - fpos, "%s%d", sep, id);
220324714Scy		}
221324714Scy		if (os_snprintf_error(fend - fpos, ret))
222324714Scy			goto fail;
223324714Scy		fpos += ret;
224324714Scy
225324714Scy		ie += elen;
226324714Scy		ie_len -= elen;
227324714Scy	}
228324714Scy
229324714Scy	ret = os_snprintf(fpos, fend - fpos, "%s%s%s%s%s%s%s%s%s",
230324714Scy			  htcap, htagg, htmcs, vhtcap, vhtrxmcs, vhttxmcs,
231324714Scy			  txpow, extcap, wps);
232324714Scy	if (os_snprintf_error(fend - fpos, ret)) {
233324714Scy	fail:
234324714Scy		fstr[0] = '\0';
235324714Scy	}
236324714Scy}
237324714Scy
238324714Scy
239324714Scyint retrieve_sta_taxonomy(const struct hostapd_data *hapd,
240324714Scy			  struct sta_info *sta, char *buf, size_t buflen)
241324714Scy{
242324714Scy	int ret;
243324714Scy	char *pos, *end;
244324714Scy
245324714Scy	if (!sta->probe_ie_taxonomy || !sta->assoc_ie_taxonomy)
246324714Scy		return 0;
247324714Scy
248324714Scy	ret = os_snprintf(buf, buflen, "wifi4|probe:");
249324714Scy	if (os_snprintf_error(buflen, ret))
250324714Scy		return 0;
251324714Scy	pos = buf + ret;
252324714Scy	end = buf + buflen;
253324714Scy
254324714Scy	ie_to_string(pos, end - pos, sta->probe_ie_taxonomy);
255324714Scy	pos = os_strchr(pos, '\0');
256324714Scy	if (pos >= end)
257324714Scy		return 0;
258324714Scy	ret = os_snprintf(pos, end - pos, "|assoc:");
259324714Scy	if (os_snprintf_error(end - pos, ret))
260324714Scy		return 0;
261324714Scy	pos += ret;
262324714Scy	ie_to_string(pos, end - pos, sta->assoc_ie_taxonomy);
263324714Scy	pos = os_strchr(pos, '\0');
264324714Scy	return pos - buf;
265324714Scy}
266324714Scy
267324714Scy
268324714Scyvoid taxonomy_sta_info_probe_req(const struct hostapd_data *hapd,
269324714Scy				 struct sta_info *sta,
270324714Scy				 const u8 *ie, size_t ie_len)
271324714Scy{
272324714Scy	wpabuf_free(sta->probe_ie_taxonomy);
273324714Scy	sta->probe_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len);
274324714Scy}
275324714Scy
276324714Scy
277324714Scyvoid taxonomy_hostapd_sta_info_probe_req(const struct hostapd_data *hapd,
278324714Scy					 struct hostapd_sta_info *info,
279324714Scy					 const u8 *ie, size_t ie_len)
280324714Scy{
281324714Scy	wpabuf_free(info->probe_ie_taxonomy);
282324714Scy	info->probe_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len);
283324714Scy}
284324714Scy
285324714Scy
286324714Scyvoid taxonomy_sta_info_assoc_req(const struct hostapd_data *hapd,
287324714Scy				 struct sta_info *sta,
288324714Scy				 const u8 *ie, size_t ie_len)
289324714Scy{
290324714Scy	wpabuf_free(sta->assoc_ie_taxonomy);
291324714Scy	sta->assoc_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len);
292324714Scy}
293