]>
Commit | Line | Data |
---|---|---|
fa201b69 JM |
1 | /* |
2 | * wpa_supplicant / WPS integration | |
3 | * Copyright (c) 2008, Jouni Malinen <j@w1.fi> | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License version 2 as | |
7 | * published by the Free Software Foundation. | |
8 | * | |
9 | * Alternatively, this software may be distributed under the terms of BSD | |
10 | * license. | |
11 | * | |
12 | * See README and COPYING for more details. | |
13 | */ | |
14 | ||
15 | #include "includes.h" | |
16 | ||
17 | #include "common.h" | |
18 | #include "ieee802_11_defs.h" | |
19 | #include "wpa_common.h" | |
20 | #include "config.h" | |
b01c18a8 | 21 | #include "eap_peer/eap.h" |
fa201b69 | 22 | #include "wpa_supplicant_i.h" |
fcc60db4 JM |
23 | #include "eloop.h" |
24 | #include "eap_common/eap_wsc_common.h" | |
fa201b69 JM |
25 | #include "wps/wps.h" |
26 | #include "wps/wps_defs.h" | |
27 | #include "wps_supplicant.h" | |
28 | ||
29 | ||
fcc60db4 JM |
30 | static void wpas_wps_timeout(void *eloop_ctx, void *timeout_ctx); |
31 | ||
32 | ||
fa201b69 JM |
33 | int wpas_wps_eapol_cb(struct wpa_supplicant *wpa_s) |
34 | { | |
fcc60db4 JM |
35 | eloop_cancel_timeout(wpas_wps_timeout, wpa_s, NULL); |
36 | ||
fa201b69 JM |
37 | if (wpa_s->key_mgmt == WPA_KEY_MGMT_WPS && wpa_s->current_ssid && |
38 | !(wpa_s->current_ssid->key_mgmt & WPA_KEY_MGMT_WPS)) { | |
39 | wpa_printf(MSG_DEBUG, "WPS: Network configuration replaced - " | |
40 | "try to associate with the received credential"); | |
41 | wpa_supplicant_deauthenticate(wpa_s, | |
42 | WLAN_REASON_DEAUTH_LEAVING); | |
43 | wpa_s->reassociate = 1; | |
44 | wpa_supplicant_req_scan(wpa_s, 0, 0); | |
45 | return 1; | |
46 | } | |
47 | ||
48 | return 0; | |
49 | } | |
50 | ||
51 | ||
bcbbc7af JM |
52 | static int wpa_supplicant_wps_cred(void *ctx, |
53 | const struct wps_credential *cred) | |
fa201b69 JM |
54 | { |
55 | struct wpa_supplicant *wpa_s = ctx; | |
56 | struct wpa_ssid *ssid = wpa_s->current_ssid; | |
57 | ||
58 | wpa_msg(wpa_s, MSG_INFO, "WPS: New credential received"); | |
59 | ||
60 | if (ssid && (ssid->key_mgmt & WPA_KEY_MGMT_WPS)) { | |
61 | wpa_printf(MSG_DEBUG, "WPS: Replace WPS network block based " | |
62 | "on the received credential"); | |
63 | os_free(ssid->eap.identity); | |
64 | ssid->eap.identity = NULL; | |
65 | ssid->eap.identity_len = 0; | |
66 | os_free(ssid->eap.phase1); | |
67 | ssid->eap.phase1 = NULL; | |
68 | os_free(ssid->eap.eap_methods); | |
69 | ssid->eap.eap_methods = NULL; | |
70 | } else { | |
71 | wpa_printf(MSG_DEBUG, "WPS: Create a new network based on the " | |
72 | "received credential"); | |
73 | ssid = wpa_config_add_network(wpa_s->conf); | |
74 | if (ssid == NULL) | |
75 | return -1; | |
76 | } | |
77 | ||
78 | wpa_config_set_network_defaults(ssid); | |
79 | ||
80 | os_free(ssid->ssid); | |
81 | ssid->ssid = os_malloc(cred->ssid_len); | |
82 | if (ssid->ssid) { | |
83 | os_memcpy(ssid->ssid, cred->ssid, cred->ssid_len); | |
84 | ssid->ssid_len = cred->ssid_len; | |
85 | } | |
86 | ||
87 | switch (cred->encr_type) { | |
88 | case WPS_ENCR_NONE: | |
89 | ssid->pairwise_cipher = ssid->group_cipher = WPA_CIPHER_NONE; | |
90 | break; | |
91 | case WPS_ENCR_WEP: | |
92 | ssid->pairwise_cipher = ssid->group_cipher = | |
93 | WPA_CIPHER_WEP40 | WPA_CIPHER_WEP104; | |
94 | if (cred->key_len > 0 && cred->key_len <= MAX_WEP_KEY_LEN && | |
95 | cred->key_idx < NUM_WEP_KEYS) { | |
96 | os_memcpy(ssid->wep_key[cred->key_idx], cred->key, | |
97 | cred->key_len); | |
98 | ssid->wep_key_len[cred->key_idx] = cred->key_len; | |
99 | ssid->wep_tx_keyidx = cred->key_idx; | |
100 | } | |
101 | break; | |
102 | case WPS_ENCR_TKIP: | |
103 | ssid->pairwise_cipher = WPA_CIPHER_TKIP; | |
104 | ssid->group_cipher = WPA_CIPHER_TKIP; | |
105 | break; | |
106 | case WPS_ENCR_AES: | |
107 | ssid->pairwise_cipher = WPA_CIPHER_CCMP; | |
108 | ssid->group_cipher = WPA_CIPHER_CCMP | WPA_CIPHER_TKIP; | |
109 | break; | |
110 | } | |
111 | ||
112 | switch (cred->auth_type) { | |
113 | case WPS_AUTH_OPEN: | |
114 | ssid->auth_alg = WPA_AUTH_ALG_OPEN; | |
115 | ssid->key_mgmt = WPA_KEY_MGMT_NONE; | |
116 | ssid->proto = 0; | |
117 | break; | |
118 | case WPS_AUTH_SHARED: | |
119 | ssid->auth_alg = WPA_AUTH_ALG_SHARED; | |
120 | ssid->key_mgmt = WPA_KEY_MGMT_NONE; | |
121 | ssid->proto = 0; | |
122 | break; | |
123 | case WPS_AUTH_WPAPSK: | |
124 | ssid->auth_alg = WPA_AUTH_ALG_OPEN; | |
125 | ssid->key_mgmt = WPA_KEY_MGMT_PSK; | |
126 | ssid->proto = WPA_PROTO_WPA; | |
127 | break; | |
128 | case WPS_AUTH_WPA: | |
129 | ssid->auth_alg = WPA_AUTH_ALG_OPEN; | |
130 | ssid->key_mgmt = WPA_KEY_MGMT_IEEE8021X; | |
131 | ssid->proto = WPA_PROTO_WPA; | |
132 | break; | |
133 | case WPS_AUTH_WPA2: | |
134 | ssid->auth_alg = WPA_AUTH_ALG_OPEN; | |
135 | ssid->key_mgmt = WPA_KEY_MGMT_IEEE8021X; | |
136 | ssid->proto = WPA_PROTO_RSN; | |
137 | break; | |
138 | case WPS_AUTH_WPA2PSK: | |
139 | ssid->auth_alg = WPA_AUTH_ALG_OPEN; | |
140 | ssid->key_mgmt = WPA_KEY_MGMT_PSK; | |
141 | ssid->proto = WPA_PROTO_RSN; | |
142 | break; | |
143 | } | |
144 | ||
145 | if (ssid->key_mgmt == WPA_KEY_MGMT_PSK) { | |
146 | if (cred->key_len == 2 * PMK_LEN) { | |
147 | if (hexstr2bin((const char *) cred->key, ssid->psk, | |
148 | PMK_LEN)) { | |
149 | wpa_printf(MSG_ERROR, "WPS: Invalid Network " | |
150 | "Key"); | |
151 | return -1; | |
152 | } | |
153 | ssid->psk_set = 1; | |
154 | } else if (cred->key_len >= 8 && cred->key_len < 2 * PMK_LEN) { | |
155 | os_free(ssid->passphrase); | |
156 | ssid->passphrase = os_malloc(cred->key_len + 1); | |
157 | if (ssid->passphrase == NULL) | |
158 | return -1; | |
159 | os_memcpy(ssid->passphrase, cred->key, cred->key_len); | |
160 | ssid->passphrase[cred->key_len] = '\0'; | |
161 | wpa_config_update_psk(ssid); | |
162 | } else { | |
163 | wpa_printf(MSG_ERROR, "WPS: Invalid Network Key " | |
164 | "length %lu", | |
165 | (unsigned long) cred->key_len); | |
166 | return -1; | |
167 | } | |
168 | } | |
169 | ||
170 | #ifndef CONFIG_NO_CONFIG_WRITE | |
171 | if (wpa_s->conf->update_config && | |
172 | wpa_config_write(wpa_s->confname, wpa_s->conf)) { | |
173 | wpa_printf(MSG_DEBUG, "WPS: Failed to update configuration"); | |
174 | return -1; | |
175 | } | |
176 | #endif /* CONFIG_NO_CONFIG_WRITE */ | |
177 | ||
178 | return 0; | |
179 | } | |
180 | ||
181 | ||
b01c18a8 | 182 | u8 wpas_wps_get_req_type(struct wpa_ssid *ssid) |
fa201b69 | 183 | { |
b01c18a8 JM |
184 | if (eap_is_wps_pbc_enrollee(&ssid->eap) || |
185 | eap_is_wps_pin_enrollee(&ssid->eap)) | |
186 | return WPS_REQ_ENROLLEE; | |
187 | else | |
188 | return WPS_REQ_REGISTRAR; | |
fa201b69 | 189 | } |
116654ce JM |
190 | |
191 | ||
fcc60db4 JM |
192 | static void wpas_clear_wps(struct wpa_supplicant *wpa_s) |
193 | { | |
194 | int id; | |
195 | struct wpa_ssid *ssid; | |
196 | ||
197 | eloop_cancel_timeout(wpas_wps_timeout, wpa_s, NULL); | |
198 | ||
199 | /* Remove any existing WPS network from configuration */ | |
200 | ssid = wpa_s->conf->ssid; | |
201 | while (ssid) { | |
202 | if (ssid->key_mgmt & WPA_KEY_MGMT_WPS) | |
203 | id = ssid->id; | |
204 | else | |
205 | id = -1; | |
206 | ssid = ssid->next; | |
207 | if (id >= 0) | |
208 | wpa_config_remove_network(wpa_s->conf, id); | |
209 | } | |
210 | } | |
211 | ||
212 | ||
213 | static void wpas_wps_timeout(void *eloop_ctx, void *timeout_ctx) | |
214 | { | |
215 | struct wpa_supplicant *wpa_s = eloop_ctx; | |
216 | wpa_printf(MSG_DEBUG, "WPS: Requested operation timed out"); | |
217 | wpas_clear_wps(wpa_s); | |
218 | } | |
219 | ||
220 | ||
221 | static struct wpa_ssid * wpas_wps_add_network(struct wpa_supplicant *wpa_s, | |
222 | int registrar, const u8 *bssid) | |
223 | { | |
224 | struct wpa_ssid *ssid; | |
225 | ||
226 | ssid = wpa_config_add_network(wpa_s->conf); | |
227 | if (ssid == NULL) | |
228 | return NULL; | |
229 | wpa_config_set_network_defaults(ssid); | |
230 | if (wpa_config_set(ssid, "key_mgmt", "WPS", 0) < 0 || | |
231 | wpa_config_set(ssid, "eap", "WSC", 0) < 0 || | |
232 | wpa_config_set(ssid, "identity", registrar ? | |
233 | "\"" WSC_ID_REGISTRAR "\"" : | |
234 | "\"" WSC_ID_ENROLLEE "\"", 0) < 0) { | |
235 | wpa_config_remove_network(wpa_s->conf, ssid->id); | |
236 | return NULL; | |
237 | } | |
238 | ||
239 | if (bssid) { | |
240 | size_t i; | |
241 | struct wpa_scan_res *res; | |
242 | ||
243 | os_memcpy(ssid->bssid, bssid, ETH_ALEN); | |
244 | ||
245 | /* Try to get SSID from scan results */ | |
246 | if (wpa_s->scan_res == NULL && | |
247 | wpa_supplicant_get_scan_results(wpa_s) < 0) | |
248 | return ssid; /* Could not find any scan results */ | |
249 | ||
250 | for (i = 0; i < wpa_s->scan_res->num; i++) { | |
251 | const u8 *ie; | |
252 | ||
253 | res = wpa_s->scan_res->res[i]; | |
254 | if (os_memcmp(bssid, res->bssid, ETH_ALEN) != 0) | |
255 | continue; | |
256 | ||
257 | ie = wpa_scan_get_ie(res, WLAN_EID_SSID); | |
258 | if (ie == NULL) | |
259 | break; | |
260 | os_free(ssid->ssid); | |
261 | ssid->ssid = os_malloc(ie[1]); | |
262 | if (ssid->ssid == NULL) | |
263 | break; | |
264 | os_memcpy(ssid->ssid, ie + 2, ie[1]); | |
265 | ssid->ssid_len = ie[1]; | |
266 | break; | |
267 | } | |
268 | } | |
269 | ||
270 | return ssid; | |
271 | } | |
272 | ||
273 | ||
274 | static void wpas_wps_reassoc(struct wpa_supplicant *wpa_s, | |
275 | struct wpa_ssid *selected) | |
276 | { | |
277 | struct wpa_ssid *ssid; | |
278 | ||
279 | /* Mark all other networks disabled and trigger reassociation */ | |
280 | ssid = wpa_s->conf->ssid; | |
281 | while (ssid) { | |
282 | ssid->disabled = ssid != selected; | |
283 | ssid = ssid->next; | |
284 | } | |
285 | wpa_s->disconnected = 0; | |
286 | wpa_s->reassociate = 1; | |
287 | wpa_supplicant_req_scan(wpa_s, 0, 0); | |
288 | } | |
289 | ||
290 | ||
291 | int wpas_wps_start_pbc(struct wpa_supplicant *wpa_s, const u8 *bssid) | |
292 | { | |
293 | struct wpa_ssid *ssid; | |
294 | wpas_clear_wps(wpa_s); | |
295 | ssid = wpas_wps_add_network(wpa_s, 0, bssid); | |
296 | if (ssid == NULL) | |
297 | return -1; | |
298 | wpa_config_set(ssid, "phase1", "\"pbc=1\"", 0); | |
299 | eloop_register_timeout(WPS_PBC_WALK_TIME, 0, wpas_wps_timeout, | |
300 | wpa_s, NULL); | |
301 | wpas_wps_reassoc(wpa_s, ssid); | |
302 | return 0; | |
303 | } | |
304 | ||
305 | ||
306 | int wpas_wps_start_pin(struct wpa_supplicant *wpa_s, const u8 *bssid, | |
307 | const char *pin) | |
308 | { | |
309 | struct wpa_ssid *ssid; | |
310 | char val[30]; | |
311 | unsigned int rpin = 0; | |
312 | ||
313 | wpas_clear_wps(wpa_s); | |
314 | ssid = wpas_wps_add_network(wpa_s, 0, bssid); | |
315 | if (ssid == NULL) | |
316 | return -1; | |
317 | if (pin) | |
318 | os_snprintf(val, sizeof(val), "\"pin=%s\"", pin); | |
319 | else { | |
320 | rpin = wps_generate_pin(); | |
321 | os_snprintf(val, sizeof(val), "\"pin=%08d\"", rpin); | |
322 | } | |
323 | wpa_config_set(ssid, "phase1", val, 0); | |
324 | eloop_register_timeout(WPS_PBC_WALK_TIME, 0, wpas_wps_timeout, | |
325 | wpa_s, NULL); | |
326 | wpas_wps_reassoc(wpa_s, ssid); | |
327 | return rpin; | |
328 | } | |
329 | ||
330 | ||
331 | int wpas_wps_start_reg(struct wpa_supplicant *wpa_s, const u8 *bssid, | |
332 | const char *pin) | |
333 | { | |
334 | struct wpa_ssid *ssid; | |
335 | char val[30]; | |
336 | ||
337 | if (!pin) | |
338 | return -1; | |
339 | wpas_clear_wps(wpa_s); | |
340 | ssid = wpas_wps_add_network(wpa_s, 1, bssid); | |
341 | if (ssid == NULL) | |
342 | return -1; | |
343 | os_snprintf(val, sizeof(val), "\"pin=%s\"", pin); | |
344 | wpa_config_set(ssid, "phase1", val, 0); | |
345 | eloop_register_timeout(WPS_PBC_WALK_TIME, 0, wpas_wps_timeout, | |
346 | wpa_s, NULL); | |
347 | wpas_wps_reassoc(wpa_s, ssid); | |
348 | return 0; | |
349 | } | |
350 | ||
351 | ||
116654ce JM |
352 | int wpas_wps_init(struct wpa_supplicant *wpa_s) |
353 | { | |
354 | struct wps_context *wps; | |
355 | ||
356 | wps = os_zalloc(sizeof(*wps)); | |
357 | if (wps == NULL) | |
358 | return -1; | |
359 | ||
360 | wps->cred_cb = wpa_supplicant_wps_cred; | |
361 | wps->cb_ctx = wpa_s; | |
362 | ||
363 | /* TODO: make the device data configurable */ | |
364 | wps->dev.device_name = "dev name"; | |
365 | wps->dev.manufacturer = "manuf"; | |
366 | wps->dev.model_name = "model name"; | |
367 | wps->dev.model_number = "model number"; | |
368 | wps->dev.serial_number = "12345"; | |
369 | wps->dev.categ = WPS_DEV_COMPUTER; | |
370 | wps->dev.oui = WPS_DEV_OUI_WFA; | |
371 | wps->dev.sub_categ = WPS_DEV_COMPUTER_PC; | |
120bd30c JM |
372 | wps->dev.os_version = 0; |
373 | wps->dev.rf_bands = WPS_RF_24GHZ | WPS_RF_50GHZ; | |
398cfbf6 JM |
374 | os_memcpy(wps->dev.mac_addr, wpa_s->own_addr, ETH_ALEN); |
375 | os_memcpy(wps->uuid, wpa_s->conf->uuid, 16); | |
116654ce JM |
376 | |
377 | wpa_s->wps = wps; | |
378 | ||
379 | return 0; | |
380 | } | |
381 | ||
382 | ||
383 | void wpas_wps_deinit(struct wpa_supplicant *wpa_s) | |
384 | { | |
fcc60db4 JM |
385 | eloop_cancel_timeout(wpas_wps_timeout, wpa_s, NULL); |
386 | ||
116654ce JM |
387 | if (wpa_s->wps == NULL) |
388 | return; | |
389 | ||
390 | os_free(wpa_s->wps->network_key); | |
391 | os_free(wpa_s->wps); | |
392 | wpa_s->wps = NULL; | |
393 | } | |
351f09a2 JM |
394 | |
395 | ||
396 | int wpas_wps_ssid_bss_match(struct wpa_ssid *ssid, struct wpa_scan_res *bss) | |
397 | { | |
398 | struct wpabuf *wps_ie; | |
399 | ||
400 | if (!(ssid->key_mgmt & WPA_KEY_MGMT_WPS)) | |
401 | return -1; | |
402 | ||
403 | wps_ie = wpa_scan_get_vendor_ie_multi(bss, WPS_IE_VENDOR_TYPE); | |
404 | if (eap_is_wps_pbc_enrollee(&ssid->eap)) { | |
405 | if (!wps_ie) { | |
406 | wpa_printf(MSG_DEBUG, " skip - non-WPS AP"); | |
407 | return 0; | |
408 | } | |
409 | ||
410 | if (!wps_is_selected_pbc_registrar(wps_ie)) { | |
411 | wpa_printf(MSG_DEBUG, " skip - WPS AP " | |
412 | "without active PBC Registrar"); | |
413 | wpabuf_free(wps_ie); | |
414 | return 0; | |
415 | } | |
416 | ||
417 | /* TODO: overlap detection */ | |
418 | wpa_printf(MSG_DEBUG, " selected based on WPS IE " | |
419 | "(Active PBC)"); | |
420 | wpabuf_free(wps_ie); | |
421 | return 1; | |
422 | } | |
423 | ||
424 | if (eap_is_wps_pin_enrollee(&ssid->eap)) { | |
425 | if (!wps_ie) { | |
426 | wpa_printf(MSG_DEBUG, " skip - non-WPS AP"); | |
427 | return 0; | |
428 | } | |
429 | ||
430 | if (!wps_is_selected_pin_registrar(wps_ie)) { | |
431 | wpa_printf(MSG_DEBUG, " skip - WPS AP " | |
432 | "without active PIN Registrar"); | |
433 | wpabuf_free(wps_ie); | |
434 | return 0; | |
435 | } | |
436 | wpa_printf(MSG_DEBUG, " selected based on WPS IE " | |
437 | "(Active PIN)"); | |
438 | wpabuf_free(wps_ie); | |
439 | return 1; | |
440 | } | |
441 | ||
442 | if (wps_ie) { | |
443 | wpa_printf(MSG_DEBUG, " selected based on WPS IE"); | |
444 | wpabuf_free(wps_ie); | |
445 | return 1; | |
446 | } | |
447 | ||
448 | return -1; | |
449 | } | |
450 | ||
451 | ||
452 | int wpas_wps_ssid_wildcard_ok(struct wpa_ssid *ssid, | |
453 | struct wpa_scan_res *bss) | |
454 | { | |
455 | struct wpabuf *wps_ie = NULL; | |
456 | int ret = 0; | |
457 | ||
458 | if (eap_is_wps_pbc_enrollee(&ssid->eap)) { | |
459 | wps_ie = wpa_scan_get_vendor_ie_multi(bss, WPS_IE_VENDOR_TYPE); | |
460 | if (wps_ie && wps_is_selected_pbc_registrar(wps_ie)) { | |
461 | /* allow wildcard SSID for WPS PBC */ | |
462 | ret = 1; | |
463 | } | |
464 | } else if (eap_is_wps_pin_enrollee(&ssid->eap)) { | |
465 | wps_ie = wpa_scan_get_vendor_ie_multi(bss, WPS_IE_VENDOR_TYPE); | |
466 | if (wps_ie && wps_is_selected_pin_registrar(wps_ie)) { | |
467 | /* allow wildcard SSID for WPS PIN */ | |
468 | ret = 1; | |
469 | } | |
470 | } | |
471 | ||
472 | wpabuf_free(wps_ie); | |
473 | ||
474 | return ret; | |
475 | } | |
476 | ||
477 | ||
478 | int wpas_wps_scan_pbc_overlap(struct wpa_supplicant *wpa_s, | |
479 | struct wpa_scan_res *selected, | |
480 | struct wpa_ssid *ssid) | |
481 | { | |
482 | const u8 *sel_uuid, *uuid; | |
483 | size_t i; | |
484 | struct wpabuf *wps_ie; | |
485 | int ret = 0; | |
486 | ||
487 | if (!eap_is_wps_pbc_enrollee(&ssid->eap)) | |
488 | return 0; | |
489 | ||
490 | /* Make sure that only one AP is in active PBC mode */ | |
491 | wps_ie = wpa_scan_get_vendor_ie_multi(selected, WPS_IE_VENDOR_TYPE); | |
492 | if (wps_ie) | |
493 | sel_uuid = wps_get_uuid_e(wps_ie); | |
494 | else | |
495 | sel_uuid = NULL; | |
496 | if (!sel_uuid) { | |
497 | wpa_printf(MSG_DEBUG, "WPS: UUID-E not available for PBC " | |
498 | "overlap detection"); | |
499 | wpabuf_free(wps_ie); | |
500 | return 1; | |
501 | } | |
502 | ||
503 | for (i = 0; i < wpa_s->scan_res->num; i++) { | |
504 | struct wpa_scan_res *bss = wpa_s->scan_res->res[i]; | |
505 | struct wpabuf *ie; | |
506 | if (bss == selected) | |
507 | continue; | |
508 | ie = wpa_scan_get_vendor_ie_multi(bss, WPS_IE_VENDOR_TYPE); | |
509 | if (!ie) | |
510 | continue; | |
511 | if (!wps_is_selected_pbc_registrar(ie)) { | |
512 | wpabuf_free(ie); | |
513 | continue; | |
514 | } | |
515 | uuid = wps_get_uuid_e(ie); | |
516 | if (uuid == NULL) { | |
517 | wpa_printf(MSG_DEBUG, "WPS: UUID-E not available for " | |
518 | "PBC overlap detection (other BSS)"); | |
519 | ret = 1; | |
520 | wpabuf_free(ie); | |
521 | break; | |
522 | } | |
523 | if (os_memcmp(sel_uuid, uuid, 16) != 0) { | |
524 | ret = 1; /* PBC overlap */ | |
525 | wpabuf_free(ie); | |
526 | break; | |
527 | } | |
528 | ||
529 | /* TODO: verify that this is reasonable dual-band situation */ | |
530 | } | |
531 | ||
532 | wpabuf_free(wps_ie); | |
533 | ||
534 | return ret; | |
535 | } |