WPS: Maintain more AP state during WPS PIN iteration
[mech_eap.git] / wpa_supplicant / wps_supplicant.c
index 8969452..3bb5427 100644 (file)
@@ -1,15 +1,9 @@
 /*
  * wpa_supplicant / WPS integration
- * Copyright (c) 2008-2010, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2008-2012, Jouni Malinen <j@w1.fi>
  *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- * Alternatively, this software may be distributed under the terms of BSD
- * license.
- *
- * See README and COPYING for more details.
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
  */
 
 #include "includes.h"
@@ -17,6 +11,7 @@
 #include "common.h"
 #include "eloop.h"
 #include "uuid.h"
+#include "crypto/random.h"
 #include "crypto/dh_group5.h"
 #include "common/ieee802_11_defs.h"
 #include "common/ieee802_11_common.h"
@@ -26,6 +21,7 @@
 #include "eap_peer/eap.h"
 #include "eapol_supp/eapol_supp_sm.h"
 #include "rsn_supp/wpa.h"
+#include "wps/wps_attr_parse.h"
 #include "config.h"
 #include "wpa_supplicant_i.h"
 #include "driver_i.h"
@@ -47,6 +43,15 @@ static void wpas_wps_timeout(void *eloop_ctx, void *timeout_ctx);
 static void wpas_clear_wps(struct wpa_supplicant *wpa_s);
 
 
+static void wpas_wps_clear_ap_info(struct wpa_supplicant *wpa_s)
+{
+       os_free(wpa_s->wps_ap);
+       wpa_s->wps_ap = NULL;
+       wpa_s->num_wps_ap = 0;
+       wpa_s->wps_ap_iter = 0;
+}
+
+
 int wpas_wps_eapol_cb(struct wpa_supplicant *wpa_s)
 {
        if (!wpa_s->wps_success &&
@@ -70,6 +75,7 @@ int wpas_wps_eapol_cb(struct wpa_supplicant *wpa_s)
                return 1;
        }
 
+       wpas_wps_clear_ap_info(wpa_s);
        eloop_cancel_timeout(wpas_wps_timeout, wpa_s, NULL);
        if (wpa_s->key_mgmt == WPA_KEY_MGMT_WPS && !wpa_s->wps_success)
                wpa_msg(wpa_s, MSG_INFO, WPS_EVENT_FAIL);
@@ -77,8 +83,10 @@ int wpas_wps_eapol_cb(struct wpa_supplicant *wpa_s)
        if (wpa_s->key_mgmt == WPA_KEY_MGMT_WPS && wpa_s->current_ssid &&
            !(wpa_s->current_ssid->key_mgmt & WPA_KEY_MGMT_WPS)) {
                int disabled = wpa_s->current_ssid->disabled;
+               unsigned int freq = wpa_s->assoc_freq;
                wpa_printf(MSG_DEBUG, "WPS: Network configuration replaced - "
-                          "try to associate with the received credential");
+                          "try to associate with the received credential "
+                          "(freq=%u)", freq);
                wpa_supplicant_deauthenticate(wpa_s,
                                              WLAN_REASON_DEAUTH_LEAVING);
                if (disabled) {
@@ -87,7 +95,7 @@ int wpas_wps_eapol_cb(struct wpa_supplicant *wpa_s)
                        return 1;
                }
                wpa_s->after_wps = 5;
-               wpa_s->wps_freq = wpa_s->assoc_freq;
+               wpa_s->wps_freq = freq;
                wpa_s->normal_scans = 0;
                wpa_s->reassociate = 1;
                wpa_supplicant_req_scan(wpa_s, 0, 0);
@@ -265,8 +273,13 @@ static int wpa_supplicant_wps_cred(void *ctx,
                ssid->eap.phase1 = NULL;
                os_free(ssid->eap.eap_methods);
                ssid->eap.eap_methods = NULL;
-               if (!ssid->p2p_group)
+               if (!ssid->p2p_group) {
                        ssid->temporary = 0;
+                       ssid->bssid_set = 0;
+               }
+               ssid->disabled_until.sec = 0;
+               ssid->disabled_until.usec = 0;
+               ssid->auth_failures = 0;
        } else {
                wpa_printf(MSG_DEBUG, "WPS: Create a new network based on the "
                           "received credential");
@@ -354,16 +367,6 @@ static int wpa_supplicant_wps_cred(void *ctx,
                ssid->key_mgmt = WPA_KEY_MGMT_PSK;
                ssid->proto = WPA_PROTO_WPA;
                break;
-       case WPS_AUTH_WPA:
-               ssid->auth_alg = WPA_AUTH_ALG_OPEN;
-               ssid->key_mgmt = WPA_KEY_MGMT_IEEE8021X;
-               ssid->proto = WPA_PROTO_WPA;
-               break;
-       case WPS_AUTH_WPA2:
-               ssid->auth_alg = WPA_AUTH_ALG_OPEN;
-               ssid->key_mgmt = WPA_KEY_MGMT_IEEE8021X;
-               ssid->proto = WPA_PROTO_RSN;
-               break;
        case WPS_AUTH_WPA2PSK:
                ssid->auth_alg = WPA_AUTH_ALG_OPEN;
                ssid->key_mgmt = WPA_KEY_MGMT_PSK;
@@ -664,6 +667,8 @@ static void wpa_supplicant_wps_event(void *ctx, enum wps_event event,
                wpa_supplicant_wps_event_er_set_sel_reg(wpa_s,
                                                        &data->set_sel_reg);
                break;
+       case WPS_EV_AP_PIN_SUCCESS:
+               break;
        }
 }
 
@@ -711,6 +716,8 @@ static void wpas_clear_wps(struct wpa_supplicant *wpa_s)
                        wpa_config_remove_network(wpa_s->conf, id);
                }
        }
