fragattacks/src/wps/wps_er.c
Jouni Malinen f0f82bd82f WPS ER: Clean up WPS session on PutMessage error cases
This is needed to allow new operation to be started after an error
without having to wait for the AP entry to time out.

Signed-off-by: Jouni Malinen <j@w1.fi>
2015-09-07 17:45:53 +03:00

2109 lines
51 KiB
C

/*
* Wi-Fi Protected Setup - External Registrar
* Copyright (c) 2009-2013, Jouni Malinen <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#include "includes.h"
#include "common.h"
#include "base64.h"
#include "uuid.h"
#include "eloop.h"
#include "httpread.h"
#include "http_client.h"
#include "http_server.h"
#include "upnp_xml.h"
#include "wps_i.h"
#include "wps_upnp.h"
#include "wps_upnp_i.h"
#include "wps_er.h"
static void wps_er_deinit_finish(void *eloop_data, void *user_ctx);
static void wps_er_ap_timeout(void *eloop_data, void *user_ctx);
static void wps_er_sta_timeout(void *eloop_data, void *user_ctx);
static void wps_er_ap_process(struct wps_er_ap *ap, struct wpabuf *msg);
static int wps_er_send_get_device_info(struct wps_er_ap *ap,
void (*m1_handler)(struct wps_er_ap *ap,
struct wpabuf *m1));
static void wps_er_sta_event(struct wps_context *wps, struct wps_er_sta *sta,
enum wps_event event)
{
union wps_event_data data;
struct wps_event_er_enrollee *ev = &data.enrollee;
if (wps->event_cb == NULL)
return;
os_memset(&data, 0, sizeof(data));
ev->uuid = sta->uuid;
ev->mac_addr = sta->addr;
ev->m1_received = sta->m1_received;
ev->config_methods = sta->config_methods;
ev->dev_passwd_id = sta->dev_passwd_id;
ev->pri_dev_type = sta->pri_dev_type;
ev->dev_name = sta->dev_name;
ev->manufacturer = sta->manufacturer;
ev->model_name = sta->model_name;
ev->model_number = sta->model_number;
ev->serial_number = sta->serial_number;
wps->event_cb(wps->cb_ctx, event, &data);
}
static struct wps_er_sta * wps_er_sta_get(struct wps_er_ap *ap, const u8 *addr,
const u8 *uuid)
{
struct wps_er_sta *sta;
dl_list_for_each(sta, &ap->sta, struct wps_er_sta, list) {
if ((addr == NULL ||
os_memcmp(sta->addr, addr, ETH_ALEN) == 0) &&
(uuid == NULL ||
os_memcmp(uuid, sta->uuid, WPS_UUID_LEN) == 0))
return sta;
}
return NULL;
}
static void wps_er_sta_free(struct wps_er_sta *sta)
{
wps_er_sta_event(sta->ap->er->wps, sta, WPS_EV_ER_ENROLLEE_REMOVE);
if (sta->wps)
wps_deinit(sta->wps);
os_free(sta->manufacturer);
os_free(sta->model_name);
os_free(sta->model_number);
os_free(sta->serial_number);
os_free(sta->dev_name);
http_client_free(sta->http);
eloop_cancel_timeout(wps_er_sta_timeout, sta, NULL);
os_free(sta->cred);
os_free(sta);
}
static void wps_er_sta_remove_all(struct wps_er_ap *ap)
{
struct wps_er_sta *prev, *sta;
dl_list_for_each_safe(sta, prev, &ap->sta, struct wps_er_sta, list)
wps_er_sta_free(sta);
}
static struct wps_er_ap * wps_er_ap_get(struct wps_er *er,
struct in_addr *addr, const u8 *uuid,
const u8 *mac_addr)
{
struct wps_er_ap *ap;
dl_list_for_each(ap, &er->ap, struct wps_er_ap, list) {
if ((addr == NULL || ap->addr.s_addr == addr->s_addr) &&
(uuid == NULL ||
os_memcmp(uuid, ap->uuid, WPS_UUID_LEN) == 0) &&
(mac_addr == NULL ||
os_memcmp(mac_addr, ap->mac_addr, ETH_ALEN) == 0))
return ap;
}
return NULL;
}
static struct wps_er_ap * wps_er_ap_get_id(struct wps_er *er, unsigned int id)
{
struct wps_er_ap *ap;
dl_list_for_each(ap, &er->ap, struct wps_er_ap, list) {
if (ap->id == id)
return ap;
}
return NULL;
}
static void wps_er_ap_event(struct wps_context *wps, struct wps_er_ap *ap,
enum wps_event event)
{
union wps_event_data data;
struct wps_event_er_ap *evap = &data.ap;
if (wps->event_cb == NULL)
return;
os_memset(&data, 0, sizeof(data));
evap->uuid = ap->uuid;
evap->friendly_name = ap->friendly_name;
evap->manufacturer = ap->manufacturer;
evap->manufacturer_url = ap->manufacturer_url;
evap->model_description = ap->model_description;
evap->model_name = ap->model_name;
evap->model_number = ap->model_number;
evap->model_url = ap->model_url;
evap->serial_number = ap->serial_number;
evap->upc = ap->upc;
evap->pri_dev_type = ap->pri_dev_type;
evap->wps_state = ap->wps_state;
evap->mac_addr = ap->mac_addr;
wps->event_cb(wps->cb_ctx, event, &data);
}
static void wps_er_ap_free(struct wps_er_ap *ap)
{
http_client_free(ap->http);
ap->http = NULL;
os_free(ap->location);
os_free(ap->friendly_name);
os_free(ap->manufacturer);
os_free(ap->manufacturer_url);
os_free(ap->model_description);
os_free(ap->model_name);
os_free(ap->model_number);
os_free(ap->model_url);
os_free(ap->serial_number);
os_free(ap->udn);
os_free(ap->upc);
os_free(ap->scpd_url);
os_free(ap->control_url);
os_free(ap->event_sub_url);
os_free(ap->ap_settings);
os_free(ap);
}
static void wps_er_ap_unsubscribed(struct wps_er *er, struct wps_er_ap *ap)
{
wpa_printf(MSG_DEBUG, "WPS ER: Unsubscribed from AP %s (%s)",
inet_ntoa(ap->addr), ap->location);
dl_list_del(&ap->list);
wps_er_ap_free(ap);
if (er->deinitializing && dl_list_empty(&er->ap_unsubscribing))
wps_er_deinit_finish(er, NULL);
}
static void wps_er_http_unsubscribe_cb(void *ctx, struct http_client *c,
enum http_client_event event)
{
struct wps_er_ap *ap = ctx;
switch (event) {
case HTTP_CLIENT_OK:
wpa_printf(MSG_DEBUG, "WPS ER: Unsubscribed from events");
ap->subscribed = 0;
break;
case HTTP_CLIENT_FAILED:
case HTTP_CLIENT_INVALID_REPLY:
case HTTP_CLIENT_TIMEOUT:
wpa_printf(MSG_DEBUG, "WPS ER: Failed to unsubscribe from "
"events");
break;
}
http_client_free(ap->http);
ap->http = NULL;
/*
* Need to get rid of the AP entry regardless of whether we managed to
* unsubscribe cleanly or not.
*/
wps_er_ap_unsubscribed(ap->er, ap);
}
static void wps_er_ap_unsubscribe(struct wps_er *er, struct wps_er_ap *ap)
{
struct wpabuf *req;
struct sockaddr_in dst;
char *url, *path;
char sid[100];
if (ap->event_sub_url == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: No eventSubURL - cannot "
"subscribe");
goto fail;
}
if (ap->http) {
wpa_printf(MSG_DEBUG, "WPS ER: Pending HTTP request - cannot "
"send subscribe request");
goto fail;
}
url = http_client_url_parse(ap->event_sub_url, &dst, &path);
if (url == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: Failed to parse eventSubURL");
goto fail;
}
req = wpabuf_alloc(os_strlen(ap->event_sub_url) + 1000);
if (req == NULL) {
os_free(url);
goto fail;
}
uuid_bin2str(ap->sid, sid, sizeof(sid));
wpabuf_printf(req,
"UNSUBSCRIBE %s HTTP/1.1\r\n"
"HOST: %s:%d\r\n"
"SID: uuid:%s\r\n"
"\r\n",
path, inet_ntoa(dst.sin_addr), ntohs(dst.sin_port), sid);
os_free(url);
wpa_hexdump_ascii(MSG_MSGDUMP, "WPS ER: Unsubscription request",
wpabuf_head(req), wpabuf_len(req));
ap->http = http_client_addr(&dst, req, 1000,
wps_er_http_unsubscribe_cb, ap);
if (ap->http == NULL) {
wpabuf_free(req);
goto fail;
}
return;
fail:
/*
* Need to get rid of the AP entry even when we fail to unsubscribe
* cleanly.
*/
wps_er_ap_unsubscribed(ap->er, ap);
}
static struct wps_er_ap_settings * wps_er_ap_get_settings(struct wps_er *er,
const u8 *uuid)
{
struct wps_er_ap_settings *s;
dl_list_for_each(s, &er->ap_settings, struct wps_er_ap_settings, list)
if (os_memcmp(uuid, s->uuid, WPS_UUID_LEN) == 0)
return s;
return NULL;
}
int wps_er_ap_cache_settings(struct wps_er *er, struct in_addr *addr)
{
struct wps_er_ap *ap;
struct wps_er_ap_settings *settings;
ap = wps_er_ap_get(er, addr, NULL, NULL);
if (ap == NULL || ap->ap_settings == NULL)
return -1;
settings = wps_er_ap_get_settings(er, ap->uuid);
if (!settings) {
settings = os_zalloc(sizeof(*settings));
if (settings == NULL)
return -1;
os_memcpy(settings->uuid, ap->uuid, WPS_UUID_LEN);
dl_list_add(&er->ap_settings, &settings->list);
}
os_memcpy(&settings->ap_settings, ap->ap_settings,
sizeof(struct wps_credential));
return 0;
}
static int wps_er_ap_use_cached_settings(struct wps_er *er,
struct wps_er_ap *ap)
{
struct wps_er_ap_settings *s;
if (ap->ap_settings)
return 0;
s = wps_er_ap_get_settings(ap->er, ap->uuid);
if (!s)
return -1;
ap->ap_settings = os_malloc(sizeof(*ap->ap_settings));
if (ap->ap_settings == NULL)
return -1;
os_memcpy(ap->ap_settings, &s->ap_settings, sizeof(*ap->ap_settings));
wpa_printf(MSG_DEBUG, "WPS ER: Use cached AP settings");
return 0;
}
static void wps_er_ap_remove_entry(struct wps_er *er, struct wps_er_ap *ap)
{
wpa_printf(MSG_DEBUG, "WPS ER: Removing AP entry for %s (%s)",
inet_ntoa(ap->addr), ap->location);
eloop_cancel_timeout(wps_er_ap_timeout, er, ap);
wps_er_sta_remove_all(ap);
wps_er_ap_event(er->wps, ap, WPS_EV_ER_AP_REMOVE);
http_client_free(ap->http);
ap->http = NULL;
if (ap->wps) {
wps_deinit(ap->wps);
ap->wps = NULL;
}
dl_list_del(&ap->list);
if (ap->subscribed) {
dl_list_add(&er->ap_unsubscribing, &ap->list);
wps_er_ap_unsubscribe(er, ap);
} else
wps_er_ap_free(ap);
}
static void wps_er_ap_timeout(void *eloop_data, void *user_ctx)
{
struct wps_er *er = eloop_data;
struct wps_er_ap *ap = user_ctx;
wpa_printf(MSG_DEBUG, "WPS ER: AP advertisement timed out");
wps_er_ap_remove_entry(er, ap);
}
static int wps_er_get_sid(struct wps_er_ap *ap, char *sid)
{
char *pos;
char txt[100];
if (!sid) {
wpa_printf(MSG_DEBUG, "WPS ER: No SID received from %s (%s)",
inet_ntoa(ap->addr), ap->location);
return -1;
}
pos = os_strstr(sid, "uuid:");
if (!pos) {
wpa_printf(MSG_DEBUG, "WPS ER: Invalid SID received from "
"%s (%s): '%s'", inet_ntoa(ap->addr), ap->location,
sid);
return -1;
}
pos += 5;
if (uuid_str2bin(pos, ap->sid) < 0) {
wpa_printf(MSG_DEBUG, "WPS ER: Invalid SID received from "
"%s (%s): '%s'", inet_ntoa(ap->addr), ap->location,
sid);
return -1;
}
uuid_bin2str(ap->sid, txt, sizeof(txt));
wpa_printf(MSG_DEBUG, "WPS ER: SID for subscription with %s (%s): %s",
inet_ntoa(ap->addr), ap->location, txt);
return 0;
}
static void wps_er_http_subscribe_cb(void *ctx, struct http_client *c,
enum http_client_event event)
{
struct wps_er_ap *ap = ctx;
switch (event) {
case HTTP_CLIENT_OK:
wpa_printf(MSG_DEBUG, "WPS ER: Subscribed to events");
ap->subscribed = 1;
wps_er_get_sid(ap, http_client_get_hdr_line(c, "SID"));
wps_er_ap_use_cached_settings(ap->er, ap);
wps_er_ap_event(ap->er->wps, ap, WPS_EV_ER_AP_ADD);
break;
case HTTP_CLIENT_FAILED:
case HTTP_CLIENT_INVALID_REPLY:
case HTTP_CLIENT_TIMEOUT:
wpa_printf(MSG_DEBUG, "WPS ER: Failed to subscribe to events");
break;
}
http_client_free(ap->http);
ap->http = NULL;
}
static void wps_er_subscribe(struct wps_er_ap *ap)
{
struct wpabuf *req;
struct sockaddr_in dst;
char *url, *path;
if (ap->event_sub_url == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: No eventSubURL - cannot "
"subscribe");
return;
}
if (ap->http) {
wpa_printf(MSG_DEBUG, "WPS ER: Pending HTTP request - cannot "
"send subscribe request");
return;
}
url = http_client_url_parse(ap->event_sub_url, &dst, &path);
if (url == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: Failed to parse eventSubURL");
return;
}
req = wpabuf_alloc(os_strlen(ap->event_sub_url) + 1000);
if (req == NULL) {
os_free(url);
return;
}
wpabuf_printf(req,
"SUBSCRIBE %s HTTP/1.1\r\n"
"HOST: %s:%d\r\n"
"CALLBACK: <http://%s:%d/event/%u/%u>\r\n"
"NT: upnp:event\r\n"
"TIMEOUT: Second-%d\r\n"
"\r\n",
path, inet_ntoa(dst.sin_addr), ntohs(dst.sin_port),
ap->er->ip_addr_text, ap->er->http_port,
ap->er->event_id, ap->id, 1800);
os_free(url);
wpa_hexdump_ascii(MSG_MSGDUMP, "WPS ER: Subscription request",
wpabuf_head(req), wpabuf_len(req));
ap->http = http_client_addr(&dst, req, 1000, wps_er_http_subscribe_cb,
ap);
if (ap->http == NULL)
wpabuf_free(req);
}
static void wps_er_ap_get_m1(struct wps_er_ap *ap, struct wpabuf *m1)
{
struct wps_parse_attr attr;
if (wps_parse_msg(m1, &attr) < 0) {
wpa_printf(MSG_DEBUG, "WPS ER: Failed to parse M1");
return;
}
if (attr.primary_dev_type)
os_memcpy(ap->pri_dev_type, attr.primary_dev_type, 8);
if (attr.wps_state)
ap->wps_state = *attr.wps_state;
if (attr.mac_addr)
os_memcpy(ap->mac_addr, attr.mac_addr, ETH_ALEN);
wps_er_subscribe(ap);
}
static void wps_er_get_device_info(struct wps_er_ap *ap)
{
wps_er_send_get_device_info(ap, wps_er_ap_get_m1);
}
static const char * wps_er_find_wfadevice(const char *data)
{
const char *tag, *tagname, *end;
char *val;
int found = 0;
while (!found) {
/* Find next <device> */
for (;;) {
if (xml_next_tag(data, &tag, &tagname, &end))
return NULL;
data = end;
if (!os_strncasecmp(tagname, "device", 6) &&
*tag != '/' &&
(tagname[6] == '>' || !isgraph(tagname[6]))) {
break;
}
}
/* Check whether deviceType is WFADevice */
val = xml_get_first_item(data, "deviceType");
if (val == NULL)
return NULL;
wpa_printf(MSG_DEBUG, "WPS ER: Found deviceType '%s'", val);
found = os_strcasecmp(val, "urn:schemas-wifialliance-org:"
"device:WFADevice:1") == 0;
os_free(val);
}
return data;
}
static void wps_er_parse_device_description(struct wps_er_ap *ap,
struct wpabuf *reply)
{
/* Note: reply includes null termination after the buffer data */
const char *tmp, *data = wpabuf_head(reply);
char *pos;
wpa_hexdump_ascii(MSG_MSGDUMP, "WPS ER: Device info",
wpabuf_head(reply), wpabuf_len(reply));
/*
* The root device description may include multiple devices, so first
* find the beginning of the WFADevice description to allow the
* simplistic parser to pick the correct entries.
*/
tmp = wps_er_find_wfadevice(data);
if (tmp == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: WFADevice:1 device not found - "
"trying to parse invalid data");
} else
data = tmp;
ap->friendly_name = xml_get_first_item(data, "friendlyName");
wpa_printf(MSG_DEBUG, "WPS ER: friendlyName='%s'", ap->friendly_name);
ap->manufacturer = xml_get_first_item(data, "manufacturer");
wpa_printf(MSG_DEBUG, "WPS ER: manufacturer='%s'", ap->manufacturer);
ap->manufacturer_url = xml_get_first_item(data, "manufacturerURL");
wpa_printf(MSG_DEBUG, "WPS ER: manufacturerURL='%s'",
ap->manufacturer_url);
ap->model_description = xml_get_first_item(data, "modelDescription");
wpa_printf(MSG_DEBUG, "WPS ER: modelDescription='%s'",
ap->model_description);
ap->model_name = xml_get_first_item(data, "modelName");
wpa_printf(MSG_DEBUG, "WPS ER: modelName='%s'", ap->model_name);
ap->model_number = xml_get_first_item(data, "modelNumber");
wpa_printf(MSG_DEBUG, "WPS ER: modelNumber='%s'", ap->model_number);
ap->model_url = xml_get_first_item(data, "modelURL");
wpa_printf(MSG_DEBUG, "WPS ER: modelURL='%s'", ap->model_url);
ap->serial_number = xml_get_first_item(data, "serialNumber");
wpa_printf(MSG_DEBUG, "WPS ER: serialNumber='%s'", ap->serial_number);
ap->udn = xml_get_first_item(data, "UDN");
if (ap->udn) {
wpa_printf(MSG_DEBUG, "WPS ER: UDN='%s'", ap->udn);
pos = os_strstr(ap->udn, "uuid:");
if (pos) {
pos += 5;
if (uuid_str2bin(pos, ap->uuid) < 0)
wpa_printf(MSG_DEBUG,
"WPS ER: Invalid UUID in UDN");
}
}
ap->upc = xml_get_first_item(data, "UPC");
wpa_printf(MSG_DEBUG, "WPS ER: UPC='%s'", ap->upc);
ap->scpd_url = http_link_update(
xml_get_first_item(data, "SCPDURL"), ap->location);
wpa_printf(MSG_DEBUG, "WPS ER: SCPDURL='%s'", ap->scpd_url);
ap->control_url = http_link_update(
xml_get_first_item(data, "controlURL"), ap->location);
wpa_printf(MSG_DEBUG, "WPS ER: controlURL='%s'", ap->control_url);
ap->event_sub_url = http_link_update(
xml_get_first_item(data, "eventSubURL"), ap->location);
wpa_printf(MSG_DEBUG, "WPS ER: eventSubURL='%s'", ap->event_sub_url);
}
static void wps_er_http_dev_desc_cb(void *ctx, struct http_client *c,
enum http_client_event event)
{
struct wps_er_ap *ap = ctx;
struct wpabuf *reply;
int ok = 0;
switch (event) {
case HTTP_CLIENT_OK:
reply = http_client_get_body(c);
if (reply == NULL)
break;
wps_er_parse_device_description(ap, reply);
ok = 1;
break;
case HTTP_CLIENT_FAILED:
case HTTP_CLIENT_INVALID_REPLY:
case HTTP_CLIENT_TIMEOUT:
wpa_printf(MSG_DEBUG, "WPS ER: Failed to fetch device info");
break;
}
http_client_free(ap->http);
ap->http = NULL;
if (ok)
wps_er_get_device_info(ap);
}
void wps_er_ap_add(struct wps_er *er, const u8 *uuid, struct in_addr *addr,
const char *location, int max_age)
{
struct wps_er_ap *ap;
ap = wps_er_ap_get(er, addr, uuid, NULL);
if (ap) {
/* Update advertisement timeout */
eloop_cancel_timeout(wps_er_ap_timeout, er, ap);
eloop_register_timeout(max_age, 0, wps_er_ap_timeout, er, ap);
return;
}
ap = os_zalloc(sizeof(*ap));
if (ap == NULL)
return;
dl_list_init(&ap->sta);
ap->er = er;
ap->id = ++er->next_ap_id;
ap->location = os_strdup(location);
if (ap->location == NULL) {
os_free(ap);
return;
}
dl_list_add(&er->ap, &ap->list);
ap->addr.s_addr = addr->s_addr;
os_memcpy(ap->uuid, uuid, WPS_UUID_LEN);
eloop_register_timeout(max_age, 0, wps_er_ap_timeout, er, ap);
wpa_printf(MSG_DEBUG, "WPS ER: Added AP entry for %s (%s)",
inet_ntoa(ap->addr), ap->location);
/* Fetch device description */
ap->http = http_client_url(ap->location, NULL, 10000,
wps_er_http_dev_desc_cb, ap);
}
void wps_er_ap_remove(struct wps_er *er, struct in_addr *addr)
{
struct wps_er_ap *ap;
dl_list_for_each(ap, &er->ap, struct wps_er_ap, list) {
if (ap->addr.s_addr == addr->s_addr) {
wps_er_ap_remove_entry(er, ap);
return;
}
}
}
static void wps_er_ap_remove_all(struct wps_er *er)
{
struct wps_er_ap *prev, *ap;
struct wps_er_ap_settings *prev_s, *s;
dl_list_for_each_safe(ap, prev, &er->ap, struct wps_er_ap, list)
wps_er_ap_remove_entry(er, ap);
dl_list_for_each_safe(s, prev_s, &er->ap_settings,
struct wps_er_ap_settings, list)
os_free(s);
}
static void http_put_date(struct wpabuf *buf)
{
wpabuf_put_str(buf, "Date: ");
format_date(buf);
wpabuf_put_str(buf, "\r\n");
}
static void wps_er_http_resp_not_found(struct http_request *req)
{
struct wpabuf *buf;
buf = wpabuf_alloc(200);
if (buf == NULL) {
http_request_deinit(req);
return;
}
wpabuf_put_str(buf,
"HTTP/1.1 404 Not Found\r\n"
"Server: unspecified, UPnP/1.0, unspecified\r\n"
"Connection: close\r\n");
http_put_date(buf);
wpabuf_put_str(buf, "\r\n");
http_request_send_and_deinit(req, buf);
}
static void wps_er_http_resp_ok(struct http_request *req)
{
struct wpabuf *buf;
buf = wpabuf_alloc(200);
if (buf == NULL) {
http_request_deinit(req);
return;
}
wpabuf_put_str(buf,
"HTTP/1.1 200 OK\r\n"
"Server: unspecified, UPnP/1.0, unspecified\r\n"
"Connection: close\r\n"
"Content-Length: 0\r\n");
http_put_date(buf);
wpabuf_put_str(buf, "\r\n");
http_request_send_and_deinit(req, buf);
}
static void wps_er_sta_timeout(void *eloop_data, void *user_ctx)
{
struct wps_er_sta *sta = eloop_data;
wpa_printf(MSG_DEBUG, "WPS ER: STA entry timed out");
dl_list_del(&sta->list);
wps_er_sta_free(sta);
}
static struct wps_er_sta * wps_er_add_sta_data(struct wps_er_ap *ap,
const u8 *addr,
struct wps_parse_attr *attr,
int probe_req)
{
struct wps_er_sta *sta = wps_er_sta_get(ap, addr, NULL);
int new_sta = 0;
int m1;
m1 = !probe_req && attr->msg_type && *attr->msg_type == WPS_M1;
if (sta == NULL) {
/*
* Only allow new STA entry to be added based on Probe Request
* or M1. This will filter out bogus events and anything that
* may have been ongoing at the time ER subscribed for events.
*/
if (!probe_req && !m1)
return NULL;
sta = os_zalloc(sizeof(*sta));
if (sta == NULL)
return NULL;
os_memcpy(sta->addr, addr, ETH_ALEN);
sta->ap = ap;
dl_list_add(&ap->sta, &sta->list);
new_sta = 1;
}
if (m1)
sta->m1_received = 1;
if (attr->config_methods && (!probe_req || !sta->m1_received))
sta->config_methods = WPA_GET_BE16(attr->config_methods);
if (attr->uuid_e && (!probe_req || !sta->m1_received))
os_memcpy(sta->uuid, attr->uuid_e, WPS_UUID_LEN);
if (attr->primary_dev_type && (!probe_req || !sta->m1_received))
os_memcpy(sta->pri_dev_type, attr->primary_dev_type, 8);
if (attr->dev_password_id && (!probe_req || !sta->m1_received))
sta->dev_passwd_id = WPA_GET_BE16(attr->dev_password_id);
if (attr->manufacturer) {
os_free(sta->manufacturer);
sta->manufacturer = dup_binstr(attr->manufacturer,
attr->manufacturer_len);
}
if (attr->model_name) {
os_free(sta->model_name);
sta->model_name = dup_binstr(attr->model_name,
attr->model_name_len);
}
if (attr->model_number) {
os_free(sta->model_number);
sta->model_number = dup_binstr(attr->model_number,
attr->model_number_len);
}
if (attr->serial_number) {
os_free(sta->serial_number);
sta->serial_number = dup_binstr(attr->serial_number,
attr->serial_number_len);
}
if (attr->dev_name) {
os_free(sta->dev_name);
sta->dev_name = dup_binstr(attr->dev_name, attr->dev_name_len);
}
eloop_cancel_timeout(wps_er_sta_timeout, sta, NULL);
eloop_register_timeout(300, 0, wps_er_sta_timeout, sta, NULL);
if (m1 || new_sta)
wps_er_sta_event(ap->er->wps, sta, WPS_EV_ER_ENROLLEE_ADD);
return sta;
}
static void wps_er_process_wlanevent_probe_req(struct wps_er_ap *ap,
const u8 *addr,
struct wpabuf *msg)
{
struct wps_parse_attr attr;
wpa_printf(MSG_DEBUG, "WPS ER: WLANEvent - Probe Request - from "
MACSTR, MAC2STR(addr));
wpa_hexdump_buf(MSG_MSGDUMP, "WPS ER: WLANEvent - Enrollee's message "
"(TLVs from Probe Request)", msg);
if (wps_validate_probe_req(msg, addr) < 0) {
wpa_printf(MSG_INFO, "WPS-STRICT: ER: Ignore invalid proxied "
"Probe Request frame from " MACSTR, MAC2STR(addr));
return;
}
if (wps_parse_msg(msg, &attr) < 0) {
wpa_printf(MSG_DEBUG, "WPS ER: Failed to parse TLVs in "
"WLANEvent message");
return;
}
wps_er_add_sta_data(ap, addr, &attr, 1);
wps_registrar_probe_req_rx(ap->er->wps->registrar, addr, msg, 0);
}
static void wps_er_http_put_wlan_response_cb(void *ctx, struct http_client *c,
enum http_client_event event)
{
struct wps_er_sta *sta = ctx;
switch (event) {
case HTTP_CLIENT_OK:
wpa_printf(MSG_DEBUG, "WPS ER: PutWLANResponse OK");
break;
case HTTP_CLIENT_FAILED:
case HTTP_CLIENT_INVALID_REPLY:
case HTTP_CLIENT_TIMEOUT:
wpa_printf(MSG_DEBUG, "WPS ER: PutWLANResponse failed");
break;
}
http_client_free(sta->http);
sta->http = NULL;
}
static const char *soap_prefix =
"<?xml version=\"1.0\"?>\n"
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
"s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n"
"<s:Body>\n";
static const char *soap_postfix =
"</s:Body>\n</s:Envelope>\n";
static const char *urn_wfawlanconfig =
"urn:schemas-wifialliance-org:service:WFAWLANConfig:1";
static struct wpabuf * wps_er_soap_hdr(const struct wpabuf *msg,
const char *name, const char *arg_name,
const char *path,
const struct sockaddr_in *dst,
char **len_ptr, char **body_ptr)
{
unsigned char *encoded;
size_t encoded_len;
struct wpabuf *buf;
if (msg) {
encoded = base64_encode(wpabuf_head(msg), wpabuf_len(msg),
&encoded_len);
if (encoded == NULL)
return NULL;
} else {
encoded = NULL;
encoded_len = 0;
}
buf = wpabuf_alloc(1000 + encoded_len);
if (buf == NULL) {
os_free(encoded);
return NULL;
}
wpabuf_printf(buf,
"POST %s HTTP/1.1\r\n"
"Host: %s:%d\r\n"
"Content-Type: text/xml; charset=\"utf-8\"\r\n"
"Content-Length: ",
path, inet_ntoa(dst->sin_addr), ntohs(dst->sin_port));
*len_ptr = wpabuf_put(buf, 0);
wpabuf_printf(buf,
" \r\n"
"SOAPACTION: \"%s#%s\"\r\n"
"\r\n",
urn_wfawlanconfig, name);
*body_ptr = wpabuf_put(buf, 0);
wpabuf_put_str(buf, soap_prefix);
wpabuf_printf(buf, "<u:%s xmlns:u=\"", name);
wpabuf_put_str(buf, urn_wfawlanconfig);
wpabuf_put_str(buf, "\">\n");
if (encoded) {
wpabuf_printf(buf, "<%s>%s</%s>\n",
arg_name, (char *) encoded, arg_name);
os_free(encoded);
}
return buf;
}
static void wps_er_soap_end(struct wpabuf *buf, const char *name,
char *len_ptr, char *body_ptr)
{
char len_buf[10];
wpabuf_printf(buf, "</u:%s>\n", name);
wpabuf_put_str(buf, soap_postfix);
os_snprintf(len_buf, sizeof(len_buf), "%d",
(int) ((char *) wpabuf_put(buf, 0) - body_ptr));
os_memcpy(len_ptr, len_buf, os_strlen(len_buf));
}
static void wps_er_sta_send_msg(struct wps_er_sta *sta, struct wpabuf *msg)
{
struct wpabuf *buf;
char *len_ptr, *body_ptr;
struct sockaddr_in dst;
char *url, *path;
if (sta->http) {
wpa_printf(MSG_DEBUG, "WPS ER: Pending HTTP request for STA - "
"ignore new request");
wpabuf_free(msg);
return;
}
if (sta->ap->control_url == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: No controlURL for AP");
wpabuf_free(msg);
return;
}
url = http_client_url_parse(sta->ap->control_url, &dst, &path);
if (url == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: Failed to parse controlURL");
wpabuf_free(msg);
return;
}
buf = wps_er_soap_hdr(msg, "PutWLANResponse", "NewMessage", path, &dst,
&len_ptr, &body_ptr);
wpabuf_free(msg);
os_free(url);
if (buf == NULL)
return;
wpabuf_printf(buf, "<NewWLANEventType>%d</NewWLANEventType>\n",
UPNP_WPS_WLANEVENT_TYPE_EAP);
wpabuf_printf(buf, "<NewWLANEventMAC>" MACSTR "</NewWLANEventMAC>\n",
MAC2STR(sta->addr));
wps_er_soap_end(buf, "PutWLANResponse", len_ptr, body_ptr);
sta->http = http_client_addr(&dst, buf, 1000,
wps_er_http_put_wlan_response_cb, sta);
if (sta->http == NULL)
wpabuf_free(buf);
}
static void wps_er_sta_process(struct wps_er_sta *sta, struct wpabuf *msg,
enum wsc_op_code op_code)
{
enum wps_process_res res;
res = wps_process_msg(sta->wps, op_code, msg);
if (res == WPS_CONTINUE) {
struct wpabuf *next = wps_get_msg(sta->wps, &op_code);
if (next)
wps_er_sta_send_msg(sta, next);
} else {
wpa_printf(MSG_DEBUG, "WPS ER: Protocol run %s with the "
"enrollee (res=%d)",
res == WPS_DONE ? "succeeded" : "failed", res);
wps_deinit(sta->wps);
sta->wps = NULL;
if (res == WPS_DONE) {
/* Remove the STA entry after short timeout */
eloop_cancel_timeout(wps_er_sta_timeout, sta, NULL);
eloop_register_timeout(10, 0, wps_er_sta_timeout, sta,
NULL);
}
}
}
static void wps_er_sta_start(struct wps_er_sta *sta, struct wpabuf *msg)
{
struct wps_config cfg;
if (sta->wps)
wps_deinit(sta->wps);
os_memset(&cfg, 0, sizeof(cfg));
cfg.wps = sta->ap->er->wps;
cfg.registrar = 1;
cfg.peer_addr = sta->addr;
sta->wps = wps_init(&cfg);
if (sta->wps == NULL)
return;
sta->wps->er = 1;
sta->wps->use_cred = sta->ap->ap_settings;
if (sta->ap->ap_settings) {
os_free(sta->cred);
sta->cred = os_malloc(sizeof(*sta->cred));
if (sta->cred) {
os_memcpy(sta->cred, sta->ap->ap_settings,
sizeof(*sta->cred));
sta->cred->cred_attr = NULL;
os_memcpy(sta->cred->mac_addr, sta->addr, ETH_ALEN);
sta->wps->use_cred = sta->cred;
}
}
wps_er_sta_process(sta, msg, WSC_MSG);
}
static void wps_er_process_wlanevent_eap(struct wps_er_ap *ap, const u8 *addr,
struct wpabuf *msg)
{
struct wps_parse_attr attr;
struct wps_er_sta *sta;
wpa_printf(MSG_DEBUG, "WPS ER: WLANEvent - EAP - from " MACSTR,
MAC2STR(addr));
wpa_hexdump_buf(MSG_MSGDUMP, "WPS ER: WLANEvent - Enrollee's message "
"(TLVs from EAP-WSC)", msg);
if (wps_parse_msg(msg, &attr) < 0) {
wpa_printf(MSG_DEBUG, "WPS ER: Failed to parse TLVs in "
"WLANEvent message");
return;
}
sta = wps_er_add_sta_data(ap, addr, &attr, 0);
if (sta == NULL)
return;
if (attr.msg_type && *attr.msg_type == WPS_M1)
wps_er_sta_start(sta, msg);
else if (sta->wps) {
enum wsc_op_code op_code = WSC_MSG;
if (attr.msg_type) {
switch (*attr.msg_type) {
case WPS_WSC_ACK:
op_code = WSC_ACK;
break;
case WPS_WSC_NACK:
op_code = WSC_NACK;
break;
case WPS_WSC_DONE:
op_code = WSC_Done;
break;
}
}
wps_er_sta_process(sta, msg, op_code);
}
}
static void wps_er_process_wlanevent(struct wps_er_ap *ap,
struct wpabuf *event)
{
u8 *data;
u8 wlan_event_type;
u8 wlan_event_mac[ETH_ALEN];
struct wpabuf msg;
wpa_hexdump(MSG_MSGDUMP, "WPS ER: Received WLANEvent",
wpabuf_head(event), wpabuf_len(event));
if (wpabuf_len(event) < 1 + 17) {
wpa_printf(MSG_DEBUG, "WPS ER: Too short WLANEvent");
return;
}
data = wpabuf_mhead(event);
wlan_event_type = data[0];
if (hwaddr_aton((char *) data + 1, wlan_event_mac) < 0) {
wpa_printf(MSG_DEBUG, "WPS ER: Invalid WLANEventMAC in "
"WLANEvent");
return;
}
wpabuf_set(&msg, data + 1 + 17, wpabuf_len(event) - (1 + 17));
switch (wlan_event_type) {
case 1:
wps_er_process_wlanevent_probe_req(ap, wlan_event_mac, &msg);
break;
case 2:
wps_er_process_wlanevent_eap(ap, wlan_event_mac, &msg);
break;
default:
wpa_printf(MSG_DEBUG, "WPS ER: Unknown WLANEventType %d",
wlan_event_type);
break;
}
}
static void wps_er_http_event(struct wps_er *er, struct http_request *req,
unsigned int ap_id)
{
struct wps_er_ap *ap = wps_er_ap_get_id(er, ap_id);
struct wpabuf *event;
enum http_reply_code ret;
if (ap == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: HTTP event from unknown AP id "
"%u", ap_id);
wps_er_http_resp_not_found(req);
return;
}
wpa_printf(MSG_MSGDUMP, "WPS ER: HTTP event from AP id %u: %s",
ap_id, http_request_get_data(req));
event = xml_get_base64_item(http_request_get_data(req), "WLANEvent",
&ret);
if (event == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: Could not extract WLANEvent "
"from the event notification");
/*
* Reply with OK anyway to avoid getting unregistered from
* events.
*/
wps_er_http_resp_ok(req);
return;
}
wps_er_process_wlanevent(ap, event);
wpabuf_free(event);
wps_er_http_resp_ok(req);
}
static void wps_er_http_notify(struct wps_er *er, struct http_request *req)
{
char *uri = http_request_get_uri(req);
if (os_strncmp(uri, "/event/", 7) == 0) {
unsigned int event_id;
char *pos;
event_id = atoi(uri + 7);
if (event_id != er->event_id) {
wpa_printf(MSG_DEBUG, "WPS ER: HTTP event for an "
"unknown event id %u", event_id);
return;
}
pos = os_strchr(uri + 7, '/');
if (pos == NULL)
return;
pos++;
wps_er_http_event(er, req, atoi(pos));
} else {
wpa_printf(MSG_DEBUG, "WPS ER: Unknown HTTP NOTIFY for '%s'",
uri);
wps_er_http_resp_not_found(req);
}
}
static void wps_er_http_req(void *ctx, struct http_request *req)
{
struct wps_er *er = ctx;
struct sockaddr_in *cli = http_request_get_cli_addr(req);
enum httpread_hdr_type type = http_request_get_type(req);
struct wpabuf *buf;
wpa_printf(MSG_DEBUG, "WPS ER: HTTP request: '%s' (type %d) from "
"%s:%d",
http_request_get_uri(req), type,
inet_ntoa(cli->sin_addr), ntohs(cli->sin_port));
switch (type) {
case HTTPREAD_HDR_TYPE_NOTIFY:
wps_er_http_notify(er, req);
break;
default:
wpa_printf(MSG_DEBUG, "WPS ER: Unsupported HTTP request type "
"%d", type);
buf = wpabuf_alloc(200);
if (buf == NULL) {
http_request_deinit(req);
return;
}
wpabuf_put_str(buf,
"HTTP/1.1 501 Unimplemented\r\n"
"Connection: close\r\n");
http_put_date(buf);
wpabuf_put_str(buf, "\r\n");
http_request_send_and_deinit(req, buf);
break;
}
}
struct wps_er *
wps_er_init(struct wps_context *wps, const char *ifname, const char *filter)
{
struct wps_er *er;
struct in_addr addr;
er = os_zalloc(sizeof(*er));
if (er == NULL)
return NULL;
dl_list_init(&er->ap);
dl_list_init(&er->ap_unsubscribing);
dl_list_init(&er->ap_settings);
er->multicast_sd = -1;
er->ssdp_sd = -1;
os_strlcpy(er->ifname, ifname, sizeof(er->ifname));
er->wps = wps;
if (os_get_random((unsigned char *) &er->event_id,
sizeof(er->event_id)) < 0) {
wps_er_deinit(er, NULL, NULL);
return NULL;
}
/* Limit event_id to < 32 bits to avoid issues with atoi() */
er->event_id &= 0x0fffffff;
if (filter && os_strncmp(filter, "ifname=", 7) == 0) {
const char *pos, *end;
pos = filter + 7;
end = os_strchr(pos, ' ');
if (end) {
size_t len = end - pos;
os_strlcpy(er->ifname, pos, len < sizeof(er->ifname) ?
len + 1 : sizeof(er->ifname));
filter = end + 1;
} else {
os_strlcpy(er->ifname, pos, sizeof(er->ifname));
filter = NULL;
}
er->forced_ifname = 1;
}
if (filter) {
if (inet_aton(filter, &er->filter_addr) == 0) {
wpa_printf(MSG_INFO, "WPS UPnP: Invalid filter "
"address %s", filter);
wps_er_deinit(er, NULL, NULL);
return NULL;
}
wpa_printf(MSG_DEBUG, "WPS UPnP: Only accepting connections "
"with %s", filter);
}
if (get_netif_info(er->ifname, &er->ip_addr, &er->ip_addr_text,
er->mac_addr)) {
wpa_printf(MSG_INFO, "WPS UPnP: Could not get IP/MAC address "
"for %s. Does it have IP address?", er->ifname);
wps_er_deinit(er, NULL, NULL);
return NULL;
}
if (wps_er_ssdp_init(er) < 0) {
wpa_printf(MSG_INFO, "WPS UPnP: SSDP initialization failed");
wps_er_deinit(er, NULL, NULL);
return NULL;
}
addr.s_addr = er->ip_addr;
er->http_srv = http_server_init(&addr, -1, wps_er_http_req, er);
if (er->http_srv == NULL) {
wpa_printf(MSG_INFO, "WPS UPnP: HTTP initialization failed");
wps_er_deinit(er, NULL, NULL);
return NULL;
}
er->http_port = http_server_get_port(er->http_srv);
wpa_printf(MSG_DEBUG, "WPS ER: Start (ifname=%s ip_addr=%s)",
er->ifname, er->ip_addr_text);
return er;
}
void wps_er_refresh(struct wps_er *er)
{
struct wps_er_ap *ap;
struct wps_er_sta *sta;
dl_list_for_each(ap, &er->ap, struct wps_er_ap, list) {
wps_er_ap_event(er->wps, ap, WPS_EV_ER_AP_ADD);
dl_list_for_each(sta, &ap->sta, struct wps_er_sta, list)
wps_er_sta_event(er->wps, sta, WPS_EV_ER_ENROLLEE_ADD);
}
wps_er_send_ssdp_msearch(er);
}
static void wps_er_deinit_finish(void *eloop_data, void *user_ctx)
{
struct wps_er *er = eloop_data;
void (*deinit_done_cb)(void *ctx);
void *deinit_done_ctx;
struct wps_er_ap *ap, *tmp;
wpa_printf(MSG_DEBUG, "WPS ER: Finishing deinit");
dl_list_for_each_safe(ap, tmp, &er->ap_unsubscribing, struct wps_er_ap,
list) {
wpa_printf(MSG_DEBUG, "WPS ER: AP entry for %s (%s) still in ap_unsubscribing list - free it",
inet_ntoa(ap->addr), ap->location);
dl_list_del(&ap->list);
wps_er_ap_free(ap);
}
eloop_cancel_timeout(wps_er_deinit_finish, er, NULL);
deinit_done_cb = er->deinit_done_cb;
deinit_done_ctx = er->deinit_done_ctx;
os_free(er->ip_addr_text);
os_free(er);
if (deinit_done_cb)
deinit_done_cb(deinit_done_ctx);
}
void wps_er_deinit(struct wps_er *er, void (*cb)(void *ctx), void *ctx)
{
if (er == NULL)
return;
http_server_deinit(er->http_srv);
wps_er_ap_remove_all(er);
wps_er_ssdp_deinit(er);
eloop_register_timeout(dl_list_empty(&er->ap_unsubscribing) ? 0 : 5, 0,
wps_er_deinit_finish, er, NULL);
wpa_printf(MSG_DEBUG, "WPS ER: Finish deinit from timeout");
er->deinitializing = 1;
er->deinit_done_cb = cb;
er->deinit_done_ctx = ctx;
}
static void wps_er_http_set_sel_reg_cb(void *ctx, struct http_client *c,
enum http_client_event event)
{
struct wps_er_ap *ap = ctx;
union wps_event_data data;
os_memset(&data, 0, sizeof(data));
switch (event) {
case HTTP_CLIENT_OK:
wpa_printf(MSG_DEBUG, "WPS ER: SetSelectedRegistrar OK");
data.set_sel_reg.state = WPS_ER_SET_SEL_REG_DONE;
data.set_sel_reg.uuid = ap->uuid;
break;
case HTTP_CLIENT_FAILED:
case HTTP_CLIENT_INVALID_REPLY:
case HTTP_CLIENT_TIMEOUT:
wpa_printf(MSG_DEBUG, "WPS ER: SetSelectedRegistrar failed");
data.set_sel_reg.state = WPS_ER_SET_SEL_REG_FAILED;
data.set_sel_reg.uuid = ap->uuid;
break;
}
http_client_free(ap->http);
ap->http = NULL;
if (data.set_sel_reg.uuid)
ap->er->wps->event_cb(ap->er->wps->cb_ctx,
WPS_EV_ER_SET_SELECTED_REGISTRAR, &data);
}
static void wps_er_send_set_sel_reg(struct wps_er_ap *ap, struct wpabuf *msg)
{
struct wpabuf *buf;
char *len_ptr, *body_ptr;
struct sockaddr_in dst;
char *url, *path;
if (ap->control_url == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: No controlURL for AP");
return;
}
if (ap->http) {
wpa_printf(MSG_DEBUG, "WPS ER: Pending HTTP request for AP - "
"ignore new request");
return;
}
if (ap->wps) {
wpa_printf(MSG_DEBUG, "WPS ER: Pending WPS operation for AP - "
"skip SetSelectedRegistrar");
return;
}
url = http_client_url_parse(ap->control_url, &dst, &path);
if (url == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: Failed to parse controlURL");
return;
}
buf = wps_er_soap_hdr(msg, "SetSelectedRegistrar", "NewMessage", path,
&dst, &len_ptr, &body_ptr);
os_free(url);
if (buf == NULL)
return;
wps_er_soap_end(buf, "SetSelectedRegistrar", len_ptr, body_ptr);
ap->http = http_client_addr(&dst, buf, 1000,
wps_er_http_set_sel_reg_cb, ap);
if (ap->http == NULL)
wpabuf_free(buf);
}
static int wps_er_build_selected_registrar(struct wpabuf *msg, int sel_reg)
{
wpabuf_put_be16(msg, ATTR_SELECTED_REGISTRAR);
wpabuf_put_be16(msg, 1);
wpabuf_put_u8(msg, !!sel_reg);
return 0;
}
static int wps_er_build_dev_password_id(struct wpabuf *msg, u16 dev_passwd_id)
{
wpabuf_put_be16(msg, ATTR_DEV_PASSWORD_ID);
wpabuf_put_be16(msg, 2);
wpabuf_put_be16(msg, dev_passwd_id);
return 0;
}
static int wps_er_build_sel_reg_config_methods(struct wpabuf *msg,
u16 sel_reg_config_methods)
{
wpabuf_put_be16(msg, ATTR_SELECTED_REGISTRAR_CONFIG_METHODS);
wpabuf_put_be16(msg, 2);
wpabuf_put_be16(msg, sel_reg_config_methods);
return 0;
}
static int wps_er_build_uuid_r(struct wpabuf *msg, const u8 *uuid_r)
{
wpabuf_put_be16(msg, ATTR_UUID_R);
wpabuf_put_be16(msg, WPS_UUID_LEN);
wpabuf_put_data(msg, uuid_r, WPS_UUID_LEN);
return 0;
}
void wps_er_set_sel_reg(struct wps_er *er, int sel_reg, u16 dev_passwd_id,
u16 sel_reg_config_methods)
{
struct wpabuf *msg;
struct wps_er_ap *ap;
struct wps_registrar *reg = er->wps->registrar;
const u8 *auth_macs;
u8 bcast[ETH_ALEN];
size_t count;
union wps_event_data data;
if (er->skip_set_sel_reg) {
wpa_printf(MSG_DEBUG, "WPS ER: Skip SetSelectedRegistrar");
return;
}
msg = wpabuf_alloc(500);
if (msg == NULL)
return;
auth_macs = wps_authorized_macs(reg, &count);
if (count == 0) {
os_memset(bcast, 0xff, ETH_ALEN);
auth_macs = bcast;
count = 1;
}
if (wps_build_version(msg) ||
wps_er_build_selected_registrar(msg, sel_reg) ||
wps_er_build_dev_password_id(msg, dev_passwd_id) ||
wps_er_build_sel_reg_config_methods(msg, sel_reg_config_methods) ||
wps_build_wfa_ext(msg, 0, auth_macs, count) ||
wps_er_build_uuid_r(msg, er->wps->uuid)) {
wpabuf_free(msg);
return;
}
os_memset(&data, 0, sizeof(data));
data.set_sel_reg.sel_reg = sel_reg;
data.set_sel_reg.dev_passwd_id = dev_passwd_id;
data.set_sel_reg.sel_reg_config_methods = sel_reg_config_methods;
data.set_sel_reg.state = WPS_ER_SET_SEL_REG_START;
dl_list_for_each(ap, &er->ap, struct wps_er_ap, list) {
if (er->set_sel_reg_uuid_filter &&
os_memcmp(ap->uuid, er->set_sel_reg_uuid_filter,
WPS_UUID_LEN) != 0)
continue;
data.set_sel_reg.uuid = ap->uuid;
er->wps->event_cb(er->wps->cb_ctx,
WPS_EV_ER_SET_SELECTED_REGISTRAR, &data);
wps_er_send_set_sel_reg(ap, msg);
}
wpabuf_free(msg);
}
int wps_er_pbc(struct wps_er *er, const u8 *uuid, const u8 *addr)
{
int res;
struct wps_er_ap *ap;
if (er == NULL || er->wps == NULL)
return -1;
if (wps_registrar_pbc_overlap(er->wps->registrar, NULL, NULL)) {
wpa_printf(MSG_DEBUG, "WPS ER: PBC overlap - do not start PBC "
"mode");
return -2;
}
if (uuid)
ap = wps_er_ap_get(er, NULL, uuid, NULL);
else
ap = NULL;
if (ap == NULL) {
struct wps_er_sta *sta = NULL;
dl_list_for_each(ap, &er->ap, struct wps_er_ap, list) {
sta = wps_er_sta_get(ap, addr, uuid);
if (sta) {
uuid = ap->uuid;
break;
}
}
if (sta == NULL)
return -3; /* Unknown UUID */
}
if (ap->ap_settings == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: AP settings not known");
return -4;
}
er->set_sel_reg_uuid_filter = uuid;
res = wps_registrar_button_pushed(er->wps->registrar, NULL);
er->set_sel_reg_uuid_filter = NULL;
if (res)
return -1;
return 0;
}
static void wps_er_ap_settings_cb(void *ctx, const struct wps_credential *cred)
{
struct wps_er_ap *ap = ctx;
union wps_event_data data;
wpa_printf(MSG_DEBUG, "WPS ER: AP Settings received");
os_free(ap->ap_settings);
ap->ap_settings = os_malloc(sizeof(*cred));
if (ap->ap_settings) {
os_memcpy(ap->ap_settings, cred, sizeof(*cred));
ap->ap_settings->cred_attr = NULL;
}
os_memset(&data, 0, sizeof(data));
data.ap_settings.uuid = ap->uuid;
data.ap_settings.cred = cred;
ap->er->wps->event_cb(ap->er->wps->cb_ctx, WPS_EV_ER_AP_SETTINGS,
&data);
}
const u8 * wps_er_get_sta_uuid(struct wps_er *er, const u8 *addr)
{
struct wps_er_ap *ap;
dl_list_for_each(ap, &er->ap, struct wps_er_ap, list) {
struct wps_er_sta *sta;
sta = wps_er_sta_get(ap, addr, NULL);
if (sta)
return sta->uuid;
}
return NULL;
}
static void wps_er_http_put_message_cb(void *ctx, struct http_client *c,
enum http_client_event event)
{
struct wps_er_ap *ap = ctx;
struct wpabuf *reply;
char *msg = NULL;
switch (event) {
case HTTP_CLIENT_OK:
wpa_printf(MSG_DEBUG, "WPS ER: PutMessage OK");
reply = http_client_get_body(c);
if (reply)
msg = os_zalloc(wpabuf_len(reply) + 1);
if (msg == NULL) {
if (ap->wps) {
wps_deinit(ap->wps);
ap->wps = NULL;
}
break;
}
os_memcpy(msg, wpabuf_head(reply), wpabuf_len(reply));
break;
case HTTP_CLIENT_FAILED:
case HTTP_CLIENT_INVALID_REPLY:
case HTTP_CLIENT_TIMEOUT:
wpa_printf(MSG_DEBUG, "WPS ER: PutMessage failed");
if (ap->wps) {
wps_deinit(ap->wps);
ap->wps = NULL;
}
break;
}
http_client_free(ap->http);
ap->http = NULL;
if (msg) {
struct wpabuf *buf;
enum http_reply_code ret;
buf = xml_get_base64_item(msg, "NewOutMessage", &ret);
os_free(msg);
if (buf == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: Could not extract "
"NewOutMessage from PutMessage response");
wps_deinit(ap->wps);
ap->wps = NULL;
return;
}
wps_er_ap_process(ap, buf);
wpabuf_free(buf);
}
}
static void wps_er_ap_put_message(struct wps_er_ap *ap,
const struct wpabuf *msg)
{
struct wpabuf *buf;
char *len_ptr, *body_ptr;
struct sockaddr_in dst;
char *url, *path;
if (ap->http) {
wpa_printf(MSG_DEBUG, "WPS ER: Pending HTTP operation ongoing "
"with the AP - cannot continue learn");
return;
}
if (ap->control_url == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: No controlURL for AP");
return;
}
url = http_client_url_parse(ap->control_url, &dst, &path);
if (url == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: Failed to parse controlURL");
goto fail;
}
buf = wps_er_soap_hdr(msg, "PutMessage", "NewInMessage", path, &dst,
&len_ptr, &body_ptr);
os_free(url);
if (buf == NULL)
goto fail;
wps_er_soap_end(buf, "PutMessage", len_ptr, body_ptr);
ap->http = http_client_addr(&dst, buf, 10000,
wps_er_http_put_message_cb, ap);
if (ap->http == NULL) {
wpabuf_free(buf);
goto fail;
}
return;
fail:
if (ap->wps) {
wps_deinit(ap->wps);
ap->wps = NULL;
}
}
static void wps_er_ap_process(struct wps_er_ap *ap, struct wpabuf *msg)
{
enum wps_process_res res;
struct wps_parse_attr attr;
enum wsc_op_code op_code;
op_code = WSC_MSG;
if (wps_parse_msg(msg, &attr) == 0 && attr.msg_type) {
switch (*attr.msg_type) {
case WPS_WSC_ACK:
op_code = WSC_ACK;
break;
case WPS_WSC_NACK:
op_code = WSC_NACK;
break;
case WPS_WSC_DONE:
op_code = WSC_Done;
break;
}
}
res = wps_process_msg(ap->wps, op_code, msg);
if (res == WPS_CONTINUE) {
struct wpabuf *next = wps_get_msg(ap->wps, &op_code);
if (next) {
wps_er_ap_put_message(ap, next);
wpabuf_free(next);
} else {
wpa_printf(MSG_DEBUG, "WPS ER: Failed to build "
"message");
wps_deinit(ap->wps);
ap->wps = NULL;
}
} else if (res == WPS_DONE) {
wpa_printf(MSG_DEBUG, "WPS ER: Protocol run done");
wps_deinit(ap->wps);
ap->wps = NULL;
} else {
wpa_printf(MSG_DEBUG, "WPS ER: Failed to process message from "
"AP (res=%d)", res);
wps_deinit(ap->wps);
ap->wps = NULL;
}
}
static void wps_er_ap_learn_m1(struct wps_er_ap *ap, struct wpabuf *m1)
{
struct wps_config cfg;
if (ap->wps) {
wpa_printf(MSG_DEBUG, "WPS ER: Protocol run already in "
"progress with this AP");
return;
}
os_memset(&cfg, 0, sizeof(cfg));
cfg.wps = ap->er->wps;
cfg.registrar = 1;
ap->wps = wps_init(&cfg);
if (ap->wps == NULL)
return;
ap->wps->ap_settings_cb = wps_er_ap_settings_cb;
ap->wps->ap_settings_cb_ctx = ap;
wps_er_ap_process(ap, m1);
}
static void wps_er_ap_learn(struct wps_er_ap *ap, const char *dev_info)
{
struct wpabuf *info;
enum http_reply_code ret;
wpa_printf(MSG_DEBUG, "WPS ER: Received GetDeviceInfo response (M1) "
"from the AP");
info = xml_get_base64_item(dev_info, "NewDeviceInfo", &ret);
if (info == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: Could not extract "
"NewDeviceInfo from GetDeviceInfo response");
return;
}
ap->m1_handler(ap, info);
wpabuf_free(info);
}
static void wps_er_http_get_dev_info_cb(void *ctx, struct http_client *c,
enum http_client_event event)
{
struct wps_er_ap *ap = ctx;
struct wpabuf *reply;
char *dev_info = NULL;
switch (event) {
case HTTP_CLIENT_OK:
wpa_printf(MSG_DEBUG, "WPS ER: GetDeviceInfo OK");
reply = http_client_get_body(c);
if (reply == NULL)
break;
dev_info = os_zalloc(wpabuf_len(reply) + 1);
if (dev_info == NULL)
break;
os_memcpy(dev_info, wpabuf_head(reply), wpabuf_len(reply));
break;
case HTTP_CLIENT_FAILED:
case HTTP_CLIENT_INVALID_REPLY:
case HTTP_CLIENT_TIMEOUT:
wpa_printf(MSG_DEBUG, "WPS ER: GetDeviceInfo failed");
break;
}
http_client_free(ap->http);
ap->http = NULL;
if (dev_info) {
wps_er_ap_learn(ap, dev_info);
os_free(dev_info);
}
}
static int wps_er_send_get_device_info(struct wps_er_ap *ap,
void (*m1_handler)(struct wps_er_ap *ap,
struct wpabuf *m1))
{
struct wpabuf *buf;
char *len_ptr, *body_ptr;
struct sockaddr_in dst;
char *url, *path;
if (ap->http) {
wpa_printf(MSG_DEBUG, "WPS ER: Pending HTTP operation ongoing "
"with the AP - cannot get device info");
return -1;
}
if (ap->control_url == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: No controlURL for AP");
return -1;
}
url = http_client_url_parse(ap->control_url, &dst, &path);
if (url == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: Failed to parse controlURL");
return -1;
}
buf = wps_er_soap_hdr(NULL, "GetDeviceInfo", NULL, path, &dst,
&len_ptr, &body_ptr);
os_free(url);
if (buf == NULL)
return -1;
wps_er_soap_end(buf, "GetDeviceInfo", len_ptr, body_ptr);
ap->http = http_client_addr(&dst, buf, 10000,
wps_er_http_get_dev_info_cb, ap);
if (ap->http == NULL) {
wpabuf_free(buf);
return -1;
}
ap->m1_handler = m1_handler;
return 0;
}
int wps_er_learn(struct wps_er *er, const u8 *uuid, const u8 *addr,
const u8 *pin, size_t pin_len)
{
struct wps_er_ap *ap;
if (er == NULL)
return -1;
ap = wps_er_ap_get(er, NULL, uuid, addr);
if (ap == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: AP not found for learn "
"request");
return -1;
}
if (uuid == NULL)
uuid = ap->uuid;
if (ap->wps) {
wpa_printf(MSG_DEBUG, "WPS ER: Pending operation ongoing "
"with the AP - cannot start learn");
return -1;
}
if (wps_er_send_get_device_info(ap, wps_er_ap_learn_m1) < 0)
return -1;
er->skip_set_sel_reg = 1;
wps_registrar_add_pin(er->wps->registrar, NULL, uuid, pin, pin_len, 0);
er->skip_set_sel_reg = 0;
return 0;
}
int wps_er_set_config(struct wps_er *er, const u8 *uuid, const u8 *addr,
const struct wps_credential *cred)
{
struct wps_er_ap *ap;
if (er == NULL)
return -1;
ap = wps_er_ap_get(er, NULL, uuid, addr);
if (ap == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: AP not found for set config "
"request");
return -1;
}
os_free(ap->ap_settings);
ap->ap_settings = os_malloc(sizeof(*cred));
if (ap->ap_settings == NULL)
return -1;
os_memcpy(ap->ap_settings, cred, sizeof(*cred));
ap->ap_settings->cred_attr = NULL;
wpa_printf(MSG_DEBUG, "WPS ER: Updated local AP settings based set "
"config request");
return 0;
}
static void wps_er_ap_config_m1(struct wps_er_ap *ap, struct wpabuf *m1)
{
struct wps_config cfg;
if (ap->wps) {
wpa_printf(MSG_DEBUG, "WPS ER: Protocol run already in "
"progress with this AP");
return;
}
os_memset(&cfg, 0, sizeof(cfg));
cfg.wps = ap->er->wps;
cfg.registrar = 1;
cfg.new_ap_settings = ap->ap_settings;
ap->wps = wps_init(&cfg);
if (ap->wps == NULL)
return;
ap->wps->ap_settings_cb = NULL;
ap->wps->ap_settings_cb_ctx = NULL;
wps_er_ap_process(ap, m1);
}
int wps_er_config(struct wps_er *er, const u8 *uuid, const u8 *addr,
const u8 *pin, size_t pin_len,
const struct wps_credential *cred)
{
struct wps_er_ap *ap;
if (er == NULL)
return -1;
ap = wps_er_ap_get(er, NULL, uuid, addr);
if (ap == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: AP not found for config "
"request");
return -1;
}
if (uuid == NULL)
uuid = ap->uuid;
if (ap->wps) {
wpa_printf(MSG_DEBUG, "WPS ER: Pending operation ongoing "
"with the AP - cannot start config");
return -1;
}
os_free(ap->ap_settings);
ap->ap_settings = os_malloc(sizeof(*cred));
if (ap->ap_settings == NULL)
return -1;
os_memcpy(ap->ap_settings, cred, sizeof(*cred));
ap->ap_settings->cred_attr = NULL;
if (wps_er_send_get_device_info(ap, wps_er_ap_config_m1) < 0)
return -1;
er->skip_set_sel_reg = 1;
wps_registrar_add_pin(er->wps->registrar, NULL, uuid, pin, pin_len, 0);
er->skip_set_sel_reg = 0;
return 0;
}
#ifdef CONFIG_WPS_NFC
struct wpabuf * wps_er_config_token_from_cred(struct wps_context *wps,
struct wps_credential *cred)
{
struct wpabuf *ret;
struct wps_data data;
ret = wpabuf_alloc(500);
if (ret == NULL)
return NULL;
os_memset(&data, 0, sizeof(data));
data.wps = wps;
data.use_cred = cred;
if (wps_build_cred(&data, ret) ||
wps_build_wfa_ext(ret, 0, NULL, 0)) {
wpabuf_free(ret);
return NULL;
}
return ret;
}
struct wpabuf * wps_er_nfc_config_token(struct wps_er *er, const u8 *uuid,
const u8 *addr)
{
struct wps_er_ap *ap;
if (er == NULL)
return NULL;
ap = wps_er_ap_get(er, NULL, uuid, addr);
if (ap == NULL)
return NULL;
if (ap->ap_settings == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: No settings known for the "
"selected AP");
return NULL;
}
return wps_er_config_token_from_cred(er->wps, ap->ap_settings);
}
struct wpabuf * wps_er_nfc_handover_sel(struct wps_er *er,
struct wps_context *wps, const u8 *uuid,
const u8 *addr, struct wpabuf *pubkey)
{
struct wps_er_ap *ap;
if (er == NULL)
return NULL;
ap = wps_er_ap_get(er, NULL, uuid, addr);
if (ap == NULL)
return NULL;
if (ap->ap_settings == NULL) {
wpa_printf(MSG_DEBUG, "WPS ER: No settings known for the "
"selected AP");
return NULL;
}
os_memcpy(wps->ssid, ap->ap_settings->ssid, ap->ap_settings->ssid_len);
wps->ssid_len = ap->ap_settings->ssid_len;
return wps_build_nfc_handover_sel(wps, pubkey, addr, 0);
}
#endif /* CONFIG_WPS_NFC */