summaryrefslogtreecommitdiffstats
path: root/src/wps
diff options
context:
space:
mode:
Diffstat (limited to 'src/wps')
-rw-r--r--src/wps/.gitignore1
-rw-r--r--src/wps/httpread.c7
-rw-r--r--src/wps/wps.h21
-rw-r--r--src/wps/wps_attr_parse.c8
-rw-r--r--src/wps/wps_common.c68
-rw-r--r--src/wps/wps_enrollee.c47
-rw-r--r--src/wps/wps_i.h8
-rw-r--r--src/wps/wps_registrar.c234
-rw-r--r--src/wps/wps_upnp.c109
-rw-r--r--src/wps/wps_upnp_event.c4
-rw-r--r--src/wps/wps_upnp_i.h6
-rw-r--r--src/wps/wps_upnp_ssdp.c45
-rw-r--r--src/wps/wps_upnp_web.c2
13 files changed, 453 insertions, 107 deletions
diff --git a/src/wps/.gitignore b/src/wps/.gitignore
deleted file mode 100644
index a438335..0000000
--- a/src/wps/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*.d
diff --git a/src/wps/httpread.c b/src/wps/httpread.c
index 313b468..0d7165e 100644
--- a/src/wps/httpread.c
+++ b/src/wps/httpread.c
@@ -206,7 +206,8 @@ static int httpread_hdr_option_analyze(
h->got_content_length = 1;
return 0;
}
- if (word_eq(hbp, "TRANSFER_ENCODING:")) {
+ if (word_eq(hbp, "TRANSFER_ENCODING:") ||
+ word_eq(hbp, "TRANSFER-ENCODING:")) {
while (isgraph(*hbp))
hbp++;
while (*hbp == ' ' || *hbp == '\t')
@@ -214,7 +215,7 @@ static int httpread_hdr_option_analyze(
/* There should (?) be no encodings of interest
* other than chunked...
*/
- if (os_strncmp(hbp, "CHUNKED", 7)) {
+ if (word_eq(hbp, "CHUNKED")) {
h->chunked = 1;
h->in_chunk_data = 0;
/* ignore possible ;<parameters> */
@@ -513,6 +514,8 @@ static void httpread_read_handler(int sd, void *eloop_ctx, void *sock_ctx)
* consists of chunks each with a header, ending with
* an ending header.
*/
+ if (nread == 0)
+ goto get_more;
if (!h->got_body) {
/* Here to get (more of) body */
/* ensure we have enough room for worst case for body
diff --git a/src/wps/wps.h b/src/wps/wps.h
index e0f2b2d..faf32c4 100644
--- a/src/wps/wps.h
+++ b/src/wps/wps.h
@@ -42,7 +42,7 @@ struct upnp_wps_device_sm;
* @key_idx: Key index
* @key: Key
* @key_len: Key length in octets
- * @mac_addr: MAC address of the peer
+ * @mac_addr: MAC address of the Credential receiver
* @cred_attr: Unparsed Credential attribute data (used only in cred_cb());
* this may be %NULL, if not used
* @cred_attr_len: Length of cred_attr in octets
@@ -266,6 +266,11 @@ struct wps_registrar_config {
* to be set with a suitable Credential and skip_cred_build being used.
*/
int disable_auto_conf;
+
+ /**
+ * static_wep_only - Whether the BSS supports only static WEP
+ */
+ int static_wep_only;
};
@@ -291,7 +296,17 @@ enum wps_event {
/**
* WPS_EV_PWD_AUTH_FAIL - Password authentication failed
*/
- WPS_EV_PWD_AUTH_FAIL
+ WPS_EV_PWD_AUTH_FAIL,
+
+ /**
+ * WPS_EV_PBC_OVERLAP - PBC session overlap detected
+ */
+ WPS_EV_PBC_OVERLAP,
+
+ /**
+ * WPS_EV_PBC_TIMEOUT - PBC walktime expired before protocol run start
+ */
+ WPS_EV_PBC_TIMEOUT
};
/**
@@ -500,7 +515,7 @@ wps_registrar_init(struct wps_context *wps,
const struct wps_registrar_config *cfg);
void wps_registrar_deinit(struct wps_registrar *reg);
int wps_registrar_add_pin(struct wps_registrar *reg, const u8 *uuid,
- const u8 *pin, size_t pin_len);
+ const u8 *pin, size_t pin_len, int timeout);
int wps_registrar_invalidate_pin(struct wps_registrar *reg, const u8 *uuid);
int wps_registrar_unlock_pin(struct wps_registrar *reg, const u8 *uuid);
int wps_registrar_button_pushed(struct wps_registrar *reg);
diff --git a/src/wps/wps_attr_parse.c b/src/wps/wps_attr_parse.c
index 25ff251..f50ae39 100644
--- a/src/wps/wps_attr_parse.c
+++ b/src/wps/wps_attr_parse.c
@@ -381,6 +381,14 @@ static int wps_set_attr(struct wps_parse_attr *attr, u16 type,
attr->eap_identity = pos;
attr->eap_identity_len = len;
break;
+ case ATTR_AP_SETUP_LOCKED:
+ if (len != 1) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid AP Setup Locked "
+ "length %u", len);
+ return -1;
+ }
+ attr->ap_setup_locked = pos;
+ break;
default:
wpa_printf(MSG_DEBUG, "WPS: Unsupported attribute type 0x%x "
"len=%u", type, len);
diff --git a/src/wps/wps_common.c b/src/wps/wps_common.c
index 48af303..4b45f00 100644
--- a/src/wps/wps_common.c
+++ b/src/wps/wps_common.c
@@ -128,56 +128,6 @@ int wps_derive_keys(struct wps_data *wps)
}
-int wps_derive_mgmt_keys(struct wps_data *wps)
-{
- u8 nonces[2 * WPS_NONCE_LEN];
- u8 keys[WPS_MGMTAUTHKEY_LEN + WPS_MGMTENCKEY_LEN];
- u8 hash[SHA256_MAC_LEN];
- const u8 *addr[2];
- size_t len[2];
- const char *auth_label = "WFA-WLAN-Management-MgmtAuthKey";
- const char *enc_label = "WFA-WLAN-Management-MgmtEncKey";
-
- /* MgmtAuthKey || MgmtEncKey =
- * kdf(EMSK, N1 || N2 || "WFA-WLAN-Management-Keys", 384) */
- os_memcpy(nonces, wps->nonce_e, WPS_NONCE_LEN);
- os_memcpy(nonces + WPS_NONCE_LEN, wps->nonce_r, WPS_NONCE_LEN);
- wps_kdf(wps->emsk, nonces, sizeof(nonces), "WFA-WLAN-Management-Keys",
- keys, sizeof(keys));
- os_memcpy(wps->mgmt_auth_key, keys, WPS_MGMTAUTHKEY_LEN);
- os_memcpy(wps->mgmt_enc_key, keys + WPS_MGMTAUTHKEY_LEN,
- WPS_MGMTENCKEY_LEN);
-
- addr[0] = nonces;
- len[0] = sizeof(nonces);
-
- /* MgmtEncKeyID = first 128 bits of
- * SHA-256(N1 || N2 || "WFA-WLAN-Management-MgmtAuthKey") */
- addr[1] = (const u8 *) auth_label;
- len[1] = os_strlen(auth_label);
- sha256_vector(2, addr, len, hash);
- os_memcpy(wps->mgmt_auth_key_id, hash, WPS_MGMT_KEY_ID_LEN);
-
- /* MgmtEncKeyID = first 128 bits of
- * SHA-256(N1 || N2 || "WFA-WLAN-Management-MgmtEncKey") */
- addr[1] = (const u8 *) enc_label;
- len[1] = os_strlen(enc_label);
- sha256_vector(2, addr, len, hash);
- os_memcpy(wps->mgmt_enc_key_id, hash, WPS_MGMT_KEY_ID_LEN);
-
- wpa_hexdump_key(MSG_DEBUG, "WPS: MgmtAuthKey",
- wps->mgmt_auth_key, WPS_MGMTAUTHKEY_LEN);
- wpa_hexdump(MSG_DEBUG, "WPS: MgmtAuthKeyID",
- wps->mgmt_auth_key_id, WPS_MGMT_KEY_ID_LEN);
- wpa_hexdump_key(MSG_DEBUG, "WPS: MgmtEncKey",
- wps->mgmt_enc_key, WPS_MGMTENCKEY_LEN);
- wpa_hexdump(MSG_DEBUG, "WPS: MgmtEncKeyID",
- wps->mgmt_enc_key_id, WPS_MGMT_KEY_ID_LEN);
-
- return 0;
-}
-
-
void wps_derive_psk(struct wps_data *wps, const u8 *dev_passwd,
size_t dev_passwd_len)
{
@@ -335,3 +285,21 @@ void wps_pwd_auth_fail_event(struct wps_context *wps, int enrollee, int part)
data.pwd_auth_fail.part = part;
wps->event_cb(wps->cb_ctx, WPS_EV_PWD_AUTH_FAIL, &data);
}
+
+
+void wps_pbc_overlap_event(struct wps_context *wps)
+{
+ if (wps->event_cb == NULL)
+ return;
+
+ wps->event_cb(wps->cb_ctx, WPS_EV_PBC_OVERLAP, NULL);
+}
+
+
+void wps_pbc_timeout_event(struct wps_context *wps)
+{
+ if (wps->event_cb == NULL)
+ return;
+
+ wps->event_cb(wps->cb_ctx, WPS_EV_PBC_TIMEOUT, NULL);
+}
diff --git a/src/wps/wps_enrollee.c b/src/wps/wps_enrollee.c
index 179f7db..5cb3e1e 100644
--- a/src/wps/wps_enrollee.c
+++ b/src/wps/wps_enrollee.c
@@ -41,7 +41,7 @@ static int wps_build_wps_state(struct wps_data *wps, struct wpabuf *msg)
state);
wpabuf_put_be16(msg, ATTR_WPS_STATE);
wpabuf_put_be16(msg, 1);
- wpabuf_put_u8(msg, WPS_STATE_NOT_CONFIGURED);
+ wpabuf_put_u8(msg, state);
return 0;
}
@@ -521,10 +521,6 @@ static int wps_process_pubkey(struct wps_data *wps, const u8 *pk,
if (wps_derive_keys(wps) < 0)
return -1;
- if (wps->request_type == WPS_REQ_WLAN_MANAGER_REGISTRAR &&
- wps_derive_mgmt_keys(wps) < 0)
- return -1;
-
return 0;
}
@@ -650,6 +646,21 @@ static int wps_process_cred_e(struct wps_data *wps, const u8 *cred,
wps_process_cred(&attr, &wps->cred))
return -1;
+ if (os_memcmp(wps->cred.mac_addr, wps->wps->dev.mac_addr, ETH_ALEN) !=
+ 0) {
+ wpa_printf(MSG_DEBUG, "WPS: MAC Address in the Credential ("
+ MACSTR ") does not match with own address (" MACSTR
+ ")", MAC2STR(wps->cred.mac_addr),
+ MAC2STR(wps->wps->dev.mac_addr));
+ /*
+ * In theory, this could be consider fatal error, but there are
+ * number of deployed implementations using other address here
+ * due to unclarity in the specification. For interoperability
+ * reasons, allow this to be processed since we do not really
+ * use the MAC Address information for anything.
+ */
+ }
+
if (wps->wps->cred_cb) {
wps->cred.cred_attr = cred - 4;
wps->cred.cred_attr_len = cred_len + 4;
@@ -700,6 +711,21 @@ static int wps_process_ap_settings_e(struct wps_data *wps,
wpa_printf(MSG_INFO, "WPS: Received new AP configuration from "
"Registrar");
+ if (os_memcmp(cred.mac_addr, wps->wps->dev.mac_addr, ETH_ALEN) !=
+ 0) {
+ wpa_printf(MSG_DEBUG, "WPS: MAC Address in the AP Settings ("
+ MACSTR ") does not match with own address (" MACSTR
+ ")", MAC2STR(cred.mac_addr),
+ MAC2STR(wps->wps->dev.mac_addr));
+ /*
+ * In theory, this could be consider fatal error, but there are
+ * number of deployed implementations using other address here
+ * due to unclarity in the specification. For interoperability
+ * reasons, allow this to be processed since we do not really
+ * use the MAC Address information for anything.
+ */
+ }
+
if (wps->wps->cred_cb) {
cred.cred_attr = wpabuf_head(attrs);
cred.cred_attr_len = wpabuf_len(attrs);
@@ -1159,6 +1185,17 @@ enum wps_process_res wps_enrollee_process_msg(struct wps_data *wps,
"op_code=%d)",
(unsigned long) wpabuf_len(msg), op_code);
+ if (op_code == WSC_UPnP) {
+ /* Determine the OpCode based on message type attribute */
+ struct wps_parse_attr attr;
+ if (wps_parse_msg(msg, &attr) == 0 && attr.msg_type) {
+ if (*attr.msg_type == WPS_WSC_ACK)
+ op_code = WSC_ACK;
+ else if (*attr.msg_type == WPS_WSC_NACK)
+ op_code = WSC_NACK;
+ }
+ }
+
switch (op_code) {
case WSC_MSG:
case WSC_UPnP:
diff --git a/src/wps/wps_i.h b/src/wps/wps_i.h
index 85adf28..3317a2c 100644
--- a/src/wps/wps_i.h
+++ b/src/wps/wps_i.h
@@ -62,10 +62,6 @@ struct wps_data {
u8 authkey[WPS_AUTHKEY_LEN];
u8 keywrapkey[WPS_KEYWRAPKEY_LEN];
u8 emsk[WPS_EMSK_LEN];
- u8 mgmt_auth_key[WPS_MGMTAUTHKEY_LEN];
- u8 mgmt_auth_key_id[WPS_MGMT_KEY_ID_LEN];
- u8 mgmt_enc_key[WPS_MGMTENCKEY_LEN];
- u8 mgmt_enc_key_id[WPS_MGMT_KEY_ID_LEN];
struct wpabuf *last_msg;
@@ -146,6 +142,7 @@ struct wps_parse_attr {
const u8 *selected_registrar; /* 1 octet (Bool) */
const u8 *request_type; /* 1 octet */
const u8 *response_type; /* 1 octet */
+ const u8 *ap_setup_locked; /* 1 octet */
/* variable length fields */
const u8 *manufacturer;
@@ -182,7 +179,6 @@ struct wps_parse_attr {
void wps_kdf(const u8 *key, const u8 *label_prefix, size_t label_prefix_len,
const char *label, u8 *res, size_t res_len);
int wps_derive_keys(struct wps_data *wps);
-int wps_derive_mgmt_keys(struct wps_data *wps);
void wps_derive_psk(struct wps_data *wps, const u8 *dev_passwd,
size_t dev_passwd_len);
struct wpabuf * wps_decrypt_encr_settings(struct wps_data *wps, const u8 *encr,
@@ -190,6 +186,8 @@ struct wpabuf * wps_decrypt_encr_settings(struct wps_data *wps, const u8 *encr,
void wps_fail_event(struct wps_context *wps, enum wps_msg_type msg);
void wps_success_event(struct wps_context *wps);
void wps_pwd_auth_fail_event(struct wps_context *wps, int enrollee, int part);
+void wps_pbc_overlap_event(struct wps_context *wps);
+void wps_pbc_timeout_event(struct wps_context *wps);
/* wps_attr_parse.c */
int wps_parse_msg(const struct wpabuf *msg, struct wps_parse_attr *attr);
diff --git a/src/wps/wps_registrar.c b/src/wps/wps_registrar.c
index f7eebdd..f34c9e9 100644
--- a/src/wps/wps_registrar.c
+++ b/src/wps/wps_registrar.c
@@ -23,6 +23,7 @@
#include "wps_dev_attr.h"
#include "wps_upnp.h"
+#define WPS_WORKAROUNDS
struct wps_uuid_pin {
struct wps_uuid_pin *next;
@@ -30,7 +31,10 @@ struct wps_uuid_pin {
int wildcard_uuid;
u8 *pin;
size_t pin_len;
- int locked;
+#define PIN_LOCKED BIT(0)
+#define PIN_EXPIRES BIT(1)
+ int flags;
+ struct os_time expiration;
};
@@ -98,6 +102,9 @@ struct wps_registrar {
int disable_auto_conf;
int sel_reg_dev_password_id_override;
int sel_reg_config_methods_override;
+ int static_wep_only;
+
+ int force_pbc_overlap;
};
@@ -376,6 +383,7 @@ wps_registrar_init(struct wps_context *wps,
reg->disable_auto_conf = cfg->disable_auto_conf;
reg->sel_reg_dev_password_id_override = -1;
reg->sel_reg_config_methods_override = -1;
+ reg->static_wep_only = cfg->static_wep_only;
if (wps_set_ie(reg)) {
wps_registrar_deinit(reg);
@@ -409,10 +417,11 @@ void wps_registrar_deinit(struct wps_registrar *reg)
* @uuid: UUID-E or %NULL for wildcard (any UUID)
* @pin: PIN (Device Password)
* @pin_len: Length of pin in octets
+ * @timeout: Time (in seconds) when the PIN will be invalidated; 0 = no timeout
* Returns: 0 on success, -1 on failure
*/
int wps_registrar_add_pin(struct wps_registrar *reg, const u8 *uuid,
- const u8 *pin, size_t pin_len)
+ const u8 *pin, size_t pin_len, int timeout)
{
struct wps_uuid_pin *p;
@@ -431,20 +440,59 @@ int wps_registrar_add_pin(struct wps_registrar *reg, const u8 *uuid,
os_memcpy(p->pin, pin, pin_len);
p->pin_len = pin_len;
+ if (timeout) {
+ p->flags |= PIN_EXPIRES;
+ os_get_time(&p->expiration);
+ p->expiration.sec += timeout;
+ }
+
p->next = reg->pins;
reg->pins = p;
- wpa_printf(MSG_DEBUG, "WPS: A new PIN configured");
+ wpa_printf(MSG_DEBUG, "WPS: A new PIN configured (timeout=%d)",
+ timeout);
wpa_hexdump(MSG_DEBUG, "WPS: UUID", uuid, WPS_UUID_LEN);
wpa_hexdump_ascii_key(MSG_DEBUG, "WPS: PIN", pin, pin_len);
reg->selected_registrar = 1;
reg->pbc = 0;
wps_set_ie(reg);
+ eloop_cancel_timeout(wps_registrar_set_selected_timeout, reg, NULL);
+ eloop_register_timeout(WPS_PBC_WALK_TIME, 0,
+ wps_registrar_set_selected_timeout,
+ reg, NULL);
return 0;
}
+static void wps_registrar_expire_pins(struct wps_registrar *reg)
+{
+ struct wps_uuid_pin *pin, *prev, *del;
+ struct os_time now;
+
+ os_get_time(&now);
+ prev = NULL;
+ pin = reg->pins;
+ while (pin) {
+ if ((pin->flags & PIN_EXPIRES) &&
+ os_time_before(&pin->expiration, &now)) {
+ if (prev == NULL)
+ reg->pins = pin->next;
+ else
+ prev->next = pin->next;
+ del = pin;
+ pin = pin->next;
+ wpa_hexdump(MSG_DEBUG, "WPS: Expired PIN for UUID",
+ del->uuid, WPS_UUID_LEN);
+ wps_free_pin(del);
+ continue;
+ }
+ prev = pin;
+ pin = pin->next;
+ }
+}
+
+
/**
* wps_registrar_invalidate_pin - Invalidate a PIN for a specific UUID-E
* @reg: Registrar data from wps_registrar_init()
@@ -481,6 +529,8 @@ static const u8 * wps_registrar_get_pin(struct wps_registrar *reg,
{
struct wps_uuid_pin *pin;
+ wps_registrar_expire_pins(reg);
+
pin = reg->pins;
while (pin) {
if (!pin->wildcard_uuid &&
@@ -512,13 +562,13 @@ static const u8 * wps_registrar_get_pin(struct wps_registrar *reg,
* Lock the PIN to avoid attacks based on concurrent re-use of the PIN
* that could otherwise avoid PIN invalidations.
*/
- if (pin->locked) {
+ if (pin->flags & PIN_LOCKED) {
wpa_printf(MSG_DEBUG, "WPS: Selected PIN locked - do not "
"allow concurrent re-use");
return NULL;
}
*pin_len = pin->pin_len;
- pin->locked = 1;
+ pin->flags |= PIN_LOCKED;
return pin->pin;
}
@@ -545,7 +595,7 @@ int wps_registrar_unlock_pin(struct wps_registrar *reg, const u8 *uuid)
"wildcard PIN");
return wps_registrar_invalidate_pin(reg, uuid);
}
- pin->locked = 0;
+ pin->flags &= ~PIN_LOCKED;
return 0;
}
pin = pin->next;
@@ -568,6 +618,7 @@ static void wps_registrar_pbc_timeout(void *eloop_ctx, void *timeout_ctx)
struct wps_registrar *reg = eloop_ctx;
wpa_printf(MSG_DEBUG, "WPS: PBC timed out - disable PBC mode");
+ wps_pbc_timeout_event(reg->wps);
wps_registrar_stop_pbc(reg);
}
@@ -586,9 +637,11 @@ int wps_registrar_button_pushed(struct wps_registrar *reg)
if (wps_registrar_pbc_overlap(reg, NULL, NULL)) {
wpa_printf(MSG_DEBUG, "WPS: PBC overlap - do not start PBC "
"mode");
+ wps_pbc_overlap_event(reg->wps);
return -1;
}
wpa_printf(MSG_DEBUG, "WPS: Button pushed - PBC mode started");
+ reg->force_pbc_overlap = 0;
reg->selected_registrar = 1;
reg->pbc = 1;
wps_set_ie(reg);
@@ -607,6 +660,14 @@ static void wps_registrar_pbc_completed(struct wps_registrar *reg)
wps_registrar_stop_pbc(reg);
}
+static void wps_registrar_pin_completed(struct wps_registrar *reg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: PIN completed using internal Registrar");
+ eloop_cancel_timeout(wps_registrar_set_selected_timeout, reg, NULL);
+ reg->selected_registrar = 0;
+ wps_set_ie(reg);
+}
+
/**
* wps_registrar_probe_req_rx - Notify Registrar of Probe Request
@@ -648,8 +709,18 @@ void wps_registrar_probe_req_rx(struct wps_registrar *reg, const u8 *addr,
wpa_printf(MSG_DEBUG, "WPS: Probe Request for PBC received from "
MACSTR, MAC2STR(addr));
+ if (attr.uuid_e == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Probe Request WPS IE: No "
+ "UUID-E included");
+ return;
+ }
wps_registrar_add_pbc_session(reg, addr, attr.uuid_e);
+ if (wps_registrar_pbc_overlap(reg, addr, attr.uuid_e)) {
+ wpa_printf(MSG_DEBUG, "WPS: PBC session overlap detected");
+ reg->force_pbc_overlap = 1;
+ wps_pbc_overlap_event(reg->wps);
+ }
}
@@ -777,6 +848,28 @@ static int wps_set_ie(struct wps_registrar *reg)
return -1;
}
+ if (reg->static_wep_only) {
+ /*
+ * Windows XP and Vista clients can get confused about
+ * EAP-Identity/Request when they probe the network with
+ * EAPOL-Start. In such a case, they may assume the network is
+ * using IEEE 802.1X and prompt user for a certificate while
+ * the correct (non-WPS) behavior would be to ask for the
+ * static WEP key. As a workaround, use Microsoft Provisioning
+ * IE to advertise that legacy 802.1X is not supported.
+ */
+ const u8 ms_wps[7] = {
+ WLAN_EID_VENDOR_SPECIFIC, 5,
+ /* Microsoft Provisioning IE (00:50:f2:5) */
+ 0x00, 0x50, 0xf2, 5,
+ 0x00 /* no legacy 802.1X or MS WPS */
+ };
+ wpa_printf(MSG_DEBUG, "WPS: Add Microsoft Provisioning IE "
+ "into Beacon/Probe Response frames");
+ wpabuf_put_data(beacon, ms_wps, sizeof(ms_wps));
+ wpabuf_put_data(probe, ms_wps, sizeof(ms_wps));
+ }
+
ret = wps_cb_set_ie(reg, beacon, probe);
wpabuf_free(beacon);
wpabuf_free(probe);
@@ -1033,8 +1126,10 @@ static int wps_build_cred(struct wps_data *wps, struct wpabuf *msg)
}
}
wps->cred.encr_type = wps->encr_type;
- /* Set MAC address in the Credential to be the AP's address (BSSID) */
- os_memcpy(wps->cred.mac_addr, wps->wps->dev.mac_addr, ETH_ALEN);
+ /*
+ * Set MAC address in the Credential to be the Enrollee's MAC address
+ */
+ os_memcpy(wps->cred.mac_addr, wps->mac_addr_e, ETH_ALEN);
if (wps->wps->wps_state == WPS_STATE_NOT_CONFIGURED && wps->wps->ap &&
!wps->wps->registrar->disable_auto_conf) {
@@ -1159,14 +1254,15 @@ static struct wpabuf * wps_build_m2(struct wps_data *wps)
static struct wpabuf * wps_build_m2d(struct wps_data *wps)
{
struct wpabuf *msg;
- u16 err = WPS_CFG_NO_ERROR;
+ u16 err = wps->config_error;
wpa_printf(MSG_DEBUG, "WPS: Building Message M2D");
msg = wpabuf_alloc(1000);
if (msg == NULL)
return NULL;
- if (wps->wps->ap && wps->wps->ap_setup_locked)
+ if (wps->wps->ap && wps->wps->ap_setup_locked &&
+ err == WPS_CFG_NO_ERROR)
err = WPS_CFG_SETUP_LOCKED;
if (wps_build_version(msg) ||
@@ -1368,8 +1464,18 @@ struct wpabuf * wps_registrar_get_msg(struct wps_data *wps,
else
wps->wps->upnp_msgs = NULL;
msg = p->msg;
+ switch (p->type) {
+ case WPS_WSC_ACK:
+ *op_code = WSC_ACK;
+ break;
+ case WPS_WSC_NACK:
+ *op_code = WSC_NACK;
+ break;
+ default:
+ *op_code = WSC_MSG;
+ break;
+ }
os_free(p);
- *op_code = WSC_MSG;
if (wps->ext_reg == 0)
wps->ext_reg = 1;
return msg;
@@ -1654,7 +1760,21 @@ static int wps_process_auth_type_flags(struct wps_data *wps, const u8 *auth)
wpa_printf(MSG_DEBUG, "WPS: No match in supported "
"authentication types (own 0x%x Enrollee 0x%x)",
wps->wps->auth_types, auth_types);
+#ifdef WPS_WORKAROUNDS
+ /*
+ * Some deployed implementations seem to advertise incorrect
+ * information in this attribute. For example, Linksys WRT350N
+ * seems to have a byteorder bug that breaks this negotiation.
+ * In order to interoperate with existing implementations,
+ * assume that the Enrollee supports everything we do.
+ */
+ wpa_printf(MSG_DEBUG, "WPS: Workaround - assume Enrollee "
+ "does not advertise supported authentication types "
+ "correctly");
+ wps->auth_type = wps->wps->auth_types;
+#else /* WPS_WORKAROUNDS */
return -1;
+#endif /* WPS_WORKAROUNDS */
}
return 0;
@@ -1678,8 +1798,23 @@ static int wps_process_encr_type_flags(struct wps_data *wps, const u8 *encr)
wps->encr_type = wps->wps->encr_types & encr_types;
if (wps->encr_type == 0) {
wpa_printf(MSG_DEBUG, "WPS: No match in supported "
- "encryption types");
+ "encryption types (own 0x%x Enrollee 0x%x)",
+ wps->wps->encr_types, encr_types);
+#ifdef WPS_WORKAROUNDS
+ /*
+ * Some deployed implementations seem to advertise incorrect
+ * information in this attribute. For example, Linksys WRT350N
+ * seems to have a byteorder bug that breaks this negotiation.
+ * In order to interoperate with existing implementations,
+ * assume that the Enrollee supports everything we do.
+ */
+ wpa_printf(MSG_DEBUG, "WPS: Workaround - assume Enrollee "
+ "does not advertise supported encryption types "
+ "correctly");
+ wps->encr_type = wps->wps->encr_types;
+#else /* WPS_WORKAROUNDS */
return -1;
+#endif /* WPS_WORKAROUNDS */
}
return 0;
@@ -1806,11 +1941,15 @@ static enum wps_process_res wps_process_m1(struct wps_data *wps,
}
if (wps->dev_pw_id == DEV_PW_PUSHBUTTON) {
- if (wps_registrar_pbc_overlap(wps->wps->registrar,
+ if (wps->wps->registrar->force_pbc_overlap ||
+ wps_registrar_pbc_overlap(wps->wps->registrar,
wps->mac_addr_e, wps->uuid_e)) {
wpa_printf(MSG_DEBUG, "WPS: PBC overlap - deny PBC "
"negotiation");
wps->state = SEND_M2D;
+ wps->config_error = WPS_CFG_MULTIPLE_PBC_DETECTED;
+ wps_pbc_overlap_event(wps->wps);
+ wps->wps->registrar->force_pbc_overlap = 1;
return WPS_CONTINUE;
}
wps_registrar_add_pbc_session(wps->wps->registrar,
@@ -1836,6 +1975,14 @@ static enum wps_process_res wps_process_m3(struct wps_data *wps,
return WPS_CONTINUE;
}
+ if (wps->pbc && wps->wps->registrar->force_pbc_overlap) {
+ wpa_printf(MSG_DEBUG, "WPS: Reject negotiation due to PBC "
+ "session overlap");
+ wps->state = SEND_WSC_NACK;
+ wps->config_error = WPS_CFG_MULTIPLE_PBC_DETECTED;
+ return WPS_CONTINUE;
+ }
+
if (wps_process_registrar_nonce(wps, attr->registrar_nonce) ||
wps_process_authenticator(wps, attr->authenticator, msg) ||
wps_process_e_hash1(wps, attr->e_hash1) ||
@@ -1865,6 +2012,14 @@ static enum wps_process_res wps_process_m5(struct wps_data *wps,
return WPS_CONTINUE;
}
+ if (wps->pbc && wps->wps->registrar->force_pbc_overlap) {
+ wpa_printf(MSG_DEBUG, "WPS: Reject negotiation due to PBC "
+ "session overlap");
+ wps->state = SEND_WSC_NACK;
+ wps->config_error = WPS_CFG_MULTIPLE_PBC_DETECTED;
+ return WPS_CONTINUE;
+ }
+
if (wps_process_registrar_nonce(wps, attr->registrar_nonce) ||
wps_process_authenticator(wps, attr->authenticator, msg)) {
wps->state = SEND_WSC_NACK;
@@ -1896,6 +2051,28 @@ static enum wps_process_res wps_process_m5(struct wps_data *wps,
}
+static void wps_sta_cred_cb(struct wps_data *wps)
+{
+ /*
+ * Update credential to only include a single authentication and
+ * encryption type in case the AP configuration includes more than one
+ * option.
+ */
+ if (wps->cred.auth_type & WPS_AUTH_WPA2PSK)
+ wps->cred.auth_type = WPS_AUTH_WPA2PSK;
+ else if (wps->cred.auth_type & WPS_AUTH_WPAPSK)
+ wps->cred.auth_type = WPS_AUTH_WPAPSK;
+ if (wps->cred.encr_type & WPS_ENCR_AES)
+ wps->cred.encr_type = WPS_ENCR_AES;
+ else if (wps->cred.encr_type & WPS_ENCR_TKIP)
+ wps->cred.encr_type = WPS_ENCR_TKIP;
+ wpa_printf(MSG_DEBUG, "WPS: Update local configuration based on the "
+ "AP configuration");
+ if (wps->wps->cred_cb)
+ wps->wps->cred_cb(wps->wps->cb_ctx, &wps->cred);
+}
+
+
static int wps_process_ap_settings_r(struct wps_data *wps,
struct wps_parse_attr *attr)
{
@@ -1908,12 +2085,21 @@ static int wps_process_ap_settings_r(struct wps_data *wps,
wpa_printf(MSG_INFO, "WPS: Received old AP configuration from AP");
+#if 0
/*
* TODO: Provide access to AP settings and allow changes before sending
* out M8. For now, just copy the settings unchanged into M8.
*/
return 0;
+#else
+ /*
+ * For now, use the AP PIN only to receive the current AP settings,
+ * not to reconfigure the AP.
+ */
+ wps_sta_cred_cb(wps);
+ return 1;
+#endif
}
@@ -1933,6 +2119,14 @@ static enum wps_process_res wps_process_m7(struct wps_data *wps,
return WPS_CONTINUE;
}
+ if (wps->pbc && wps->wps->registrar->force_pbc_overlap) {
+ wpa_printf(MSG_DEBUG, "WPS: Reject negotiation due to PBC "
+ "session overlap");
+ wps->state = SEND_WSC_NACK;
+ wps->config_error = WPS_CFG_MULTIPLE_PBC_DETECTED;
+ return WPS_CONTINUE;
+ }
+
if (wps_process_registrar_nonce(wps, attr->registrar_nonce) ||
wps_process_authenticator(wps, attr->authenticator, msg)) {
wps->state = SEND_WSC_NACK;
@@ -2281,12 +2475,8 @@ static enum wps_process_res wps_process_wsc_done(struct wps_data *wps,
wps->new_psk = NULL;
}
- if (!wps->wps->ap) {
- wpa_printf(MSG_DEBUG, "WPS: Update local configuration based "
- "on the modified AP configuration");
- if (wps->wps->cred_cb)
- wps->wps->cred_cb(wps->wps->cb_ctx, &wps->cred);
- }
+ if (!wps->wps->ap)
+ wps_sta_cred_cb(wps);
if (wps->new_psk) {
if (wps_cb_new_psk(wps->wps->registrar, wps->mac_addr_e,
@@ -2304,6 +2494,8 @@ static enum wps_process_res wps_process_wsc_done(struct wps_data *wps,
wps_registrar_remove_pbc_session(wps->wps->registrar,
wps->mac_addr_e, wps->uuid_e);
wps_registrar_pbc_completed(wps->wps->registrar);
+ } else {
+ wps_registrar_pin_completed(wps->wps->registrar);
}
wps_success_event(wps->wps);
@@ -2333,7 +2525,8 @@ enum wps_process_res wps_registrar_process_msg(struct wps_data *wps,
wps_registrar_free_pending_m2(wps->wps);
if (wps->wps->wps_upnp && wps->ext_reg &&
wps->wps->upnp_msgs == NULL &&
- (op_code == WSC_MSG || op_code == WSC_Done)) {
+ (op_code == WSC_MSG || op_code == WSC_Done || op_code == WSC_NACK))
+ {
struct wps_parse_attr attr;
int type;
if (wps_parse_msg(msg, &attr) < 0 || attr.msg_type == NULL)
@@ -2401,7 +2594,6 @@ static void wps_registrar_set_selected_timeout(void *eloop_ctx,
* wps_registrar_set_selected_registrar - Notification of SetSelectedRegistrar
* @reg: Registrar data from wps_registrar_init()
* @msg: Received message from SetSelectedRegistrar
- * @msg_len: Length of msg in octets
* Returns: 0 on success, -1 on failure
*
* This function is called when an AP receives a SetSelectedRegistrar UPnP
diff --git a/src/wps/wps_upnp.c b/src/wps/wps_upnp.c
index 6d2de0e..4c6aac2 100644
--- a/src/wps/wps_upnp.c
+++ b/src/wps/wps_upnp.c
@@ -47,9 +47,6 @@
* -- Needs renaming with module prefix to avoid polluting the debugger
* namespace and causing possible collisions with other static fncs
* and structure declarations when using the debugger.
- * -- Just what should be in the first event message sent after subscription
- * for the WLANEvent field? If i pass it empty, Vista replies with OK
- * but apparently barfs on the message.
* -- The http error code generation is pretty bogus, hopefully noone cares.
*
* Author: Ted Merrill, Atheros Communications, based upon earlier work
@@ -641,6 +638,27 @@ struct subscription * subscription_find(struct upnp_wps_device_sm *sm,
}
+static struct wpabuf * build_fake_wsc_ack(void)
+{
+ struct wpabuf *msg = wpabuf_alloc(100);
+ if (msg == NULL)
+ return NULL;
+ wpabuf_put_u8(msg, UPNP_WPS_WLANEVENT_TYPE_EAP);
+ wpabuf_put_str(msg, "00:00:00:00:00:00");
+ wps_build_version(msg);
+ wps_build_msg_type(msg, WPS_WSC_ACK);
+ /* Enrollee Nonce */
+ wpabuf_put_be16(msg, ATTR_ENROLLEE_NONCE);
+ wpabuf_put_be16(msg, WPS_NONCE_LEN);
+ wpabuf_put(msg, WPS_NONCE_LEN);
+ /* Registrar Nonce */
+ wpabuf_put_be16(msg, ATTR_REGISTRAR_NONCE);
+ wpabuf_put_be16(msg, WPS_NONCE_LEN);
+ wpabuf_put(msg, WPS_NONCE_LEN);
+ return msg;
+}
+
+
/* subscription_first_event -- send format/queue event that is automatically
* sent on a new subscription.
*/
@@ -665,6 +683,28 @@ static int subscription_first_event(struct subscription *s)
const char *tail = "</e:propertyset>\n";
char txt[10];
+ if (s->sm->wlanevent == NULL) {
+ /*
+ * There has been no events before the subscription. However,
+ * UPnP device architecture specification requires all the
+ * evented variables to be included, so generate a dummy event
+ * for this particular case using a WSC_ACK and all-zeros
+ * nonces. The ER (UPnP control point) will ignore this, but at
+ * least it will learn that WLANEvent variable will be used in
+ * event notifications in the future.
+ */
+ struct wpabuf *msg;
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Use a fake WSC_ACK as the "
+ "initial WLANEvent");
+ msg = build_fake_wsc_ack();
+ if (msg) {
+ s->sm->wlanevent = (char *)
+ base64_encode(wpabuf_head(msg),
+ wpabuf_len(msg), NULL);
+ wpabuf_free(msg);
+ }
+ }
+
wlan_event = s->sm->wlanevent;
if (wlan_event == NULL || *wlan_event == '\0') {
wpa_printf(MSG_DEBUG, "WPS UPnP: WLANEvent not known for "
@@ -694,13 +734,13 @@ static int subscription_first_event(struct subscription *s)
/**
- * subscription_start - Rremember a UPnP control point to send events to.
+ * subscription_start - Remember a UPnP control point to send events to.
* @sm: WPS UPnP state machine from upnp_wps_device_init()
- * @callback_urls: malloc' mem given to the subscription
+ * @callback_urls: Callback URLs
* Returns: %NULL on error, or pointer to new subscription structure.
*/
struct subscription * subscription_start(struct upnp_wps_device_sm *sm,
- char *callback_urls)
+ const char *callback_urls)
{
struct subscription *s;
time_t now = time(NULL);
@@ -740,7 +780,6 @@ struct subscription * subscription_start(struct upnp_wps_device_sm *sm,
}
wpa_printf(MSG_DEBUG, "WPS UPnP: Subscription %p started with %s",
s, callback_urls);
- os_free(callback_urls);
/* Schedule sending this */
event_send_all_later(sm);
return s;
@@ -832,6 +871,50 @@ fail:
}
+#ifdef __FreeBSD__
+#include <sys/sysctl.h>
+#include <net/route.h>
+#include <net/if_dl.h>
+
+static int eth_get(const char *device, u8 ea[ETH_ALEN])
+{
+ struct if_msghdr *ifm;
+ struct sockaddr_dl *sdl;
+ u_char *p, *buf;
+ size_t len;
+ int mib[] = { CTL_NET, AF_ROUTE, 0, AF_LINK, NET_RT_IFLIST, 0 };
+
+ if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0)
+ return -1;
+ if ((buf = os_malloc(len)) == NULL)
+ return -1;
+ if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) {
+ os_free(buf);
+ return -1;
+ }
+ for (p = buf; p < buf + len; p += ifm->ifm_msglen) {
+ ifm = (struct if_msghdr *)p;
+ sdl = (struct sockaddr_dl *)(ifm + 1);
+ if (ifm->ifm_type != RTM_IFINFO ||
+ (ifm->ifm_addrs & RTA_IFP) == 0)
+ continue;
+ if (sdl->sdl_family != AF_LINK || sdl->sdl_nlen == 0 ||
+ os_memcmp(sdl->sdl_data, device, sdl->sdl_nlen) != 0)
+ continue;
+ os_memcpy(ea, LLADDR(sdl), sdl->sdl_alen);
+ break;
+ }
+ os_free(buf);
+
+ if (p >= buf + len) {
+ errno = ESRCH;
+ return -1;
+ }
+ return 0;
+}
+#endif /* __FreeBSD__ */
+
+
/**
* get_netif_info - Get hw and IP addresses for network device
* @net_if: Selected network interface name
@@ -870,6 +953,7 @@ static int get_netif_info(const char *net_if, unsigned *ip_addr,
in_addr.s_addr = *ip_addr;
os_snprintf(*ip_addr_text, 16, "%s", inet_ntoa(in_addr));
+#ifdef __linux__
os_strlcpy(req.ifr_name, net_if, sizeof(req.ifr_name));
if (ioctl(sock, SIOCGIFHWADDR, &req) < 0) {
wpa_printf(MSG_ERROR, "WPS UPnP: SIOCGIFHWADDR failed: "
@@ -877,6 +961,14 @@ static int get_netif_info(const char *net_if, unsigned *ip_addr,
goto fail;
}
os_memcpy(mac, req.ifr_addr.sa_data, 6);
+#elif defined(__FreeBSD__)
+ if (eth_get(net_if, mac) < 0) {
+ wpa_printf(MSG_ERROR, "WPS UPnP: Failed to get MAC address");
+ goto fail;
+ }
+#else
+#error MAC address fetch not implemented
+#endif
os_snprintf(*mac_addr_text, 18, MACSTR, MAC2STR(req.ifr_addr.sa_data));
close(sock);
@@ -914,8 +1006,7 @@ void upnp_wps_device_stop(struct upnp_wps_device_sm *sm)
subscription_destroy(s);
}
- advertisement_state_machine_stop(sm);
- /* TODO: send byebye notifications */
+ advertisement_state_machine_stop(sm, 1);
event_send_stop_all(sm);
os_free(sm->wlanevent);
diff --git a/src/wps/wps_upnp_event.c b/src/wps/wps_upnp_event.c
index f278f35..4122a87 100644
--- a/src/wps/wps_upnp_event.c
+++ b/src/wps/wps_upnp_event.c
@@ -89,7 +89,7 @@ static void event_clean(struct wps_event_ *e)
/* event_delete -- delete single unqueued event
* (be sure to dequeue first if need be)
*/
-void event_delete(struct wps_event_ *e)
+static void event_delete(struct wps_event_ *e)
{
event_clean(e);
wpabuf_free(e->data);
@@ -432,7 +432,7 @@ static int event_send_start(struct subscription *s)
/* event_send_all_later_handler -- actually send events as needed */
-void event_send_all_later_handler(void *eloop_data, void *user_ctx)
+static void event_send_all_later_handler(void *eloop_data, void *user_ctx)
{
struct upnp_wps_device_sm *sm = user_ctx;
struct subscription *s;
diff --git a/src/wps/wps_upnp_i.h b/src/wps/wps_upnp_i.h
index d4b6569..ba4ec20 100644
--- a/src/wps/wps_upnp_i.h
+++ b/src/wps/wps_upnp_i.h
@@ -160,7 +160,7 @@ struct upnp_wps_device_sm {
/* wps_upnp.c */
void format_date(struct wpabuf *buf);
struct subscription * subscription_start(struct upnp_wps_device_sm *sm,
- char *callback_urls);
+ const char *callback_urls);
struct subscription * subscription_renew(struct upnp_wps_device_sm *sm,
const u8 uuid[UUID_LEN]);
void subscription_unlink(struct subscription *s);
@@ -172,7 +172,8 @@ int send_wpabuf(int fd, struct wpabuf *buf);
/* wps_upnp_ssdp.c */
void msearchreply_state_machine_stop(struct advertisement_state_machine *a);
int advertisement_state_machine_start(struct upnp_wps_device_sm *sm);
-void advertisement_state_machine_stop(struct upnp_wps_device_sm *sm);
+void advertisement_state_machine_stop(struct upnp_wps_device_sm *sm,
+ int send_byebye);
void ssdp_listener_stop(struct upnp_wps_device_sm *sm);
int ssdp_listener_start(struct upnp_wps_device_sm *sm);
int add_ssdp_network(char *net_if);
@@ -185,7 +186,6 @@ void web_listener_stop(struct upnp_wps_device_sm *sm);
/* wps_upnp_event.c */
int event_add(struct subscription *s, const struct wpabuf *data);
-void event_delete(struct wps_event_ *e);
void event_delete_all(struct subscription *s);
void event_send_all_later(struct upnp_wps_device_sm *sm);
void event_send_stop_all(struct upnp_wps_device_sm *sm);
diff --git a/src/wps/wps_upnp_ssdp.c b/src/wps/wps_upnp_ssdp.c
index 6a94c59..c1dc99d 100644
--- a/src/wps/wps_upnp_ssdp.c
+++ b/src/wps/wps_upnp_ssdp.c
@@ -229,10 +229,41 @@ static void advertisement_state_machine_handler(void *eloop_data,
/**
* advertisement_state_machine_stop - Stop SSDP advertisements
* @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * @send_byebye: Send byebye advertisement messages immediately
*/
-void advertisement_state_machine_stop(struct upnp_wps_device_sm *sm)
+void advertisement_state_machine_stop(struct upnp_wps_device_sm *sm,
+ int send_byebye)
{
+ struct advertisement_state_machine *a = &sm->advertisement;
+ int islast = 0;
+ struct wpabuf *msg;
+ struct sockaddr_in dest;
+
eloop_cancel_timeout(advertisement_state_machine_handler, NULL, sm);
+ if (!send_byebye || sm->multicast_sd < 0)
+ return;
+
+ a->type = ADVERTISE_DOWN;
+ a->state = 0;
+ a->sm = sm;
+
+ os_memset(&dest, 0, sizeof(dest));
+ dest.sin_family = AF_INET;
+ dest.sin_addr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS);
+ dest.sin_port = htons(UPNP_MULTICAST_PORT);
+
+ while (!islast) {
+ msg = next_advertisement(a, &islast);
+ if (msg == NULL)
+ break;
+ if (sendto(sm->multicast_sd, wpabuf_head(msg), wpabuf_len(msg),
+ 0, (struct sockaddr *) &dest, sizeof(dest)) < 0) {
+ wpa_printf(MSG_INFO, "WPS UPnP: Advertisement sendto "
+ "failed: %d (%s)", errno, strerror(errno));
+ }
+ wpabuf_free(msg);
+ a->state++;
+ }
}
@@ -318,7 +349,7 @@ int advertisement_state_machine_start(struct upnp_wps_device_sm *sm)
struct advertisement_state_machine *a = &sm->advertisement;
int next_timeout_msec;
- advertisement_state_machine_stop(sm);
+ advertisement_state_machine_stop(sm, 0);
/*
* Start out advertising down, this automatically switches
@@ -461,7 +492,7 @@ static void msearchreply_state_machine_start(struct upnp_wps_device_sm *sm,
a->type = MSEARCH_REPLY;
a->state = 0;
a->sm = sm;
- os_memcpy(&a->client, client, sizeof(client));
+ os_memcpy(&a->client, client, sizeof(*client));
/* Wait time depending on MX value */
next_timeout_msec = (1000 * mx * (os_random() & 0xFF)) >> 8;
next_timeout_sec = next_timeout_msec / 1000;
@@ -784,6 +815,7 @@ fail:
*/
int add_ssdp_network(char *net_if)
{
+#ifdef __linux__
int ret = -1;
int sock = -1;
struct rtentry rt;
@@ -798,11 +830,11 @@ int add_ssdp_network(char *net_if)
goto fail;
rt.rt_dev = net_if;
- sin = (struct sockaddr_in *) &rt.rt_dst;
+ sin = aliasing_hide_typecast(&rt.rt_dst, struct sockaddr_in);
sin->sin_family = AF_INET;
sin->sin_port = 0;
sin->sin_addr.s_addr = inet_addr(SSDP_TARGET);
- sin = (struct sockaddr_in *) &rt.rt_genmask;
+ sin = aliasing_hide_typecast(&rt.rt_genmask, struct sockaddr_in);
sin->sin_family = AF_INET;
sin->sin_port = 0;
sin->sin_addr.s_addr = inet_addr(SSDP_NETMASK);
@@ -826,6 +858,9 @@ fail:
close(sock);
return ret;
+#else /* __linux__ */
+ return 0;
+#endif /* __linux__ */
}
diff --git a/src/wps/wps_upnp_web.c b/src/wps/wps_upnp_web.c
index db47c29..b637454 100644
--- a/src/wps/wps_upnp_web.c
+++ b/src/wps/wps_upnp_web.c
@@ -1578,7 +1578,6 @@ static void web_connection_parse_subscribe(struct web_connection *c,
ret = HTTP_INTERNAL_SERVER_ERROR;
goto error;
}
- callback_urls = NULL; /* is now owned by subscription */
} else {
ret = HTTP_PRECONDITION_FAILED;
goto error;
@@ -1630,6 +1629,7 @@ error:
http_put_empty(buf, ret);
send_wpabuf(c->sd, buf);
wpabuf_free(buf);
+ os_free(callback_urls);
}
OpenPOWER on IntegriCloud