+
+       wpas_wps_clear_ap_info(wpa_s);
 }
 
 
@@ -790,9 +797,23 @@ static struct wpa_ssid * wpas_wps_add_network(struct wpa_supplicant *wpa_s,
 
 
 static void wpas_wps_reassoc(struct wpa_supplicant *wpa_s,
-                            struct wpa_ssid *selected)
+                            struct wpa_ssid *selected, const u8 *bssid)
 {
        struct wpa_ssid *ssid;
+       struct wpa_bss *bss;
+
+       wpa_s->known_wps_freq = 0;
+       if (bssid) {
+               bss = wpa_bss_get_bssid(wpa_s, bssid);
+               if (bss && bss->freq > 0) {
+                       wpa_s->known_wps_freq = 1;
+                       wpa_s->wps_freq = bss->freq;
+               }
+       }
+
+       if (wpa_s->current_ssid)
+               wpa_supplicant_deauthenticate(
+                       wpa_s, WLAN_REASON_DEAUTH_LEAVING);
 
        /* Mark all other networks disabled and trigger reassociation */
        ssid = wpa_s->conf->ssid;
@@ -849,7 +870,7 @@ int wpas_wps_start_pbc(struct wpa_supplicant *wpa_s, const u8 *bssid,
                ssid->eap.fragment_size = wpa_s->wps_fragment_size;
        eloop_register_timeout(WPS_PBC_WALK_TIME, 0, wpas_wps_timeout,
                               wpa_s, NULL);
-       wpas_wps_reassoc(wpa_s, ssid);
+       wpas_wps_reassoc(wpa_s, ssid, bssid);
        return 0;
 }
 
@@ -892,7 +913,8 @@ int wpas_wps_start_pin(struct wpa_supplicant *wpa_s, const u8 *bssid,
                ssid->eap.fragment_size = wpa_s->wps_fragment_size;
        eloop_register_timeout(WPS_PBC_WALK_TIME, 0, wpas_wps_timeout,
                               wpa_s, NULL);
-       wpas_wps_reassoc(wpa_s, ssid);
+       wpa_s->wps_ap_iter = 1;
+       wpas_wps_reassoc(wpa_s, ssid, bssid);
        return rpin;
 }
 
@@ -907,7 +929,8 @@ int wpas_wps_cancel(struct wpa_supplicant *wpa_s)
        }
 #endif /* CONFIG_AP */
 
