WPS: Share a single function for generating NFS password tokens
[mech_eap.git] / wpa_supplicant / wps_supplicant.c
index 7b5c9be..7356d1a 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"
@@ -24,7 +19,9 @@
 #include "common/wpa_ctrl.h"
 #include "eap_common/eap_wsc_common.h"
 #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"
@@ -70,15 +67,26 @@ int wpas_wps_eapol_cb(struct wpa_supplicant *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);
 
        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) {
+                       wpa_printf(MSG_DEBUG, "WPS: Current network is "
+                                  "disabled - wait for user to enable");
+                       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);
                return 1;
@@ -118,6 +126,8 @@ static void wpas_wps_security_workaround(struct wpa_supplicant *wpa_s,
        if (wpa_drv_get_capa(wpa_s, &capa))
                return; /* Unknown what driver supports */
 
+       if (ssid->ssid == NULL)
+               return;
        bss = wpa_bss_get(wpa_s, cred->mac_addr, ssid->ssid, ssid->ssid_len);
        if (bss == NULL) {
                wpa_printf(MSG_DEBUG, "WPS: The AP was not found from BSS "
@@ -183,6 +193,9 @@ static int wpa_supplicant_wps_cred(void *ctx,
        struct wpa_ssid *ssid = wpa_s->current_ssid;
        u8 key_idx = 0;
        u16 auth_type;
+#ifdef CONFIG_WPS_REG_DISABLE_OPEN
+       int registrar = 0;
+#endif /* CONFIG_WPS_REG_DISABLE_OPEN */
 
        if ((wpa_s->conf->wps_cred_processing == 1 ||
             wpa_s->conf->wps_cred_processing == 2) && cred->cred_attr) {
@@ -236,6 +249,13 @@ static int wpa_supplicant_wps_cred(void *ctx,
        if (ssid && (ssid->key_mgmt & WPA_KEY_MGMT_WPS)) {
                wpa_printf(MSG_DEBUG, "WPS: Replace WPS network block based "
                           "on the received credential");
+#ifdef CONFIG_WPS_REG_DISABLE_OPEN
+               if (ssid->eap.identity &&
+                   ssid->eap.identity_len == WSC_ID_REGISTRAR_LEN &&
+                   os_memcmp(ssid->eap.identity, WSC_ID_REGISTRAR,
+                             WSC_ID_REGISTRAR_LEN) == 0)
+                       registrar = 1;
+#endif /* CONFIG_WPS_REG_DISABLE_OPEN */
                os_free(ssid->eap.identity);
                ssid->eap.identity = NULL;
                ssid->eap.identity_len = 0;
@@ -245,6 +265,7 @@ static int wpa_supplicant_wps_cred(void *ctx,
                ssid->eap.eap_methods = NULL;
                if (!ssid->p2p_group)
                        ssid->temporary = 0;
+               ssid->bssid_set = 0;
        } else {
                wpa_printf(MSG_DEBUG, "WPS: Create a new network based on the "
                           "received credential");
@@ -311,6 +332,16 @@ static int wpa_supplicant_wps_cred(void *ctx,
                ssid->auth_alg = WPA_AUTH_ALG_OPEN;
                ssid->key_mgmt = WPA_KEY_MGMT_NONE;
                ssid->proto = 0;
+#ifdef CONFIG_WPS_REG_DISABLE_OPEN
+               if (registrar) {
+                       wpa_msg(wpa_s, MSG_INFO, WPS_EVENT_OPEN_NETWORK
+                               "id=%d - Credentials for an open "
+                               "network disabled by default - use "
+                               "'select_network %d' to enable",
+                               ssid->id, ssid->id);
+                       ssid->disabled = 1;
+               }
+#endif /* CONFIG_WPS_REG_DISABLE_OPEN */
                break;
        case WPS_AUTH_SHARED:
                ssid->auth_alg = WPA_AUTH_ALG_SHARED;
@@ -348,6 +379,7 @@ static int wpa_supplicant_wps_cred(void *ctx,
                                return -1;
                        }
                        ssid->psk_set = 1;
+                       ssid->export_keys = 1;
                } else if (cred->key_len >= 8 && cred->key_len < 2 * PMK_LEN) {
                        os_free(ssid->passphrase);
                        ssid->passphrase = os_malloc(cred->key_len + 1);
@@ -356,6 +388,7 @@ static int wpa_supplicant_wps_cred(void *ctx,
                        os_memcpy(ssid->passphrase, cred->key, cred->key_len);
                        ssid->passphrase[cred->key_len] = '\0';
                        wpa_config_update_psk(ssid);
+                       ssid->export_keys = 1;
                } else {
                        wpa_printf(MSG_ERROR, "WPS: Invalid Network Key "
                                   "length %lu",
@@ -378,6 +411,15 @@ static int wpa_supplicant_wps_cred(void *ctx,
 }
 
 
+#ifdef CONFIG_P2P
+static void wpas_wps_pbc_overlap_cb(void *eloop_ctx, void *timeout_ctx)
+{
+       struct wpa_supplicant *wpa_s = eloop_ctx;
+       wpas_p2p_notif_pbc_overlap(wpa_s);
+}
+#endif /* CONFIG_P2P */
+
+
 static void wpa_supplicant_wps_event_m2d(struct wpa_supplicant *wpa_s,
                                         struct wps_event_m2d *m2d)
 {
@@ -385,20 +427,59 @@ static void wpa_supplicant_wps_event_m2d(struct wpa_supplicant *wpa_s,
                "dev_password_id=%d config_error=%d",
                m2d->dev_password_id, m2d->config_error);
        wpas_notify_wps_event_m2d(wpa_s, m2d);
+#ifdef CONFIG_P2P
+       if (wpa_s->parent && wpa_s->parent != wpa_s) {
+               wpa_msg(wpa_s->parent, MSG_INFO, WPS_EVENT_M2D
+                       "dev_password_id=%d config_error=%d",
+                       m2d->dev_password_id, m2d->config_error);
+       }
+       if (m2d->config_error == WPS_CFG_MULTIPLE_PBC_DETECTED) {
+               /*
+                * Notify P2P from eloop timeout to avoid issues with the
+                * interface getting removed while processing a message.
+                */
+               eloop_register_timeout(0, 0, wpas_wps_pbc_overlap_cb, wpa_s,
+                                      NULL);
+       }
+#endif /* CONFIG_P2P */
 }
 
 
+static const char * wps_event_fail_reason[NUM_WPS_EI_VALUES] = {
+       "No Error", /* WPS_EI_NO_ERROR */
+       "TKIP Only Prohibited", /* WPS_EI_SECURITY_TKIP_ONLY_PROHIBITED */
+       "WEP Prohibited" /* WPS_EI_SECURITY_WEP_PROHIBITED */
+};
+
 static void wpa_supplicant_wps_event_fail(struct wpa_supplicant *wpa_s,
                                          struct wps_event_fail *fail)
 {
-       wpa_msg(wpa_s, MSG_INFO, WPS_EVENT_FAIL "msg=%d config_error=%d",
-               fail->msg, fail->config_error);
-       if (wpa_s->parent && wpa_s->parent != wpa_s)
-               wpa_msg(wpa_s->parent, MSG_INFO, WPS_EVENT_FAIL
-                       "msg=%d config_error=%d",
+       if (fail->error_indication > 0 &&
+           fail->error_indication < NUM_WPS_EI_VALUES) {
+               wpa_msg(wpa_s, MSG_INFO,
+                       WPS_EVENT_FAIL "msg=%d config_error=%d reason=%d (%s)",
+                       fail->msg, fail->config_error, fail->error_indication,
+                       wps_event_fail_reason[fail->error_indication]);
+               if (wpa_s->parent && wpa_s->parent != wpa_s)
+                       wpa_msg(wpa_s->parent, MSG_INFO, WPS_EVENT_FAIL
+                               "msg=%d config_error=%d reason=%d (%s)",
+                               fail->msg, fail->config_error,
+                               fail->error_indication,
+                               wps_event_fail_reason[fail->error_indication]);
+       } else {
+               wpa_msg(wpa_s, MSG_INFO,
+                       WPS_EVENT_FAIL "msg=%d config_error=%d",
                        fail->msg, fail->config_error);
+               if (wpa_s->parent && wpa_s->parent != wpa_s)
+                       wpa_msg(wpa_s->parent, MSG_INFO, WPS_EVENT_FAIL
+                               "msg=%d config_error=%d",
+                               fail->msg, fail->config_error);
+       }
        wpas_clear_wps(wpa_s);
        wpas_notify_wps_event_fail(wpa_s, fail);
+#ifdef CONFIG_P2P
+       wpas_p2p_wps_failed(wpa_s, fail);
+#endif /* CONFIG_P2P */
 }
 
 
@@ -582,6 +663,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;
        }
 }
 
@@ -599,7 +682,9 @@ enum wps_request_type wpas_wps_get_req_type(struct wpa_ssid *ssid)
 static void wpas_clear_wps(struct wpa_supplicant *wpa_s)
 {
        int id;
-       struct wpa_ssid *ssid, *remove_ssid = NULL;
+       struct wpa_ssid *ssid, *remove_ssid = NULL, *prev_current;
+
+       prev_current = wpa_s->current_ssid;
 
        eloop_cancel_timeout(wpas_wps_timeout, wpa_s, NULL);
 
@@ -618,6 +703,11 @@ static void wpas_clear_wps(struct wpa_supplicant *wpa_s)
                        id = -1;
                ssid = ssid->next;
                if (id >= 0) {
+                       if (prev_current == remove_ssid) {
+                               wpa_sm_set_config(wpa_s->wpa, NULL);
+                               eapol_sm_notify_config(wpa_s->eapol, NULL,
+                                                      NULL);
+                       }
                        wpas_notify_network_removed(wpa_s, remove_ssid);
                        wpa_config_remove_network(wpa_s->conf, id);
                }
@@ -701,22 +791,46 @@ 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;
        while (ssid) {
                int was_disabled = ssid->disabled;
-               ssid->disabled = ssid != selected;
-               if (was_disabled != ssid->disabled)
-                       wpas_notify_network_enabled_changed(wpa_s, ssid);
+               /*
+                * In case the network object corresponds to a persistent group
+                * then do not send out network disabled signal. In addition,
+                * do not change disabled status of persistent network objects
+                * from 2 to 1 should we connect to another network.
+                */
+               if (was_disabled != 2) {
+                       ssid->disabled = ssid != selected;
+                       if (was_disabled != ssid->disabled)
+                               wpas_notify_network_enabled_changed(wpa_s,
+                                                                   ssid);
+               }
                ssid = ssid->next;
        }
        wpa_s->disconnected = 0;
        wpa_s->reassociate = 1;
        wpa_s->scan_runs = 0;
+       wpa_s->normal_scans = 0;
        wpa_s->wps_success = 0;
        wpa_s->blacklist_cleared = 0;
        wpa_supplicant_req_scan(wpa_s, 0, 0);
@@ -750,7 +864,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;
 }
 
@@ -793,7 +907,7 @@ 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);
+       wpas_wps_reassoc(wpa_s, ssid, bssid);
        return rpin;
 }
 
@@ -808,7 +922,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);
@@ -913,7 +1028,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;
 }
 
@@ -992,10 +1107,58 @@ static u16 wps_fix_config_methods(u16 config_methods)
 }
 
 
+static void wpas_wps_set_uuid(struct wpa_supplicant *wpa_s,
+                             struct wps_context *wps)
+{
+       wpa_printf(MSG_DEBUG, "WPS: Set UUID for interface %s", wpa_s->ifname);
+       if (is_nil_uuid(wpa_s->conf->uuid)) {
+               struct wpa_supplicant *first;
+               first = wpa_s->global->ifaces;
+               while (first && first->next)
+                       first = first->next;
+               if (first && first != wpa_s) {
+                       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 {
+                       uuid_gen_mac_addr(wpa_s->own_addr, wps->uuid);
+                       wpa_hexdump(MSG_DEBUG, "WPS: UUID based on MAC "
+                                   "address", wps->uuid, WPS_UUID_LEN);
+               }
+       } else {
+               os_memcpy(wps->uuid, wpa_s->conf->uuid, WPS_UUID_LEN);
+               wpa_hexdump(MSG_DEBUG, "WPS: UUID based on configuration",
+                           wps->uuid, WPS_UUID_LEN);
+       }
+}
+
+
+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;
        struct wps_registrar_config rcfg;
+       struct hostapd_hw_modes *modes;
+       u16 m;
 
        wps = os_zalloc(sizeof(*wps));
        if (wps == NULL)
@@ -1020,22 +1183,36 @@ int wpas_wps_init(struct wpa_supplicant *wpa_s)
                return -1;
        }
        wps->config_methods = wps_fix_config_methods(wps->config_methods);
-       if (wpa_s->conf->device_type &&
-           wps_dev_type_str2bin(wpa_s->conf->device_type,
-                                wps->dev.pri_dev_type) < 0) {
-               wpa_printf(MSG_ERROR, "WPS: Invalid device_type");
-               os_free(wps);
-               return -1;
-       }
+       wps->dev.config_methods = wps->config_methods;
+       os_memcpy(wps->dev.pri_dev_type, wpa_s->conf->device_type,
+                 WPS_DEV_TYPE_LEN);
+
+       wps->dev.num_sec_dev_types = wpa_s->conf->num_sec_device_types;
+       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);
-       wps->dev.rf_bands = WPS_RF_24GHZ | WPS_RF_50GHZ; /* TODO: config */
+       modes = wpa_s->hw.modes;
+       if (modes) {
+               for (m = 0; m < wpa_s->hw.num_modes; m++) {
+                       if (modes[m].mode == HOSTAPD_MODE_IEEE80211B ||
+                           modes[m].mode == HOSTAPD_MODE_IEEE80211G)
+                               wps->dev.rf_bands |= WPS_RF_24GHZ;
+                       else if (modes[m].mode == HOSTAPD_MODE_IEEE80211A)
+                               wps->dev.rf_bands |= WPS_RF_50GHZ;
+               }
+       }
+       if (wps->dev.rf_bands == 0) {
+               /*
+                * Default to claiming support for both bands if the driver
+                * does not provide support for fetching supported bands.
+                */
+               wps->dev.rf_bands = WPS_RF_24GHZ | WPS_RF_50GHZ;
+       }
        os_memcpy(wps->dev.mac_addr, wpa_s->own_addr, ETH_ALEN);
-       if (is_nil_uuid(wpa_s->conf->uuid)) {
-               uuid_gen_mac_addr(wpa_s->own_addr, wps->uuid);
-               wpa_hexdump(MSG_DEBUG, "WPS: UUID based on MAC address",
-                           wps->uuid, WPS_UUID_LEN);
-       } else
-               os_memcpy(wps->uuid, wpa_s->conf->uuid, WPS_UUID_LEN);
+       wpas_wps_set_uuid(wpa_s, wps);
 
        wps->auth_types = WPS_AUTH_WPA2PSK | WPS_AUTH_WPAPSK;
        wps->encr_types = WPS_ENCR_AES | WPS_ENCR_TKIP;
@@ -1076,6 +1253,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;
@@ -1275,6 +1453,7 @@ int wpas_wps_scan_pbc_overlap(struct wpa_supplicant *wpa_s,
 void wpas_wps_notify_scan_results(struct wpa_supplicant *wpa_s)
 {
        struct wpa_bss *bss;
+       unsigned int pbc = 0, auth = 0, pin = 0, wps = 0;
 
        if (wpa_s->disconnected || wpa_s->wpa_state >= WPA_ASSOCIATED)
                return;
@@ -1285,20 +1464,24 @@ void wpas_wps_notify_scan_results(struct wpa_supplicant *wpa_s)
                if (!ie)
                        continue;
                if (wps_is_selected_pbc_registrar(ie))
-                       wpa_msg_ctrl(wpa_s, MSG_INFO,
-                                    WPS_EVENT_AP_AVAILABLE_PBC);
+                       pbc++;
                else if (wps_is_addr_authorized(ie, wpa_s->own_addr, 0))
-                       wpa_msg_ctrl(wpa_s, MSG_INFO,
-                                    WPS_EVENT_AP_AVAILABLE_AUTH);
+                       auth++;
                else if (wps_is_selected_pin_registrar(ie))
-                       wpa_msg_ctrl(wpa_s, MSG_INFO,
-                                    WPS_EVENT_AP_AVAILABLE_PIN);
+                       pin++;
                else
-                       wpa_msg_ctrl(wpa_s, MSG_INFO,
-                                    WPS_EVENT_AP_AVAILABLE);
+                       wps++;
                wpabuf_free(ie);
-               break;
        }
+
+       if (pbc)
+               wpa_msg_ctrl(wpa_s, MSG_INFO, WPS_EVENT_AP_AVAILABLE_PBC);
+       else if (auth)
+               wpa_msg_ctrl(wpa_s, MSG_INFO, WPS_EVENT_AP_AVAILABLE_AUTH);
+       else if (pin)
+               wpa_msg_ctrl(wpa_s, MSG_INFO, WPS_EVENT_AP_AVAILABLE_PIN);
+       else if (wps)
+               wpa_msg_ctrl(wpa_s, MSG_INFO, WPS_EVENT_AP_AVAILABLE);
 }
 
 
@@ -1491,10 +1674,41 @@ 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)
 {
        wpa_printf(MSG_DEBUG, "WPS ER: Terminated");
-       eloop_terminate();
+       if (--callbacks_pending <= 0)
+               eloop_terminate();
 }
 #endif /* CONFIG_WPS_ER */
 
@@ -1503,6 +1717,7 @@ int wpas_wps_terminate_pending(struct wpa_supplicant *wpa_s)
 {
 #ifdef CONFIG_WPS_ER
        if (wpa_s->wps_er) {
+               callbacks_pending++;
                wps_er_deinit(wpa_s->wps_er, wpas_wps_terminate_cb, wpa_s);
                wpa_s->wps_er = NULL;
                return 1;
@@ -1546,24 +1761,24 @@ void wpas_wps_update_config(struct wpa_supplicant *wpa_s)
        }
        wps->config_methods = wps_fix_config_methods(wps->config_methods);
 
-       if (wpa_s->conf->changed_parameters & CFG_CHANGED_DEVICE_TYPE) {
-               if (wpa_s->conf->device_type &&
-                   wps_dev_type_str2bin(wpa_s->conf->device_type,
-                                        wps->dev.pri_dev_type) < 0)
-                       wpa_printf(MSG_ERROR, "WPS: Invalid device_type");
+       if (wpa_s->conf->changed_parameters & CFG_CHANGED_DEVICE_TYPE)
+               os_memcpy(wps->dev.pri_dev_type, wpa_s->conf->device_type,
+                         WPS_DEV_TYPE_LEN);
+
+       if (wpa_s->conf->changed_parameters & CFG_CHANGED_SEC_DEVICE_TYPE) {
+               wps->dev.num_sec_dev_types = wpa_s->conf->num_sec_device_types;
+               os_memcpy(wps->dev.sec_dev_type, wpa_s->conf->sec_device_type,
+                         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);
 
-       if (wpa_s->conf->changed_parameters & CFG_CHANGED_UUID) {
-               if (is_nil_uuid(wpa_s->conf->uuid)) {
-                       uuid_gen_mac_addr(wpa_s->own_addr, wps->uuid);
-                       wpa_hexdump(MSG_DEBUG, "WPS: UUID based on MAC "
-                                   "address", wps->uuid, WPS_UUID_LEN);
-               } else
-                       os_memcpy(wps->uuid, wpa_s->conf->uuid, WPS_UUID_LEN);
-       }
+       if (wpa_s->conf->changed_parameters & CFG_CHANGED_UUID)
+               wpas_wps_set_uuid(wpa_s, wps);
 
        if (wpa_s->conf->changed_parameters &
            (CFG_CHANGED_DEVICE_NAME | CFG_CHANGED_WPS_STRING)) {
@@ -1575,3 +1790,132 @@ 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 */