-       if (wpa_s->wpa_state == WPA_SCANNING) {
+       if (wpa_s->wpa_state == WPA_SCANNING ||
+           wpa_s->wpa_state == WPA_DISCONNECTED) {
                wpa_printf(MSG_DEBUG, "WPS: Cancel operation - cancel scan");
                wpa_supplicant_cancel_scan(wpa_s);
                wpas_clear_wps(wpa_s);
@@ -917,7 +940,8 @@ int wpas_wps_cancel(struct wpa_supplicant *wpa_s)
                wpa_supplicant_deauthenticate(wpa_s,
                                              WLAN_REASON_DEAUTH_LEAVING);
                wpas_clear_wps(wpa_s);
-       }
+       } else
+               wpas_wps_clear_ap_info(wpa_s);
 
        return 0;
 }
@@ -1012,7 +1036,7 @@ int wpas_wps_start_reg(struct wpa_supplicant *wpa_s, const u8 *bssid,
                ssid->eap.fragment_size = wpa_s->wps_fragment_size;
        eloop_register_timeout(WPS_PBC_WALK_TIME, 0, wpas_wps_timeout,
                               wpa_s, NULL);
-       wpas_wps_reassoc(wpa_s, ssid);
+       wpas_wps_reassoc(wpa_s, ssid, bssid);
        return 0;
 }
 
@@ -1101,8 +1125,10 @@ static void wpas_wps_set_uuid(struct wpa_supplicant *wpa_s,
                while (first && first->next)
                        first = first->next;
                if (first && first != wpa_s) {
-                       os_memcpy(wps->uuid, wpa_s->global->ifaces->wps->uuid,
-                                 WPS_UUID_LEN);
+                       if (wps != wpa_s->global->ifaces->wps)
+                               os_memcpy(wps->uuid,
+                                         wpa_s->global->ifaces->wps->uuid,
+                                         WPS_UUID_LEN);
                        wpa_hexdump(MSG_DEBUG, "WPS: UUID from the first "
                                    "interface", wps->uuid, WPS_UUID_LEN);
                } else {
@@ -1118,6 +1144,23 @@ static void wpas_wps_set_uuid(struct wpa_supplicant *wpa_s,
 }
 
 
+static void wpas_wps_set_vendor_ext_m1(struct wpa_supplicant *wpa_s,
+                                      struct wps_context *wps)
+{
+       wpabuf_free(wps->dev.vendor_ext_m1);
+       wps->dev.vendor_ext_m1 = NULL;
+
+       if (wpa_s->conf->wps_vendor_ext_m1) {
+               wps->dev.vendor_ext_m1 =
+                       wpabuf_dup(wpa_s->conf->wps_vendor_ext_m1);
+               if (!wps->dev.vendor_ext_m1) {
+                       wpa_printf(MSG_ERROR, "WPS: Cannot "
+                                  "allocate memory for vendor_ext_m1");
+               }
+       }
+}
+
+
 int wpas_wps_init(struct wpa_supplicant *wpa_s)
 {
        struct wps_context *wps;
@@ -1156,6 +1199,8 @@ int wpas_wps_init(struct wpa_supplicant *wpa_s)
        os_memcpy(wps->dev.sec_dev_type, wpa_s->conf->sec_device_type,
                  WPS_DEV_TYPE_LEN * wps->dev.num_sec_dev_types);
 
+       wpas_wps_set_vendor_ext_m1(wpa_s, wps);
+
        wps->dev.os_version = WPA_GET_BE32(wpa_s->conf->os_version);
        modes = wpa_s->hw.modes;
        if (modes) {
@@ -1202,6 +1247,7 @@ int wpas_wps_init(struct wpa_supplicant *wpa_s)
 void wpas_wps_deinit(struct wpa_supplicant *wpa_s)
 {
        eloop_cancel_timeout(wpas_wps_timeout, wpa_s, NULL);
+       wpas_wps_clear_ap_info(wpa_s);
 
        if (wpa_s->wps == NULL)
                return;
@@ -1216,6 +1262,7 @@ void wpas_wps_deinit(struct wpa_supplicant *wpa_s)
        wpabuf_free(wpa_s->wps->dh_privkey);
        wpabuf_free(wpa_s->wps->oob_conf.pubkey_hash);
        wpabuf_free(wpa_s->wps->oob_conf.dev_password);
+       wpabuf_free(wpa_s->wps->dev.vendor_ext_m1);
        os_free(wpa_s->wps->network_key);
        os_free(wpa_s->wps);
        wpa_s->wps = NULL;
@@ -1636,6 +1683,34 @@ int wpas_wps_er_config(struct wpa_supplicant *wpa_s, const char *uuid,
 }
 
 
+#ifdef CONFIG_WPS_NFC
+struct wpabuf * wpas_wps_er_nfc_config_token(struct wpa_supplicant *wpa_s,
+                                            int ndef, const char *uuid)
+{
+       struct wpabuf *ret;
+       u8 u[UUID_LEN];
+
+       if (!wpa_s->wps_er)
+               return NULL;
+
+       if (uuid_str2bin(uuid, u))
+               return NULL;
+
+       ret = wps_er_nfc_config_token(wpa_s->wps_er, u);
+       if (ndef && ret) {
+               struct wpabuf *tmp;
+               tmp = ndef_build_wifi(ret);
+               wpabuf_free(ret);
+               if (tmp == NULL)
+                       return NULL;
+               ret = tmp;
+       }
+
+       return ret;
+}
+#endif /* CONFIG_WPS_NFC */
+
+
 static int callbacks_pending = 0;
 
 static void wpas_wps_terminate_cb(void *ctx)
@@ -1694,6 +1769,7 @@ void wpas_wps_update_config(struct wpa_supplicant *wpa_s)
                }
        }
        wps->config_methods = wps_fix_config_methods(wps->config_methods);
+       wps->dev.config_methods = wps->config_methods;
 
        if (wpa_s->conf->changed_parameters & CFG_CHANGED_DEVICE_TYPE)
                os_memcpy(wps->dev.pri_dev_type, wpa_s->conf->device_type,
@@ -1705,6 +1781,9 @@ void wpas_wps_update_config(struct wpa_supplicant *wpa_s)
                          wps->dev.num_sec_dev_types * WPS_DEV_TYPE_LEN);
        }
 
+       if (wpa_s->conf->changed_parameters & CFG_CHANGED_VENDOR_EXTENSION)
+               wpas_wps_set_vendor_ext_m1(wpa_s, wps);
+
        if (wpa_s->conf->changed_parameters & CFG_CHANGED_OS_VERSION)
                wps->dev.os_version = WPA_GET_BE32(wpa_s->conf->os_version);
 
@@ -1721,3 +1800,257 @@ void wpas_wps_update_config(struct wpa_supplicant *wpa_s)
                wps->dev.serial_number = wpa_s->conf->serial_number;
        }
 }
+
+
+#ifdef CONFIG_WPS_NFC
+
+struct wpabuf * wpas_wps_nfc_token(struct wpa_supplicant *wpa_s, int ndef)
+{
+       return wps_nfc_token_gen(ndef, &wpa_s->conf->wps_nfc_dev_pw_id,
+                                &wpa_s->conf->wps_nfc_dh_pubkey,
+                                &wpa_s->conf->wps_nfc_dh_privkey,
+                                &wpa_s->conf->wps_nfc_dev_pw);
+}
+
+
+int wpas_wps_start_nfc(struct wpa_supplicant *wpa_s, const u8 *bssid)
+{
+       struct wps_context *wps = wpa_s->wps;
+       char pw[32 * 2 + 1];
+
+       if (wpa_s->conf->wps_nfc_dh_pubkey == NULL ||
+           wpa_s->conf->wps_nfc_dh_privkey == NULL ||
+           wpa_s->conf->wps_nfc_dev_pw == NULL)
+               return -1;
+
+       dh5_free(wps->dh_ctx);
+       wpabuf_free(wps->dh_pubkey);
+       wpabuf_free(wps->dh_privkey);
+       wps->dh_privkey = wpabuf_dup(wpa_s->conf->wps_nfc_dh_privkey);
+       wps->dh_pubkey = wpabuf_dup(wpa_s->conf->wps_nfc_dh_pubkey);
+       if (wps->dh_privkey == NULL || wps->dh_pubkey == NULL) {
+               wps->dh_ctx = NULL;
+               wpabuf_free(wps->dh_pubkey);
+               wps->dh_pubkey = NULL;
+               wpabuf_free(wps->dh_privkey);
+               wps->dh_privkey = NULL;
+               return -1;
+       }
+       wps->dh_ctx = dh5_init_fixed(wps->dh_privkey, wps->dh_pubkey);
+       if (wps->dh_ctx == NULL)
+               return -1;
+
+       wpa_snprintf_hex_uppercase(pw, sizeof(pw),
+                                  wpabuf_head(wpa_s->conf->wps_nfc_dev_pw),
+                                  wpabuf_len(wpa_s->conf->wps_nfc_dev_pw));
+       return wpas_wps_start_pin(wpa_s, bssid, pw, 0,
+                                 wpa_s->conf->wps_nfc_dev_pw_id);
+}
+
+
+static int wpas_wps_use_cred(struct wpa_supplicant *wpa_s,
+                            struct wps_parse_attr *attr)
+{
+       if (wps_oob_use_cred(wpa_s->wps, attr) < 0)
+               return -1;
+
+       if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED)
+               return 0;
+
+       wpa_printf(MSG_DEBUG, "WPS: Request reconnection with new network "
+                  "based on the received credential added");
+       wpa_s->normal_scans = 0;
+       wpa_supplicant_reinit_autoscan(wpa_s);
+       wpa_s->disconnected = 0;
+       wpa_s->reassociate = 1;
+       wpa_supplicant_req_scan(wpa_s, 0, 0);
+
+       return 0;
+}
+
+
+static int wpas_wps_add_nfc_password_token(struct wpa_supplicant *wpa_s,
+                                          struct wps_parse_attr *attr)
+{
+       return wps_registrar_add_nfc_password_token(
+               wpa_s->wps->registrar, attr->oob_dev_password,
+               attr->oob_dev_password_len);
+}
+
+
+static int wpas_wps_nfc_tag_process(struct wpa_supplicant *wpa_s,
+                                   const struct wpabuf *wps)
+{
+       struct wps_parse_attr attr;
+
+       wpa_hexdump_buf(MSG_DEBUG, "WPS: Received NFC tag payload", wps);
+
+       if (wps_parse_msg(wps, &attr)) {
+               wpa_printf(MSG_DEBUG, "WPS: Ignore invalid data from NFC tag");
+               return -1;
+       }
+
+       if (attr.num_cred)
+               return wpas_wps_use_cred(wpa_s, &attr);
+
+#ifdef CONFIG_WPS_ER
+       if (attr.oob_dev_password)
+               return wpas_wps_add_nfc_password_token(wpa_s, &attr);
+#endif /* CONFIG_WPS_ER */
+
+       wpa_printf(MSG_DEBUG, "WPS: Ignore unrecognized NFC tag");
+       return -1;
+}
+
+
+int wpas_wps_nfc_tag_read(struct wpa_supplicant *wpa_s,
+                         const struct wpabuf *data)
+{
+       const struct wpabuf *wps = data;
+       struct wpabuf *tmp = NULL;
+       int ret;
+
+       if (wpabuf_len(data) < 4)
+               return -1;
+
+       if (*wpabuf_head_u8(data) != 0x10) {
+               /* Assume this contains full NDEF record */
+               tmp = ndef_parse_wifi(data);
+               if (tmp == NULL) {
+                       wpa_printf(MSG_DEBUG, "WPS: Could not parse NDEF");
+                       return -1;
+               }
+               wps = tmp;
+       }
+
+       ret = wpas_wps_nfc_tag_process(wpa_s, wps);
+       wpabuf_free(tmp);
+       return ret;
+}
+
+#endif /* CONFIG_WPS_NFC */
+
+
+extern int wpa_debug_level;
+
+static void wpas_wps_dump_ap_info(struct wpa_supplicant *wpa_s)
+{
+       size_t i;
+       struct os_time now;
+
+       if (wpa_debug_level > MSG_DEBUG)
+               return;
+
+       if (wpa_s->wps_ap == NULL)
+               return;
+
+       os_get_time(&now);
+
+       for (i = 0; i < wpa_s->num_wps_ap; i++) {
+               struct wps_ap_info *ap = &wpa_s->wps_ap[i];
+               struct wpa_blacklist *e = wpa_blacklist_get(wpa_s, ap->bssid);
+
+               wpa_printf(MSG_DEBUG, "WPS: AP[%d] " MACSTR " type=%d "
+                          "tries=%d last_attempt=%d sec ago blacklist=%d",
+                          (int) i, MAC2STR(ap->bssid), ap->type, ap->tries,
+                          ap->last_attempt.sec > 0 ?
+                          (int) now.sec - (int) ap->last_attempt.sec : -1,
+                          e ? e->count : 0);
+       }
+}
+
+
+static struct wps_ap_info * wpas_wps_get_ap_info(struct wpa_supplicant *wpa_s,
+                                                const u8 *bssid)
+{
+       size_t i;
+
+       if (wpa_s->wps_ap == NULL)
+               return NULL;
+
+       for (i = 0; i < wpa_s->num_wps_ap; i++) {
+               struct wps_ap_info *ap = &wpa_s->wps_ap[i];
+               if (os_memcmp(ap->bssid, bssid, ETH_ALEN) == 0)
+                       return ap;
+       }
+
+       return NULL;
+}
+
+
+static void wpas_wps_update_ap_info_bss(struct wpa_supplicant *wpa_s,
+                                       struct wpa_scan_res *res)
+{
+       struct wpabuf *wps;
+       enum wps_ap_info_type type;
+       struct wps_ap_info *ap;
+       int r;
+
+       if (wpa_scan_get_vendor_ie(res, WPS_IE_VENDOR_TYPE) == NULL)
+               return;
+
+       wps = wpa_scan_get_vendor_ie_multi(res, WPS_IE_VENDOR_TYPE);
+       if (wps == NULL)
+               return;
+
+       r = wps_is_addr_authorized(wps, wpa_s->own_addr, 1);
+       if (r == 2)
+               type = WPS_AP_SEL_REG_OUR;
+       else if (r == 1)
+               type = WPS_AP_SEL_REG;
+       else
+               type = WPS_AP_NOT_SEL_REG;
+
+       wpabuf_free(wps);
+
+       ap = wpas_wps_get_ap_info(wpa_s, res->bssid);
+       if (ap) {
+               if (ap->type != type) {
+                       wpa_printf(MSG_DEBUG, "WPS: AP " MACSTR
+                                  " changed type %d -> %d",
+                                  MAC2STR(res->bssid), ap->type, type);
+                       ap->type = type;
+               }
+               return;
+       }
+
+       ap = os_realloc_array(wpa_s->wps_ap, wpa_s->num_wps_ap + 1,
+                             sizeof(struct wps_ap_info));
+       if (ap == NULL)
+               return;
+
+       wpa_s->wps_ap = ap;
+       ap = &wpa_s->wps_ap[wpa_s->num_wps_ap];
+       wpa_s->num_wps_ap++;
+
+       os_memset(ap, 0, sizeof(*ap));
+       os_memcpy(ap->bssid, res->bssid, ETH_ALEN);
+       ap->type = type;
+       wpa_printf(MSG_DEBUG, "WPS: AP " MACSTR " type %d added",
+                  MAC2STR(ap->bssid), ap->type);
+}
+
+
+void wpas_wps_update_ap_info(struct wpa_supplicant *wpa_s,
+                            struct wpa_scan_results *scan_res)
+{
+       size_t i;
+
+       for (i = 0; i < scan_res->num; i++)
+               wpas_wps_update_ap_info_bss(wpa_s, scan_res->res[i]);
+
+       wpas_wps_dump_ap_info(wpa_s);
+}
+
+
+void wpas_wps_notify_assoc(struct wpa_supplicant *wpa_s, const u8 *bssid)
+{
+       struct wps_ap_info *ap;
+       if (!wpa_s->wps_ap_iter)
+               return;
+       ap = wpas_wps_get_ap_info(wpa_s, bssid);
+       if (ap == NULL)
+               return;
+       ap->tries++;
+       os_get_time(&ap->last_attempt);
+}