wpa_supplicant: Initial Revision 0.8.X
Based on:
commit 0725cc7b7efc434910e89865c42eda7ce61bbf08
Author: Jouni Malinen <j@w1.fi>
Date: Thu Apr 21 20:41:01 2011 +0300
Enable CONFIG_DRIVER_NL80211=y in the default configuration
nl80211 should be preferred over WEXT with any recent Linux
kernel version.
Change-Id: I26aec5afbbd4f4a1f5fd900912545b6f5050de64
Signed-off-by: Dmitry Shmidt <dimitrysh@google.com>
diff --git a/src/wps/Makefile b/src/wps/Makefile
new file mode 100644
index 0000000..9c41962
--- /dev/null
+++ b/src/wps/Makefile
@@ -0,0 +1,8 @@
+all:
+ @echo Nothing to be made.
+
+clean:
+ rm -f *~ *.o *.d
+
+install:
+ @echo Nothing to be made.
diff --git a/src/wps/http.h b/src/wps/http.h
new file mode 100644
index 0000000..2fee3a8
--- /dev/null
+++ b/src/wps/http.h
@@ -0,0 +1,29 @@
+/*
+ * HTTP for WPS
+ * Copyright (c) 2000-2003 Intel Corporation
+ * Copyright (c) 2006-2007 Sony Corporation
+ * Copyright (c) 2008-2009 Atheros Communications
+ * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
+ *
+ * See wps_upnp.c for more details on licensing and code history.
+ */
+
+#ifndef HTTP_H
+#define HTTP_H
+
+enum http_reply_code {
+ HTTP_OK = 200,
+ HTTP_BAD_REQUEST = 400,
+ UPNP_INVALID_ACTION = 401,
+ UPNP_INVALID_ARGS = 402,
+ HTTP_NOT_FOUND = 404,
+ HTTP_PRECONDITION_FAILED = 412,
+ HTTP_INTERNAL_SERVER_ERROR = 500,
+ HTTP_UNIMPLEMENTED = 501,
+ UPNP_ACTION_FAILED = 501,
+ UPNP_ARG_VALUE_INVALID = 600,
+ UPNP_ARG_VALUE_OUT_OF_RANGE = 601,
+ UPNP_OUT_OF_MEMORY = 603
+};
+
+#endif /* HTTP_H */
diff --git a/src/wps/http_client.c b/src/wps/http_client.c
new file mode 100644
index 0000000..9b53b80
--- /dev/null
+++ b/src/wps/http_client.c
@@ -0,0 +1,374 @@
+/*
+ * http_client - HTTP client
+ * Copyright (c) 2009, 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.
+ */
+
+#include "includes.h"
+#include <fcntl.h>
+
+#include "common.h"
+#include "eloop.h"
+#include "httpread.h"
+#include "http_client.h"
+
+
+#define HTTP_CLIENT_TIMEOUT_SEC 30
+
+
+struct http_client {
+ struct sockaddr_in dst;
+ int sd;
+ struct wpabuf *req;
+ size_t req_pos;
+ size_t max_response;
+
+ void (*cb)(void *ctx, struct http_client *c,
+ enum http_client_event event);
+ void *cb_ctx;
+ struct httpread *hread;
+ struct wpabuf body;
+};
+
+
+static void http_client_timeout(void *eloop_data, void *user_ctx)
+{
+ struct http_client *c = eloop_data;
+ wpa_printf(MSG_DEBUG, "HTTP: Timeout (c=%p)", c);
+ c->cb(c->cb_ctx, c, HTTP_CLIENT_TIMEOUT);
+}
+
+
+static void http_client_got_response(struct httpread *handle, void *cookie,
+ enum httpread_event e)
+{
+ struct http_client *c = cookie;
+
+ wpa_printf(MSG_DEBUG, "HTTP: httpread callback: handle=%p cookie=%p "
+ "e=%d", handle, cookie, e);
+
+ eloop_cancel_timeout(http_client_timeout, c, NULL);
+ switch (e) {
+ case HTTPREAD_EVENT_FILE_READY:
+ if (httpread_hdr_type_get(c->hread) == HTTPREAD_HDR_TYPE_REPLY)
+ {
+ int reply_code = httpread_reply_code_get(c->hread);
+ if (reply_code == 200 /* OK */) {
+ wpa_printf(MSG_DEBUG, "HTTP: Response OK from "
+ "%s:%d",
+ inet_ntoa(c->dst.sin_addr),
+ ntohs(c->dst.sin_port));
+ c->cb(c->cb_ctx, c, HTTP_CLIENT_OK);
+ } else {
+ wpa_printf(MSG_DEBUG, "HTTP: Error %d from "
+ "%s:%d", reply_code,
+ inet_ntoa(c->dst.sin_addr),
+ ntohs(c->dst.sin_port));
+ c->cb(c->cb_ctx, c, HTTP_CLIENT_INVALID_REPLY);
+ }
+ } else
+ c->cb(c->cb_ctx, c, HTTP_CLIENT_INVALID_REPLY);
+ break;
+ case HTTPREAD_EVENT_TIMEOUT:
+ c->cb(c->cb_ctx, c, HTTP_CLIENT_TIMEOUT);
+ break;
+ case HTTPREAD_EVENT_ERROR:
+ c->cb(c->cb_ctx, c, HTTP_CLIENT_FAILED);
+ break;
+ }
+}
+
+
+static void http_client_tx_ready(int sock, void *eloop_ctx, void *sock_ctx)
+{
+ struct http_client *c = eloop_ctx;
+ int res;
+
+ wpa_printf(MSG_DEBUG, "HTTP: Send client request to %s:%d (%lu of %lu "
+ "bytes remaining)",
+ inet_ntoa(c->dst.sin_addr), ntohs(c->dst.sin_port),
+ (unsigned long) wpabuf_len(c->req),
+ (unsigned long) wpabuf_len(c->req) - c->req_pos);
+
+ res = send(c->sd, wpabuf_head(c->req) + c->req_pos,
+ wpabuf_len(c->req) - c->req_pos, 0);
+ if (res < 0) {
+ wpa_printf(MSG_DEBUG, "HTTP: Failed to send buffer: %s",
+ strerror(errno));
+ eloop_unregister_sock(c->sd, EVENT_TYPE_WRITE);
+ c->cb(c->cb_ctx, c, HTTP_CLIENT_FAILED);
+ return;
+ }
+
+ if ((size_t) res < wpabuf_len(c->req) - c->req_pos) {
+ wpa_printf(MSG_DEBUG, "HTTP: Sent %d of %lu bytes; %lu bytes "
+ "remaining",
+ res, (unsigned long) wpabuf_len(c->req),
+ (unsigned long) wpabuf_len(c->req) - c->req_pos -
+ res);
+ c->req_pos += res;
+ return;
+ }
+
+ wpa_printf(MSG_DEBUG, "HTTP: Full client request sent to %s:%d",
+ inet_ntoa(c->dst.sin_addr), ntohs(c->dst.sin_port));
+ eloop_unregister_sock(c->sd, EVENT_TYPE_WRITE);
+ wpabuf_free(c->req);
+ c->req = NULL;
+
+ c->hread = httpread_create(c->sd, http_client_got_response, c,
+ c->max_response, HTTP_CLIENT_TIMEOUT_SEC);
+ if (c->hread == NULL) {
+ c->cb(c->cb_ctx, c, HTTP_CLIENT_FAILED);
+ return;
+ }
+}
+
+
+struct http_client * http_client_addr(struct sockaddr_in *dst,
+ struct wpabuf *req, size_t max_response,
+ void (*cb)(void *ctx,
+ struct http_client *c,
+ enum http_client_event event),
+ void *cb_ctx)
+{
+ struct http_client *c;
+
+ c = os_zalloc(sizeof(*c));
+ if (c == NULL)
+ return NULL;
+ c->sd = -1;
+ c->dst = *dst;
+ c->max_response = max_response;
+ c->cb = cb;
+ c->cb_ctx = cb_ctx;
+
+ c->sd = socket(AF_INET, SOCK_STREAM, 0);
+ if (c->sd < 0) {
+ http_client_free(c);
+ return NULL;
+ }
+
+ if (fcntl(c->sd, F_SETFL, O_NONBLOCK) != 0) {
+ wpa_printf(MSG_DEBUG, "HTTP: fnctl(O_NONBLOCK) failed: %s",
+ strerror(errno));
+ http_client_free(c);
+ return NULL;
+ }
+
+ if (connect(c->sd, (struct sockaddr *) dst, sizeof(*dst))) {
+ if (errno != EINPROGRESS) {
+ wpa_printf(MSG_DEBUG, "HTTP: Failed to connect: %s",
+ strerror(errno));
+ http_client_free(c);
+ return NULL;
+ }
+
+ /*
+ * Continue connecting in the background; eloop will call us
+ * once the connection is ready (or failed).
+ */
+ }
+
+ if (eloop_register_sock(c->sd, EVENT_TYPE_WRITE, http_client_tx_ready,
+ c, NULL)) {
+ http_client_free(c);
+ return NULL;
+ }
+
+ if (eloop_register_timeout(HTTP_CLIENT_TIMEOUT_SEC, 0,
+ http_client_timeout, c, NULL)) {
+ http_client_free(c);
+ return NULL;
+ }
+
+ c->req = req;
+
+ return c;
+}
+
+
+char * http_client_url_parse(const char *url, struct sockaddr_in *dst,
+ char **ret_path)
+{
+ char *u, *addr, *port, *path;
+
+ u = os_strdup(url);
+ if (u == NULL)
+ return NULL;
+
+ os_memset(dst, 0, sizeof(*dst));
+ dst->sin_family = AF_INET;
+ addr = u + 7;
+ path = os_strchr(addr, '/');
+ port = os_strchr(addr, ':');
+ if (path == NULL) {
+ path = "/";
+ } else {
+ *path = '\0'; /* temporary nul termination for address */
+ if (port > path)
+ port = NULL;
+ }
+ if (port)
+ *port++ = '\0';
+
+ if (inet_aton(addr, &dst->sin_addr) == 0) {
+ /* TODO: name lookup */
+ wpa_printf(MSG_DEBUG, "HTTP: Unsupported address in URL '%s' "
+ "(addr='%s' port='%s')",
+ url, addr, port);
+ os_free(u);
+ return NULL;
+ }
+
+ if (port)
+ dst->sin_port = htons(atoi(port));
+ else
+ dst->sin_port = htons(80);
+
+ if (*path == '\0') {
+ /* remove temporary nul termination for address */
+ *path = '/';
+ }
+
+ *ret_path = path;
+
+ return u;
+}
+
+
+struct http_client * http_client_url(const char *url,
+ struct wpabuf *req, size_t max_response,
+ void (*cb)(void *ctx,
+ struct http_client *c,
+ enum http_client_event event),
+ void *cb_ctx)
+{
+ struct sockaddr_in dst;
+ struct http_client *c;
+ char *u, *path;
+ struct wpabuf *req_buf = NULL;
+
+ if (os_strncmp(url, "http://", 7) != 0)
+ return NULL;
+ u = http_client_url_parse(url, &dst, &path);
+ if (u == NULL)
+ return NULL;
+
+ if (req == NULL) {
+ req_buf = wpabuf_alloc(os_strlen(url) + 1000);
+ if (req_buf == NULL) {
+ os_free(u);
+ return NULL;
+ }
+ req = req_buf;
+ wpabuf_printf(req,
+ "GET %s HTTP/1.1\r\n"
+ "Cache-Control: no-cache\r\n"
+ "Pragma: no-cache\r\n"
+ "Accept: text/xml, application/xml\r\n"
+ "User-Agent: wpa_supplicant\r\n"
+ "Host: %s:%d\r\n"
+ "\r\n",
+ path, inet_ntoa(dst.sin_addr),
+ ntohs(dst.sin_port));
+ }
+ os_free(u);
+
+ c = http_client_addr(&dst, req, max_response, cb, cb_ctx);
+ if (c == NULL) {
+ wpabuf_free(req_buf);
+ return NULL;
+ }
+
+ return c;
+}
+
+
+void http_client_free(struct http_client *c)
+{
+ if (c == NULL)
+ return;
+ httpread_destroy(c->hread);
+ wpabuf_free(c->req);
+ if (c->sd >= 0) {
+ eloop_unregister_sock(c->sd, EVENT_TYPE_WRITE);
+ close(c->sd);
+ }
+ eloop_cancel_timeout(http_client_timeout, c, NULL);
+ os_free(c);
+}
+
+
+struct wpabuf * http_client_get_body(struct http_client *c)
+{
+ if (c->hread == NULL)
+ return NULL;
+ wpabuf_set(&c->body, httpread_data_get(c->hread),
+ httpread_length_get(c->hread));
+ return &c->body;
+}
+
+
+char * http_client_get_hdr_line(struct http_client *c, const char *tag)
+{
+ if (c->hread == NULL)
+ return NULL;
+ return httpread_hdr_line_get(c->hread, tag);
+}
+
+
+char * http_link_update(char *url, const char *base)
+{
+ char *n;
+ size_t len;
+ const char *pos;
+
+ /* RFC 2396, Chapter 5.2 */
+ /* TODO: consider adding all cases described in RFC 2396 */
+
+ if (url == NULL)
+ return NULL;
+
+ if (os_strncmp(url, "http://", 7) == 0)
+ return url; /* absolute link */
+
+ if (os_strncmp(base, "http://", 7) != 0)
+ return url; /* unable to handle base URL */
+
+ len = os_strlen(url) + 1 + os_strlen(base) + 1;
+ n = os_malloc(len);
+ if (n == NULL)
+ return url; /* failed */
+
+ if (url[0] == '/') {
+ pos = os_strchr(base + 7, '/');
+ if (pos == NULL) {
+ os_snprintf(n, len, "%s%s", base, url);
+ } else {
+ os_memcpy(n, base, pos - base);
+ os_memcpy(n + (pos - base), url, os_strlen(url) + 1);
+ }
+ } else {
+ pos = os_strrchr(base + 7, '/');
+ if (pos == NULL) {
+ os_snprintf(n, len, "%s/%s", base, url);
+ } else {
+ os_memcpy(n, base, pos - base + 1);
+ os_memcpy(n + (pos - base) + 1, url, os_strlen(url) +
+ 1);
+ }
+ }
+
+ os_free(url);
+
+ return n;
+}
diff --git a/src/wps/http_client.h b/src/wps/http_client.h
new file mode 100644
index 0000000..924d6ab
--- /dev/null
+++ b/src/wps/http_client.h
@@ -0,0 +1,46 @@
+/*
+ * http_client - HTTP client
+ * Copyright (c) 2009, 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.
+ */
+
+#ifndef HTTP_CLIENT_H
+#define HTTP_CLIENT_H
+
+struct http_client;
+
+enum http_client_event {
+ HTTP_CLIENT_FAILED,
+ HTTP_CLIENT_TIMEOUT,
+ HTTP_CLIENT_OK,
+ HTTP_CLIENT_INVALID_REPLY,
+};
+
+char * http_client_url_parse(const char *url, struct sockaddr_in *dst,
+ char **path);
+struct http_client * http_client_addr(struct sockaddr_in *dst,
+ struct wpabuf *req, size_t max_response,
+ void (*cb)(void *ctx,
+ struct http_client *c,
+ enum http_client_event event),
+ void *cb_ctx);
+struct http_client * http_client_url(const char *url,
+ struct wpabuf *req, size_t max_response,
+ void (*cb)(void *ctx,
+ struct http_client *c,
+ enum http_client_event event),
+ void *cb_ctx);
+void http_client_free(struct http_client *c);
+struct wpabuf * http_client_get_body(struct http_client *c);
+char * http_client_get_hdr_line(struct http_client *c, const char *tag);
+char * http_link_update(char *url, const char *base);
+
+#endif /* HTTP_CLIENT_H */
diff --git a/src/wps/http_server.c b/src/wps/http_server.c
new file mode 100644
index 0000000..356f599
--- /dev/null
+++ b/src/wps/http_server.c
@@ -0,0 +1,312 @@
+/*
+ * http_server - HTTP server
+ * Copyright (c) 2009, 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.
+ */
+
+#include "includes.h"
+#include <fcntl.h>
+
+#include "common.h"
+#include "eloop.h"
+#include "httpread.h"
+#include "http_server.h"
+
+#define HTTP_SERVER_TIMEOUT 30
+#define HTTP_SERVER_MAX_REQ_LEN 8000
+#define HTTP_SERVER_MAX_CONNECTIONS 10
+
+struct http_request {
+ struct http_request *next;
+ struct http_server *srv;
+ int fd;
+ struct sockaddr_in cli;
+ struct httpread *hread;
+};
+
+struct http_server {
+ void (*cb)(void *ctx, struct http_request *req);
+ void *cb_ctx;
+
+ int fd;
+ int port;
+
+ struct http_request *requests;
+ unsigned int request_count;
+};
+
+
+static void http_request_cb(struct httpread *handle, void *cookie,
+ enum httpread_event en)
+{
+ struct http_request *req = cookie;
+ struct http_server *srv = req->srv;
+
+ if (en == HTTPREAD_EVENT_FILE_READY) {
+ wpa_printf(MSG_DEBUG, "HTTP: Request from %s:%d received",
+ inet_ntoa(req->cli.sin_addr),
+ ntohs(req->cli.sin_port));
+ srv->cb(srv->cb_ctx, req);
+ return;
+ }
+ wpa_printf(MSG_DEBUG, "HTTP: Request from %s:%d could not be received "
+ "completely", inet_ntoa(req->cli.sin_addr),
+ ntohs(req->cli.sin_port));
+ http_request_deinit(req);
+}
+
+
+static struct http_request * http_request_init(struct http_server *srv, int fd,
+ struct sockaddr_in *cli)
+{
+ struct http_request *req;
+
+ if (srv->request_count >= HTTP_SERVER_MAX_CONNECTIONS) {
+ wpa_printf(MSG_DEBUG, "HTTP: Too many concurrent requests");
+ return NULL;
+ }
+
+ req = os_zalloc(sizeof(*req));
+ if (req == NULL)
+ return NULL;
+
+ req->srv = srv;
+ req->fd = fd;
+ req->cli = *cli;
+
+ req->hread = httpread_create(req->fd, http_request_cb, req,
+ HTTP_SERVER_MAX_REQ_LEN,
+ HTTP_SERVER_TIMEOUT);
+ if (req->hread == NULL) {
+ http_request_deinit(req);
+ return NULL;
+ }
+
+ return req;
+}
+
+
+void http_request_deinit(struct http_request *req)
+{
+ struct http_request *r, *p;
+ struct http_server *srv;
+
+ if (req == NULL)
+ return;
+
+ srv = req->srv;
+ p = NULL;
+ r = srv->requests;
+ while (r) {
+ if (r == req) {
+ if (p)
+ p->next = r->next;
+ else
+ srv->requests = r->next;
+ srv->request_count--;
+ break;
+ }
+ p = r;
+ r = r->next;
+ }
+
+ httpread_destroy(req->hread);
+ close(req->fd);
+ os_free(req);
+}
+
+
+static void http_request_free_all(struct http_request *req)
+{
+ struct http_request *prev;
+ while (req) {
+ prev = req;
+ req = req->next;
+ http_request_deinit(prev);
+ }
+}
+
+
+void http_request_send(struct http_request *req, struct wpabuf *resp)
+{
+ int res;
+
+ wpa_printf(MSG_DEBUG, "HTTP: Send %lu byte response to %s:%d",
+ (unsigned long) wpabuf_len(resp),
+ inet_ntoa(req->cli.sin_addr),
+ ntohs(req->cli.sin_port));
+
+ res = send(req->fd, wpabuf_head(resp), wpabuf_len(resp), 0);
+ if (res < 0) {
+ wpa_printf(MSG_DEBUG, "HTTP: Send failed: %s",
+ strerror(errno));
+ } else if ((size_t) res < wpabuf_len(resp)) {
+ wpa_printf(MSG_DEBUG, "HTTP: Sent only %d of %lu bytes",
+ res, (unsigned long) wpabuf_len(resp));
+ /* TODO: add eloop handler for sending rest of the data */
+ }
+
+ wpabuf_free(resp);
+}
+
+
+void http_request_send_and_deinit(struct http_request *req,
+ struct wpabuf *resp)
+{
+ http_request_send(req, resp);
+ http_request_deinit(req);
+}
+
+
+enum httpread_hdr_type http_request_get_type(struct http_request *req)
+{
+ return httpread_hdr_type_get(req->hread);
+}
+
+
+char * http_request_get_uri(struct http_request *req)
+{
+ return httpread_uri_get(req->hread);
+}
+
+
+char * http_request_get_hdr(struct http_request *req)
+{
+ return httpread_hdr_get(req->hread);
+}
+
+
+char * http_request_get_data(struct http_request *req)
+{
+ return httpread_data_get(req->hread);
+}
+
+
+char * http_request_get_hdr_line(struct http_request *req, const char *tag)
+{
+ return httpread_hdr_line_get(req->hread, tag);
+}
+
+
+struct sockaddr_in * http_request_get_cli_addr(struct http_request *req)
+{
+ return &req->cli;
+}
+
+
+static void http_server_cb(int sd, void *eloop_ctx, void *sock_ctx)
+{
+ struct sockaddr_in addr;
+ socklen_t addr_len = sizeof(addr);
+ struct http_server *srv = eloop_ctx;
+ int conn;
+ struct http_request *req;
+
+ conn = accept(srv->fd, (struct sockaddr *) &addr, &addr_len);
+ if (conn < 0) {
+ wpa_printf(MSG_DEBUG, "HTTP: Failed to accept new connection: "
+ "%s", strerror(errno));
+ return;
+ }
+ wpa_printf(MSG_DEBUG, "HTTP: Connection from %s:%d",
+ inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
+
+ req = http_request_init(srv, conn, &addr);
+ if (req == NULL) {
+ close(conn);
+ return;
+ }
+
+ req->next = srv->requests;
+ srv->requests = req;
+ srv->request_count++;
+}
+
+
+struct http_server * http_server_init(struct in_addr *addr, int port,
+ void (*cb)(void *ctx,
+ struct http_request *req),
+ void *cb_ctx)
+{
+ struct sockaddr_in sin;
+ struct http_server *srv;
+
+ srv = os_zalloc(sizeof(*srv));
+ if (srv == NULL)
+ return NULL;
+ srv->cb = cb;
+ srv->cb_ctx = cb_ctx;
+
+ srv->fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (srv->fd < 0)
+ goto fail;
+ if (fcntl(srv->fd, F_SETFL, O_NONBLOCK) < 0)
+ goto fail;
+ if (port < 0)
+ srv->port = 49152;
+ else
+ srv->port = port;
+
+ os_memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = addr->s_addr;
+
+ for (;;) {
+ sin.sin_port = htons(srv->port);
+ if (bind(srv->fd, (struct sockaddr *) &sin, sizeof(sin)) == 0)
+ break;
+ if (errno == EADDRINUSE) {
+ /* search for unused port */
+ if (++srv->port == 65535 || port >= 0)
+ goto fail;
+ continue;
+ }
+ wpa_printf(MSG_DEBUG, "HTTP: Failed to bind server port %d: "
+ "%s", srv->port, strerror(errno));
+ goto fail;
+ }
+ if (listen(srv->fd, 10 /* max backlog */) < 0)
+ goto fail;
+ if (fcntl(srv->fd, F_SETFL, O_NONBLOCK) < 0)
+ goto fail;
+ if (eloop_register_sock(srv->fd, EVENT_TYPE_READ, http_server_cb,
+ srv, NULL))
+ goto fail;
+
+ wpa_printf(MSG_DEBUG, "HTTP: Started server on %s:%d",
+ inet_ntoa(*addr), srv->port);
+
+ return srv;
+
+fail:
+ http_server_deinit(srv);
+ return NULL;
+}
+
+
+void http_server_deinit(struct http_server *srv)
+{
+ if (srv == NULL)
+ return;
+ if (srv->fd >= 0) {
+ eloop_unregister_sock(srv->fd, EVENT_TYPE_READ);
+ close(srv->fd);
+ }
+ http_request_free_all(srv->requests);
+
+ os_free(srv);
+}
+
+
+int http_server_get_port(struct http_server *srv)
+{
+ return srv->port;
+}
diff --git a/src/wps/http_server.h b/src/wps/http_server.h
new file mode 100644
index 0000000..219941c
--- /dev/null
+++ b/src/wps/http_server.h
@@ -0,0 +1,39 @@
+/*
+ * http_server - HTTP server
+ * Copyright (c) 2009, 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.
+ */
+
+#ifndef HTTP_SERVER_H
+#define HTTP_SERVER_H
+
+struct http_server;
+struct http_request;
+
+void http_request_deinit(struct http_request *req);
+void http_request_send(struct http_request *req, struct wpabuf *resp);
+void http_request_send_and_deinit(struct http_request *req,
+ struct wpabuf *resp);
+enum httpread_hdr_type http_request_get_type(struct http_request *req);
+char * http_request_get_uri(struct http_request *req);
+char * http_request_get_hdr(struct http_request *req);
+char * http_request_get_data(struct http_request *req);
+char * http_request_get_hdr_line(struct http_request *req, const char *tag);
+struct sockaddr_in * http_request_get_cli_addr(struct http_request *req);
+
+struct http_server * http_server_init(struct in_addr *addr, int port,
+ void (*cb)(void *ctx,
+ struct http_request *req),
+ void *cb_ctx);
+void http_server_deinit(struct http_server *srv);
+int http_server_get_port(struct http_server *srv);
+
+#endif /* HTTP_SERVER_H */
diff --git a/src/wps/httpread.c b/src/wps/httpread.c
new file mode 100644
index 0000000..40422e4
--- /dev/null
+++ b/src/wps/httpread.c
@@ -0,0 +1,861 @@
+/*
+ * httpread - Manage reading file(s) from HTTP/TCP socket
+ * Author: Ted Merrill
+ * Copyright 2008 Atheros Communications
+ *
+ * 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.
+ *
+ * The files are buffered via internal callbacks from eloop, then presented to
+ * an application callback routine when completely read into memory. May also
+ * be used if no file is expected but just to get the header, including HTTP
+ * replies (e.g. HTTP/1.1 200 OK etc.).
+ *
+ * This does not attempt to be an optimally efficient implementation, but does
+ * attempt to be of reasonably small size and memory consumption; assuming that
+ * only small files are to be read. A maximum file size is provided by
+ * application and enforced.
+ *
+ * It is assumed that the application does not expect any of the following:
+ * -- transfer encoding other than chunked
+ * -- trailer fields
+ * It is assumed that, even if the other side requested that the connection be
+ * kept open, that we will close it (thus HTTP messages sent by application
+ * should have the connection closed field); this is allowed by HTTP/1.1 and
+ * simplifies things for us.
+ *
+ * Other limitations:
+ * -- HTTP header may not exceed a hard-coded size.
+ *
+ * Notes:
+ * This code would be massively simpler without some of the new features of
+ * HTTP/1.1, especially chunked data.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "eloop.h"
+#include "httpread.h"
+
+
+/* Tunable parameters */
+#define HTTPREAD_READBUF_SIZE 1024 /* read in chunks of this size */
+#define HTTPREAD_HEADER_MAX_SIZE 4096 /* max allowed for headers */
+#define HTTPREAD_BODYBUF_DELTA 4096 /* increase allocation by this */
+
+#if 0
+/* httpread_debug -- set this global variable > 0 e.g. from debugger
+ * to enable debugs (larger numbers for more debugs)
+ * Make this a #define of 0 to eliminate the debugging code.
+ */
+int httpread_debug = 99;
+#else
+#define httpread_debug 0 /* eliminates even the debugging code */
+#endif
+
+
+/* control instance -- actual definition (opaque to application)
+ */
+struct httpread {
+ /* information from creation */
+ int sd; /* descriptor of TCP socket to read from */
+ void (*cb)(struct httpread *handle, void *cookie,
+ enum httpread_event e); /* call on event */
+ void *cookie; /* pass to callback */
+ int max_bytes; /* maximum file size else abort it */
+ int timeout_seconds; /* 0 or total duration timeout period */
+
+ /* dynamically used information follows */
+ int sd_registered; /* nonzero if we need to unregister socket */
+ int to_registered; /* nonzero if we need to unregister timeout */
+
+ int got_hdr; /* nonzero when header is finalized */
+ char hdr[HTTPREAD_HEADER_MAX_SIZE+1]; /* headers stored here */
+ int hdr_nbytes;
+
+ enum httpread_hdr_type hdr_type;
+ int version; /* 1 if we've seen 1.1 */
+ int reply_code; /* for type REPLY, e.g. 200 for HTTP/1.1 200 OK */
+ int got_content_length; /* true if we know content length for sure */
+ int content_length; /* body length, iff got_content_length */
+ int chunked; /* nonzero for chunked data */
+ char *uri;
+
+ int got_body; /* nonzero when body is finalized */
+ char *body;
+ int body_nbytes;
+ int body_alloc_nbytes; /* amount allocated */
+
+ int got_file; /* here when we are done */
+
+ /* The following apply if data is chunked: */
+ int in_chunk_data; /* 0=in/at header, 1=in the data or tail*/
+ int chunk_start; /* offset in body of chunk hdr or data */
+ int chunk_size; /* data of chunk (not hdr or ending CRLF)*/
+ int in_trailer; /* in header fields after data (chunked only)*/
+ enum trailer_state {
+ trailer_line_begin = 0,
+ trailer_empty_cr, /* empty line + CR */
+ trailer_nonempty,
+ trailer_nonempty_cr,
+ } trailer_state;
+};
+
+
+/* Check words for equality, where words consist of graphical characters
+ * delimited by whitespace
+ * Returns nonzero if "equal" doing case insensitive comparison.
+ */
+static int word_eq(char *s1, char *s2)
+{
+ int c1;
+ int c2;
+ int end1 = 0;
+ int end2 = 0;
+ for (;;) {
+ c1 = *s1++;
+ c2 = *s2++;
+ if (isalpha(c1) && isupper(c1))
+ c1 = tolower(c1);
+ if (isalpha(c2) && isupper(c2))
+ c2 = tolower(c2);
+ end1 = !isgraph(c1);
+ end2 = !isgraph(c2);
+ if (end1 || end2 || c1 != c2)
+ break;
+ }
+ return end1 && end2; /* reached end of both words? */
+}
+
+
+/* convert hex to binary
+ * Requires that c have been previously tested true with isxdigit().
+ */
+static int hex_value(int c)
+{
+ if (isdigit(c))
+ return c - '0';
+ if (islower(c))
+ return 10 + c - 'a';
+ return 10 + c - 'A';
+}
+
+
+static void httpread_timeout_handler(void *eloop_data, void *user_ctx);
+
+/* httpread_destroy -- if h is non-NULL, clean up
+ * This must eventually be called by the application following
+ * call of the application's callback and may be called
+ * earlier if desired.
+ */
+void httpread_destroy(struct httpread *h)
+{
+ if (httpread_debug >= 10)
+ wpa_printf(MSG_DEBUG, "ENTER httpread_destroy(%p)", h);
+ if (!h)
+ return;
+
+ if (h->to_registered)
+ eloop_cancel_timeout(httpread_timeout_handler, NULL, h);
+ h->to_registered = 0;
+ if (h->sd_registered)
+ eloop_unregister_sock(h->sd, EVENT_TYPE_READ);
+ h->sd_registered = 0;
+ os_free(h->body);
+ os_free(h->uri);
+ os_memset(h, 0, sizeof(*h)); /* aid debugging */
+ h->sd = -1; /* aid debugging */
+ os_free(h);
+}
+
+
+/* httpread_timeout_handler -- called on excessive total duration
+ */
+static void httpread_timeout_handler(void *eloop_data, void *user_ctx)
+{
+ struct httpread *h = user_ctx;
+ wpa_printf(MSG_DEBUG, "httpread timeout (%p)", h);
+ h->to_registered = 0; /* is self-cancelling */
+ (*h->cb)(h, h->cookie, HTTPREAD_EVENT_TIMEOUT);
+}
+
+
+/* Analyze options only so far as is needed to correctly obtain the file.
+ * The application can look at the raw header to find other options.
+ */
+static int httpread_hdr_option_analyze(
+ struct httpread *h,
+ char *hbp /* pointer to current line in header buffer */
+ )
+{
+ if (word_eq(hbp, "CONTENT-LENGTH:")) {
+ while (isgraph(*hbp))
+ hbp++;
+ while (*hbp == ' ' || *hbp == '\t')
+ hbp++;
+ if (!isdigit(*hbp))
+ return -1;
+ h->content_length = atol(hbp);
+ h->got_content_length = 1;
+ return 0;
+ }
+ if (word_eq(hbp, "TRANSFER_ENCODING:") ||
+ word_eq(hbp, "TRANSFER-ENCODING:")) {
+ while (isgraph(*hbp))
+ hbp++;
+ while (*hbp == ' ' || *hbp == '\t')
+ hbp++;
+ /* There should (?) be no encodings of interest
+ * other than chunked...
+ */
+ if (word_eq(hbp, "CHUNKED")) {
+ h->chunked = 1;
+ h->in_chunk_data = 0;
+ /* ignore possible ;<parameters> */
+ }
+ return 0;
+ }
+ /* skip anything we don't know, which is a lot */
+ return 0;
+}
+
+
+static int httpread_hdr_analyze(struct httpread *h)
+{
+ char *hbp = h->hdr; /* pointer into h->hdr */
+ int standard_first_line = 1;
+
+ /* First line is special */
+ h->hdr_type = HTTPREAD_HDR_TYPE_UNKNOWN;
+ if (!isgraph(*hbp))
+ goto bad;
+ if (os_strncmp(hbp, "HTTP/", 5) == 0) {
+ h->hdr_type = HTTPREAD_HDR_TYPE_REPLY;
+ standard_first_line = 0;
+ hbp += 5;
+ if (hbp[0] == '1' && hbp[1] == '.' &&
+ isdigit(hbp[2]) && hbp[2] != '0')
+ h->version = 1;
+ while (isgraph(*hbp))
+ hbp++;
+ while (*hbp == ' ' || *hbp == '\t')
+ hbp++;
+ if (!isdigit(*hbp))
+ goto bad;
+ h->reply_code = atol(hbp);
+ } else if (word_eq(hbp, "GET"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_GET;
+ else if (word_eq(hbp, "HEAD"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_HEAD;
+ else if (word_eq(hbp, "POST"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_POST;
+ else if (word_eq(hbp, "PUT"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_PUT;
+ else if (word_eq(hbp, "DELETE"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_DELETE;
+ else if (word_eq(hbp, "TRACE"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_TRACE;
+ else if (word_eq(hbp, "CONNECT"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_CONNECT;
+ else if (word_eq(hbp, "NOTIFY"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_NOTIFY;
+ else if (word_eq(hbp, "M-SEARCH"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_M_SEARCH;
+ else if (word_eq(hbp, "M-POST"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_M_POST;
+ else if (word_eq(hbp, "SUBSCRIBE"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_SUBSCRIBE;
+ else if (word_eq(hbp, "UNSUBSCRIBE"))
+ h->hdr_type = HTTPREAD_HDR_TYPE_UNSUBSCRIBE;
+ else {
+ }
+
+ if (standard_first_line) {
+ char *rawuri;
+ char *uri;
+ /* skip type */
+ while (isgraph(*hbp))
+ hbp++;
+ while (*hbp == ' ' || *hbp == '\t')
+ hbp++;
+ /* parse uri.
+ * Find length, allocate memory for translated
+ * copy, then translate by changing %<hex><hex>
+ * into represented value.
+ */
+ rawuri = hbp;
+ while (isgraph(*hbp))
+ hbp++;
+ h->uri = os_malloc((hbp - rawuri) + 1);
+ if (h->uri == NULL)
+ goto bad;
+ uri = h->uri;
+ while (rawuri < hbp) {
+ int c = *rawuri;
+ if (c == '%' &&
+ isxdigit(rawuri[1]) && isxdigit(rawuri[2])) {
+ *uri++ = (hex_value(rawuri[1]) << 4) |
+ hex_value(rawuri[2]);
+ rawuri += 3;
+ } else {
+ *uri++ = c;
+ rawuri++;
+ }
+ }
+ *uri = 0; /* null terminate */
+ while (isgraph(*hbp))
+ hbp++;
+ while (*hbp == ' ' || *hbp == '\t')
+ hbp++;
+ /* get version */
+ if (0 == strncmp(hbp, "HTTP/", 5)) {
+ hbp += 5;
+ if (hbp[0] == '1' && hbp[1] == '.' &&
+ isdigit(hbp[2]) && hbp[2] != '0')
+ h->version = 1;
+ }
+ }
+ /* skip rest of line */
+ while (*hbp)
+ if (*hbp++ == '\n')
+ break;
+
+ /* Remainder of lines are options, in any order;
+ * or empty line to terminate
+ */
+ for (;;) {
+ /* Empty line to terminate */
+ if (hbp[0] == '\n' ||
+ (hbp[0] == '\r' && hbp[1] == '\n'))
+ break;
+ if (!isgraph(*hbp))
+ goto bad;
+ if (httpread_hdr_option_analyze(h, hbp))
+ goto bad;
+ /* skip line */
+ while (*hbp)
+ if (*hbp++ == '\n')
+ break;
+ }
+
+ /* chunked overrides content-length always */
+ if (h->chunked)
+ h->got_content_length = 0;
+
+ /* For some types, we should not try to read a body
+ * This is in addition to the application determining
+ * that we should not read a body.
+ */
+ switch (h->hdr_type) {
+ case HTTPREAD_HDR_TYPE_REPLY:
+ /* Some codes can have a body and some not.
+ * For now, just assume that any other than 200
+ * do not...
+ */
+ if (h->reply_code != 200)
+ h->max_bytes = 0;
+ break;
+ case HTTPREAD_HDR_TYPE_GET:
+ case HTTPREAD_HDR_TYPE_HEAD:
+ /* in practice it appears that it is assumed
+ * that GETs have a body length of 0... ?
+ */
+ if (h->chunked == 0 && h->got_content_length == 0)
+ h->max_bytes = 0;
+ break;
+ case HTTPREAD_HDR_TYPE_POST:
+ case HTTPREAD_HDR_TYPE_PUT:
+ case HTTPREAD_HDR_TYPE_DELETE:
+ case HTTPREAD_HDR_TYPE_TRACE:
+ case HTTPREAD_HDR_TYPE_CONNECT:
+ case HTTPREAD_HDR_TYPE_NOTIFY:
+ case HTTPREAD_HDR_TYPE_M_SEARCH:
+ case HTTPREAD_HDR_TYPE_M_POST:
+ case HTTPREAD_HDR_TYPE_SUBSCRIBE:
+ case HTTPREAD_HDR_TYPE_UNSUBSCRIBE:
+ default:
+ break;
+ }
+
+ return 0;
+
+bad:
+ /* Error */
+ return -1;
+}
+
+
+/* httpread_read_handler -- called when socket ready to read
+ *
+ * Note: any extra data we read past end of transmitted file is ignored;
+ * if we were to support keeping connections open for multiple files then
+ * this would have to be addressed.
+ */
+static void httpread_read_handler(int sd, void *eloop_ctx, void *sock_ctx)
+{
+ struct httpread *h = sock_ctx;
+ int nread;
+ char *rbp; /* pointer into read buffer */
+ char *hbp; /* pointer into header buffer */
+ char *bbp; /* pointer into body buffer */
+ char readbuf[HTTPREAD_READBUF_SIZE]; /* temp use to read into */
+
+ if (httpread_debug >= 20)
+ wpa_printf(MSG_DEBUG, "ENTER httpread_read_handler(%p)", h);
+
+ /* read some at a time, then search for the interal
+ * boundaries between header and data and etc.
+ */
+ nread = read(h->sd, readbuf, sizeof(readbuf));
+ if (nread < 0)
+ goto bad;
+ if (nread == 0) {
+ /* end of transmission... this may be normal
+ * or may be an error... in some cases we can't
+ * tell which so we must assume it is normal then.
+ */
+ if (!h->got_hdr) {
+ /* Must at least have completed header */
+ wpa_printf(MSG_DEBUG, "httpread premature eof(%p)", h);
+ goto bad;
+ }
+ if (h->chunked || h->got_content_length) {
+ /* Premature EOF; e.g. dropped connection */
+ wpa_printf(MSG_DEBUG,
+ "httpread premature eof(%p) %d/%d",
+ h, h->body_nbytes,
+ h->content_length);
+ goto bad;
+ }
+ /* No explicit length, hopefully we have all the data
+ * although dropped connections can cause false
+ * end
+ */
+ if (httpread_debug >= 10)
+ wpa_printf(MSG_DEBUG, "httpread ok eof(%p)", h);
+ h->got_body = 1;
+ goto got_file;
+ }
+ rbp = readbuf;
+
+ /* Header consists of text lines (terminated by both CR and LF)
+ * and an empty line (CR LF only).
+ */
+ if (!h->got_hdr) {
+ hbp = h->hdr + h->hdr_nbytes;
+ /* add to headers until:
+ * -- we run out of data in read buffer
+ * -- or, we run out of header buffer room
+ * -- or, we get double CRLF in headers
+ */
+ for (;;) {
+ if (nread == 0)
+ goto get_more;
+ if (h->hdr_nbytes == HTTPREAD_HEADER_MAX_SIZE) {
+ goto bad;
+ }
+ *hbp++ = *rbp++;
+ nread--;
+ h->hdr_nbytes++;
+ if (h->hdr_nbytes >= 4 &&
+ hbp[-1] == '\n' &&
+ hbp[-2] == '\r' &&
+ hbp[-3] == '\n' &&
+ hbp[-4] == '\r' ) {
+ h->got_hdr = 1;
+ *hbp = 0; /* null terminate */
+ break;
+ }
+ }
+ /* here we've just finished reading the header */
+ if (httpread_hdr_analyze(h)) {
+ wpa_printf(MSG_DEBUG, "httpread bad hdr(%p)", h);
+ goto bad;
+ }
+ if (h->max_bytes == 0) {
+ if (httpread_debug >= 10)
+ wpa_printf(MSG_DEBUG,
+ "httpread no body hdr end(%p)", h);
+ goto got_file;
+ }
+ if (h->got_content_length && h->content_length == 0) {
+ if (httpread_debug >= 10)
+ wpa_printf(MSG_DEBUG,
+ "httpread zero content length(%p)",
+ h);
+ goto got_file;
+ }
+ }
+
+ /* Certain types of requests never have data and so
+ * must be specially recognized.
+ */
+ if (!os_strncasecmp(h->hdr, "SUBSCRIBE", 9) ||
+ !os_strncasecmp(h->hdr, "UNSUBSCRIBE", 11) ||
+ !os_strncasecmp(h->hdr, "HEAD", 4) ||
+ !os_strncasecmp(h->hdr, "GET", 3)) {
+ if (!h->got_body) {
+ if (httpread_debug >= 10)
+ wpa_printf(MSG_DEBUG,
+ "httpread NO BODY for sp. type");
+ }
+ h->got_body = 1;
+ goto got_file;
+ }
+
+ /* Data can be just plain binary data, or if "chunked"
+ * 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
+ * plus a null termination character
+ */
+ if (h->body_alloc_nbytes < (h->body_nbytes + nread + 1)) {
+ char *new_body;
+ int new_alloc_nbytes;
+
+ if (h->body_nbytes >= h->max_bytes)
+ goto bad;
+ new_alloc_nbytes = h->body_alloc_nbytes +
+ HTTPREAD_BODYBUF_DELTA;
+ /* For content-length case, the first time
+ * through we allocate the whole amount
+ * we need.
+ */
+ if (h->got_content_length &&
+ new_alloc_nbytes < (h->content_length + 1))
+ new_alloc_nbytes = h->content_length + 1;
+ if ((new_body = os_realloc(h->body, new_alloc_nbytes))
+ == NULL)
+ goto bad;
+
+ h->body = new_body;
+ h->body_alloc_nbytes = new_alloc_nbytes;
+ }
+ /* add bytes */
+ bbp = h->body + h->body_nbytes;
+ for (;;) {
+ int ncopy;
+ /* See if we need to stop */
+ if (h->chunked && h->in_chunk_data == 0) {
+ /* in chunk header */
+ char *cbp = h->body + h->chunk_start;
+ if (bbp-cbp >= 2 && bbp[-2] == '\r' &&
+ bbp[-1] == '\n') {
+ /* end of chunk hdr line */
+ /* hdr line consists solely
+ * of a hex numeral and CFLF
+ */
+ if (!isxdigit(*cbp))
+ goto bad;
+ h->chunk_size = strtoul(cbp, NULL, 16);
+ /* throw away chunk header
+ * so we have only real data
+ */
+ h->body_nbytes = h->chunk_start;
+ bbp = cbp;
+ if (h->chunk_size == 0) {
+ /* end of chunking */
+ /* trailer follows */
+ h->in_trailer = 1;
+ if (httpread_debug >= 20)
+ wpa_printf(
+ MSG_DEBUG,
+ "httpread end chunks(%p)", h);
+ break;
+ }
+ h->in_chunk_data = 1;
+ /* leave chunk_start alone */
+ }
+ } else if (h->chunked) {
+ /* in chunk data */
+ if ((h->body_nbytes - h->chunk_start) ==
+ (h->chunk_size + 2)) {
+ /* end of chunk reached,
+ * new chunk starts
+ */
+ /* check chunk ended w/ CRLF
+ * which we'll throw away
+ */
+ if (bbp[-1] == '\n' &&
+ bbp[-2] == '\r') {
+ } else
+ goto bad;
+ h->body_nbytes -= 2;
+ bbp -= 2;
+ h->chunk_start = h->body_nbytes;
+ h->in_chunk_data = 0;
+ h->chunk_size = 0; /* just in case */
+ }
+ } else if (h->got_content_length &&
+ h->body_nbytes >= h->content_length) {
+ h->got_body = 1;
+ if (httpread_debug >= 10)
+ wpa_printf(
+ MSG_DEBUG,
+ "httpread got content(%p)", h);
+ goto got_file;
+ }
+ if (nread <= 0)
+ break;
+ /* Now transfer. Optimize using memcpy where we can. */
+ if (h->chunked && h->in_chunk_data) {
+ /* copy up to remainder of chunk data
+ * plus the required CR+LF at end
+ */
+ ncopy = (h->chunk_start + h->chunk_size + 2) -
+ h->body_nbytes;
+ } else if (h->chunked) {
+ /*in chunk header -- don't optimize */
+ *bbp++ = *rbp++;
+ nread--;
+ h->body_nbytes++;
+ continue;
+ } else if (h->got_content_length) {
+ ncopy = h->content_length - h->body_nbytes;
+ } else {
+ ncopy = nread;
+ }
+ /* Note: should never be 0 */
+ if (ncopy > nread)
+ ncopy = nread;
+ os_memcpy(bbp, rbp, ncopy);
+ bbp += ncopy;
+ h->body_nbytes += ncopy;
+ rbp += ncopy;
+ nread -= ncopy;
+ } /* body copy loop */
+ } /* !got_body */
+ if (h->chunked && h->in_trailer) {
+ /* If "chunked" then there is always a trailer,
+ * consisting of zero or more non-empty lines
+ * ending with CR LF and then an empty line w/ CR LF.
+ * We do NOT support trailers except to skip them --
+ * this is supported (generally) by the http spec.
+ */
+ bbp = h->body + h->body_nbytes;
+ for (;;) {
+ int c;
+ if (nread <= 0)
+ break;
+ c = *rbp++;
+ nread--;
+ switch (h->trailer_state) {
+ case trailer_line_begin:
+ if (c == '\r')
+ h->trailer_state = trailer_empty_cr;
+ else
+ h->trailer_state = trailer_nonempty;
+ break;
+ case trailer_empty_cr:
+ /* end empty line */
+ if (c == '\n') {
+ h->trailer_state = trailer_line_begin;
+ h->in_trailer = 0;
+ if (httpread_debug >= 10)
+ wpa_printf(
+ MSG_DEBUG,
+ "httpread got content(%p)", h);
+ h->got_body = 1;
+ goto got_file;
+ }
+ h->trailer_state = trailer_nonempty;
+ break;
+ case trailer_nonempty:
+ if (c == '\r')
+ h->trailer_state = trailer_nonempty_cr;
+ break;
+ case trailer_nonempty_cr:
+ if (c == '\n')
+ h->trailer_state = trailer_line_begin;
+ else
+ h->trailer_state = trailer_nonempty;
+ break;
+ }
+ }
+ }
+ goto get_more;
+
+bad:
+ /* Error */
+ wpa_printf(MSG_DEBUG, "httpread read/parse failure (%p)", h);
+ (*h->cb)(h, h->cookie, HTTPREAD_EVENT_ERROR);
+ return;
+
+get_more:
+ return;
+
+got_file:
+ if (httpread_debug >= 10)
+ wpa_printf(MSG_DEBUG,
+ "httpread got file %d bytes type %d",
+ h->body_nbytes, h->hdr_type);
+ /* Null terminate for convenience of some applications */
+ if (h->body)
+ h->body[h->body_nbytes] = 0; /* null terminate */
+ h->got_file = 1;
+ /* Assume that we do NOT support keeping connection alive,
+ * and just in case somehow we don't get destroyed right away,
+ * unregister now.
+ */
+ if (h->sd_registered)
+ eloop_unregister_sock(h->sd, EVENT_TYPE_READ);
+ h->sd_registered = 0;
+ /* The application can destroy us whenever they feel like...
+ * cancel timeout.
+ */
+ if (h->to_registered)
+ eloop_cancel_timeout(httpread_timeout_handler, NULL, h);
+ h->to_registered = 0;
+ (*h->cb)(h, h->cookie, HTTPREAD_EVENT_FILE_READY);
+}
+
+
+/* httpread_create -- start a new reading session making use of eloop.
+ * The new instance will use the socket descriptor for reading (until
+ * it gets a file and not after) but will not close the socket, even
+ * when the instance is destroyed (the application must do that).
+ * Return NULL on error.
+ *
+ * Provided that httpread_create successfully returns a handle,
+ * the callback fnc is called to handle httpread_event events.
+ * The caller should do destroy on any errors or unknown events.
+ *
+ * Pass max_bytes == 0 to not read body at all (required for e.g.
+ * reply to HEAD request).
+ */
+struct httpread * httpread_create(
+ int sd, /* descriptor of TCP socket to read from */
+ void (*cb)(struct httpread *handle, void *cookie,
+ enum httpread_event e), /* call on event */
+ void *cookie, /* pass to callback */
+ int max_bytes, /* maximum body size else abort it */
+ int timeout_seconds /* 0; or total duration timeout period */
+ )
+{
+ struct httpread *h = NULL;
+
+ h = os_zalloc(sizeof(*h));
+ if (h == NULL)
+ goto fail;
+ h->sd = sd;
+ h->cb = cb;
+ h->cookie = cookie;
+ h->max_bytes = max_bytes;
+ h->timeout_seconds = timeout_seconds;
+
+ if (timeout_seconds > 0) {
+ if (eloop_register_timeout(timeout_seconds, 0,
+ httpread_timeout_handler,
+ NULL, h)) {
+ /* No way to recover (from malloc failure) */
+ goto fail;
+ }
+ h->to_registered = 1;
+ }
+ if (eloop_register_sock(sd, EVENT_TYPE_READ, httpread_read_handler,
+ NULL, h)) {
+ /* No way to recover (from malloc failure) */
+ goto fail;
+ }
+ h->sd_registered = 1;
+ return h;
+
+fail:
+
+ /* Error */
+ httpread_destroy(h);
+ return NULL;
+}
+
+
+/* httpread_hdr_type_get -- When file is ready, returns header type. */
+enum httpread_hdr_type httpread_hdr_type_get(struct httpread *h)
+{
+ return h->hdr_type;
+}
+
+
+/* httpread_uri_get -- When file is ready, uri_get returns (translated) URI
+ * or possibly NULL (which would be an error).
+ */
+char * httpread_uri_get(struct httpread *h)
+{
+ return h->uri;
+}
+
+
+/* httpread_reply_code_get -- When reply is ready, returns reply code */
+int httpread_reply_code_get(struct httpread *h)
+{
+ return h->reply_code;
+}
+
+
+/* httpread_length_get -- When file is ready, returns file length. */
+int httpread_length_get(struct httpread *h)
+{
+ return h->body_nbytes;
+}
+
+
+/* httpread_data_get -- When file is ready, returns file content
+ * with null byte appened.
+ * Might return NULL in some error condition.
+ */
+void * httpread_data_get(struct httpread *h)
+{
+ return h->body ? h->body : "";
+}
+
+
+/* httpread_hdr_get -- When file is ready, returns header content
+ * with null byte appended.
+ * Might return NULL in some error condition.
+ */
+char * httpread_hdr_get(struct httpread *h)
+{
+ return h->hdr;
+}
+
+
+/* httpread_hdr_line_get -- When file is ready, returns pointer
+ * to line within header content matching the given tag
+ * (after the tag itself and any spaces/tabs).
+ *
+ * The tag should end with a colon for reliable matching.
+ *
+ * If not found, returns NULL;
+ */
+char * httpread_hdr_line_get(struct httpread *h, const char *tag)
+{
+ int tag_len = os_strlen(tag);
+ char *hdr = h->hdr;
+ hdr = os_strchr(hdr, '\n');
+ if (hdr == NULL)
+ return NULL;
+ hdr++;
+ for (;;) {
+ if (!os_strncasecmp(hdr, tag, tag_len)) {
+ hdr += tag_len;
+ while (*hdr == ' ' || *hdr == '\t')
+ hdr++;
+ return hdr;
+ }
+ hdr = os_strchr(hdr, '\n');
+ if (hdr == NULL)
+ return NULL;
+ hdr++;
+ }
+}
diff --git a/src/wps/httpread.h b/src/wps/httpread.h
new file mode 100644
index 0000000..51aa214
--- /dev/null
+++ b/src/wps/httpread.h
@@ -0,0 +1,123 @@
+/*
+ * httpread - Manage reading file(s) from HTTP/TCP socket
+ * Author: Ted Merrill
+ * Copyright 2008 Atheros Communications
+ *
+ * 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.
+ */
+
+#ifndef HTTPREAD_H
+#define HTTPREAD_H
+
+/* event types (passed to callback) */
+enum httpread_event {
+ HTTPREAD_EVENT_FILE_READY = 1, /* including reply ready */
+ HTTPREAD_EVENT_TIMEOUT = 2,
+ HTTPREAD_EVENT_ERROR = 3 /* misc. error, esp malloc error */
+};
+
+
+/* header type detected
+ * available to callback via call to httpread_reply_code_get()
+ */
+enum httpread_hdr_type {
+ HTTPREAD_HDR_TYPE_UNKNOWN = 0, /* none of the following */
+ HTTPREAD_HDR_TYPE_REPLY = 1, /* hdr begins w/ HTTP/ */
+ HTTPREAD_HDR_TYPE_GET = 2, /* hdr begins with GET<sp> */
+ HTTPREAD_HDR_TYPE_HEAD = 3, /* hdr begins with HEAD<sp> */
+ HTTPREAD_HDR_TYPE_POST = 4, /* hdr begins with POST<sp> */
+ HTTPREAD_HDR_TYPE_PUT = 5, /* hdr begins with ... */
+ HTTPREAD_HDR_TYPE_DELETE = 6, /* hdr begins with ... */
+ HTTPREAD_HDR_TYPE_TRACE = 7, /* hdr begins with ... */
+ HTTPREAD_HDR_TYPE_CONNECT = 8, /* hdr begins with ... */
+ HTTPREAD_HDR_TYPE_NOTIFY = 9, /* hdr begins with ... */
+ HTTPREAD_HDR_TYPE_M_SEARCH = 10, /* hdr begins with ... */
+ HTTPREAD_HDR_TYPE_M_POST = 11, /* hdr begins with ... */
+ HTTPREAD_HDR_TYPE_SUBSCRIBE = 12, /* hdr begins with ... */
+ HTTPREAD_HDR_TYPE_UNSUBSCRIBE = 13, /* hdr begins with ... */
+
+ HTTPREAD_N_HDR_TYPES /* keep last */
+};
+
+
+/* control instance -- opaque struct declaration
+ */
+struct httpread;
+
+
+/* httpread_destroy -- if h is non-NULL, clean up
+ * This must eventually be called by the application following
+ * call of the application's callback and may be called
+ * earlier if desired.
+ */
+void httpread_destroy(struct httpread *h);
+
+/* httpread_create -- start a new reading session making use of eloop.
+ * The new instance will use the socket descriptor for reading (until
+ * it gets a file and not after) but will not close the socket, even
+ * when the instance is destroyed (the application must do that).
+ * Return NULL on error.
+ *
+ * Provided that httpread_create successfully returns a handle,
+ * the callback fnc is called to handle httpread_event events.
+ * The caller should do destroy on any errors or unknown events.
+ *
+ * Pass max_bytes == 0 to not read body at all (required for e.g.
+ * reply to HEAD request).
+ */
+struct httpread * httpread_create(
+ int sd, /* descriptor of TCP socket to read from */
+ void (*cb)(struct httpread *handle, void *cookie,
+ enum httpread_event e), /* call on event */
+ void *cookie, /* pass to callback */
+ int max_bytes, /* maximum file size else abort it */
+ int timeout_seconds /* 0; or total duration timeout period */
+ );
+
+/* httpread_hdr_type_get -- When file is ready, returns header type.
+ */
+enum httpread_hdr_type httpread_hdr_type_get(struct httpread *h);
+
+
+/* httpread_uri_get -- When file is ready, uri_get returns (translated) URI
+ * or possibly NULL (which would be an error).
+ */
+char *httpread_uri_get(struct httpread *h);
+
+/* httpread_reply_code_get -- When reply is ready, returns reply code */
+int httpread_reply_code_get(struct httpread *h);
+
+
+/* httpread_length_get -- When file is ready, returns file length. */
+int httpread_length_get(struct httpread *h);
+
+/* httpread_data_get -- When file is ready, returns file content
+ * with null byte appened.
+ * Might return NULL in some error condition.
+ */
+void * httpread_data_get(struct httpread *h);
+
+/* httpread_hdr_get -- When file is ready, returns header content
+ * with null byte appended.
+ * Might return NULL in some error condition.
+ */
+char * httpread_hdr_get(struct httpread *h);
+
+/* httpread_hdr_line_get -- When file is ready, returns pointer
+ * to line within header content matching the given tag
+ * (after the tag itself and any spaces/tabs).
+ *
+ * The tag should end with a colon for reliable matching.
+ *
+ * If not found, returns NULL;
+ */
+char * httpread_hdr_line_get(struct httpread *h, const char *tag);
+
+#endif /* HTTPREAD_H */
diff --git a/src/wps/ndef.c b/src/wps/ndef.c
new file mode 100644
index 0000000..9baec7f
--- /dev/null
+++ b/src/wps/ndef.c
@@ -0,0 +1,175 @@
+/*
+ * NDEF(NFC Data Exchange Format) routines for Wi-Fi Protected Setup
+ * Reference is "NFCForum-TS-NDEF_1.0 2006-07-24".
+ * Copyright (c) 2009, Masashi Honma <honma@ictec.co.jp>
+ *
+ * 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.
+ */
+
+#include "includes.h"
+#include "common.h"
+#include "wps/wps.h"
+#include "wps/wps_i.h"
+
+#define FLAG_MESSAGE_BEGIN (1 << 7)
+#define FLAG_MESSAGE_END (1 << 6)
+#define FLAG_CHUNK (1 << 5)
+#define FLAG_SHORT_RECORD (1 << 4)
+#define FLAG_ID_LENGTH_PRESENT (1 << 3)
+#define FLAG_TNF_RFC2046 (0x02)
+
+struct ndef_record {
+ u8 *type;
+ u8 *id;
+ u8 *payload;
+ u8 type_length;
+ u8 id_length;
+ u32 payload_length;
+ u32 total_length;
+};
+
+static char wifi_handover_type[] = "application/vnd.wfa.wsc";
+
+static int ndef_parse_record(u8 *data, u32 size, struct ndef_record *record)
+{
+ u8 *pos = data + 1;
+
+ if (size < 2)
+ return -1;
+ record->type_length = *pos++;
+ if (data[0] & FLAG_SHORT_RECORD) {
+ if (size < 3)
+ return -1;
+ record->payload_length = *pos++;
+ } else {
+ if (size < 6)
+ return -1;
+ record->payload_length = ntohl(*(u32 *)pos);
+ pos += sizeof(u32);
+ }
+
+ if (data[0] & FLAG_ID_LENGTH_PRESENT) {
+ if ((int) size < pos - data + 1)
+ return -1;
+ record->id_length = *pos++;
+ } else
+ record->id_length = 0;
+
+ record->type = record->type_length == 0 ? NULL : pos;
+ pos += record->type_length;
+
+ record->id = record->id_length == 0 ? NULL : pos;
+ pos += record->id_length;
+
+ record->payload = record->payload_length == 0 ? NULL : pos;
+ pos += record->payload_length;
+
+ record->total_length = pos - data;
+ if (record->total_length > size)
+ return -1;
+ return 0;
+}
+
+
+static struct wpabuf * ndef_parse_records(struct wpabuf *buf,
+ int (*filter)(struct ndef_record *))
+{
+ struct ndef_record record;
+ int len = wpabuf_len(buf);
+ u8 *data = wpabuf_mhead(buf);
+
+ while (len > 0) {
+ if (ndef_parse_record(data, len, &record) < 0) {
+ wpa_printf(MSG_ERROR, "NDEF : Failed to parse");
+ return NULL;
+ }
+ if (filter == NULL || filter(&record))
+ return wpabuf_alloc_copy(record.payload,
+ record.payload_length);
+ data += record.total_length;
+ len -= record.total_length;
+ }
+ wpa_printf(MSG_ERROR, "NDEF : Record not found");
+ return NULL;
+}
+
+
+static struct wpabuf * ndef_build_record(u8 flags, void *type,
+ u8 type_length, void *id,
+ u8 id_length, void *payload,
+ u32 payload_length)
+{
+ struct wpabuf *record;
+ size_t total_len;
+ int short_record;
+ u8 local_flag;
+
+ short_record = payload_length < 256 ? 1 : 0;
+
+ total_len = 2; /* flag + type length */
+ /* payload length */
+ total_len += short_record ? sizeof(u8) : sizeof(u32);
+ if (id_length > 0)
+ total_len += 1;
+ total_len += type_length + id_length + payload_length;
+ record = wpabuf_alloc(total_len);
+ if (record == NULL) {
+ wpa_printf(MSG_ERROR, "NDEF : Failed to allocate "
+ "record for build");
+ return NULL;
+ }
+
+ local_flag = flags;
+ if (id_length > 0)
+ local_flag |= FLAG_ID_LENGTH_PRESENT;
+ if (short_record)
+ local_flag |= FLAG_SHORT_RECORD;
+ wpabuf_put_u8(record, local_flag);
+
+ wpabuf_put_u8(record, type_length);
+
+ if (short_record)
+ wpabuf_put_u8(record, payload_length);
+ else
+ wpabuf_put_be32(record, payload_length);
+
+ if (id_length > 0)
+ wpabuf_put_u8(record, id_length);
+ wpabuf_put_data(record, type, type_length);
+ wpabuf_put_data(record, id, id_length);
+ wpabuf_put_data(record, payload, payload_length);
+ return record;
+}
+
+
+static int wifi_filter(struct ndef_record *record)
+{
+ if (record->type_length != os_strlen(wifi_handover_type))
+ return 0;
+ if (os_memcmp(record->type, wifi_handover_type,
+ os_strlen(wifi_handover_type)) != 0)
+ return 0;
+ return 1;
+}
+
+
+struct wpabuf * ndef_parse_wifi(struct wpabuf *buf)
+{
+ return ndef_parse_records(buf, wifi_filter);
+}
+
+
+struct wpabuf * ndef_build_wifi(struct wpabuf *buf)
+{
+ return ndef_build_record(FLAG_MESSAGE_BEGIN | FLAG_MESSAGE_END |
+ FLAG_TNF_RFC2046, wifi_handover_type,
+ os_strlen(wifi_handover_type), NULL, 0,
+ wpabuf_mhead(buf), wpabuf_len(buf));
+}
diff --git a/src/wps/upnp_xml.c b/src/wps/upnp_xml.c
new file mode 100644
index 0000000..b1b1e2b
--- /dev/null
+++ b/src/wps/upnp_xml.c
@@ -0,0 +1,252 @@
+/*
+ * UPnP XML helper routines
+ * Copyright (c) 2000-2003 Intel Corporation
+ * Copyright (c) 2006-2007 Sony Corporation
+ * Copyright (c) 2008-2009 Atheros Communications
+ * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
+ *
+ * See wps_upnp.c for more details on licensing and code history.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "base64.h"
+#include "http.h"
+#include "upnp_xml.h"
+
+
+/*
+ * XML parsing and formatting
+ *
+ * XML is a markup language based on unicode; usually (and in our case,
+ * always!) based on utf-8. utf-8 uses a variable number of bytes per
+ * character. utf-8 has the advantage that all non-ASCII unicode characters are
+ * represented by sequences of non-ascii (high bit set) bytes, whereas ASCII
+ * characters are single ascii bytes, thus we can use typical text processing.
+ *
+ * (One other interesting thing about utf-8 is that it is possible to look at
+ * any random byte and determine if it is the first byte of a character as
+ * versus a continuation byte).
+ *
+ * The base syntax of XML uses a few ASCII punctionation characters; any
+ * characters that would appear in the payload data are rewritten using
+ * sequences, e.g., & for ampersand(&) and < for left angle bracket (<).
+ * Five such escapes total (more can be defined but that does not apply to our
+ * case). Thus we can safely parse for angle brackets etc.
+ *
+ * XML describes tree structures of tagged data, with each element beginning
+ * with an opening tag <label> and ending with a closing tag </label> with
+ * matching label. (There is also a self-closing tag <label/> which is supposed
+ * to be equivalent to <label></label>, i.e., no payload, but we are unlikely
+ * to see it for our purpose).
+ *
+ * Actually the opening tags are a little more complicated because they can
+ * contain "attributes" after the label (delimited by ascii space or tab chars)
+ * of the form attribute_label="value" or attribute_label='value'; as it turns
+ * out we do not have to read any of these attributes, just ignore them.
+ *
+ * Labels are any sequence of chars other than space, tab, right angle bracket
+ * (and ?), but may have an inner structure of <namespace><colon><plain_label>.
+ * As it turns out, we can ignore the namespaces, in fact we can ignore the
+ * entire tree hierarchy, because the plain labels we are looking for will be
+ * unique (not in general, but for this application). We do however have to be
+ * careful to skip over the namespaces.
+ *
+ * In generating XML we have to be more careful, but that is easy because
+ * everything we do is pretty canned. The only real care to take is to escape
+ * any special chars in our payload.
+ */
+
+/**
+ * xml_next_tag - Advance to next tag
+ * @in: Input
+ * @out: OUT: start of tag just after '<'
+ * @out_tagname: OUT: start of name of tag, skipping namespace
+ * @end: OUT: one after tag
+ * Returns: 0 on success, 1 on failure
+ *
+ * A tag has form:
+ * <left angle bracket><...><right angle bracket>
+ * Within the angle brackets, there is an optional leading forward slash (which
+ * makes the tag an ending tag), then an optional leading label (followed by
+ * colon) and then the tag name itself.
+ *
+ * Note that angle brackets present in the original data must have been encoded
+ * as < and > so they will not trouble us.
+ */
+static int xml_next_tag(const char *in, const char **out,
+ const char **out_tagname, const char **end)
+{
+ while (*in && *in != '<')
+ in++;
+ if (*in != '<')
+ return 1;
+ *out = ++in;
+ if (*in == '/')
+ in++;
+ *out_tagname = in; /* maybe */
+ while (isalnum(*in) || *in == '-')
+ in++;
+ if (*in == ':')
+ *out_tagname = ++in;
+ while (*in && *in != '>')
+ in++;
+ if (*in != '>')
+ return 1;
+ *end = ++in;
+ return 0;
+}
+
+
+/* xml_data_encode -- format data for xml file, escaping special characters.
+ *
+ * Note that we assume we are using utf8 both as input and as output!
+ * In utf8, characters may be classed as follows:
+ * 0xxxxxxx(2) -- 1 byte ascii char
+ * 11xxxxxx(2) -- 1st byte of multi-byte char w/ unicode value >= 0x80
+ * 110xxxxx(2) -- 1st byte of 2 byte sequence (5 payload bits here)
+ * 1110xxxx(2) -- 1st byte of 3 byte sequence (4 payload bits here)
+ * 11110xxx(2) -- 1st byte of 4 byte sequence (3 payload bits here)
+ * 10xxxxxx(2) -- extension byte (6 payload bits per byte)
+ * Some values implied by the above are however illegal because they
+ * do not represent unicode chars or are not the shortest encoding.
+ * Actually, we can almost entirely ignore the above and just do
+ * text processing same as for ascii text.
+ *
+ * XML is written with arbitrary unicode characters, except that five
+ * characters have special meaning and so must be escaped where they
+ * appear in payload data... which we do here.
+ */
+void xml_data_encode(struct wpabuf *buf, const char *data, int len)
+{
+ int i;
+ for (i = 0; i < len; i++) {
+ u8 c = ((u8 *) data)[i];
+ if (c == '<') {
+ wpabuf_put_str(buf, "<");
+ continue;
+ }
+ if (c == '>') {
+ wpabuf_put_str(buf, ">");
+ continue;
+ }
+ if (c == '&') {
+ wpabuf_put_str(buf, "&");
+ continue;
+ }
+ if (c == '\'') {
+ wpabuf_put_str(buf, "'");
+ continue;
+ }
+ if (c == '"') {
+ wpabuf_put_str(buf, """);
+ continue;
+ }
+ /*
+ * We could try to represent control characters using the
+ * sequence: &#x; where x is replaced by a hex numeral, but not
+ * clear why we would do this.
+ */
+ wpabuf_put_u8(buf, c);
+ }
+}
+
+
+/* xml_add_tagged_data -- format tagged data as a new xml line.
+ *
+ * tag must not have any special chars.
+ * data may have special chars, which are escaped.
+ */
+void xml_add_tagged_data(struct wpabuf *buf, const char *tag, const char *data)
+{
+ wpabuf_printf(buf, "<%s>", tag);
+ xml_data_encode(buf, data, os_strlen(data));
+ wpabuf_printf(buf, "</%s>\n", tag);
+}
+
+
+/* A POST body looks something like (per upnp spec):
+ * <?xml version="1.0"?>
+ * <s:Envelope
+ * xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
+ * s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
+ * <s:Body>
+ * <u:actionName xmlns:u="urn:schemas-upnp-org:service:serviceType:v">
+ * <argumentName>in arg value</argumentName>
+ * other in args and their values go here, if any
+ * </u:actionName>
+ * </s:Body>
+ * </s:Envelope>
+ *
+ * where :
+ * s: might be some other namespace name followed by colon
+ * u: might be some other namespace name followed by colon
+ * actionName will be replaced according to action requested
+ * schema following actionName will be WFA scheme instead
+ * argumentName will be actual argument name
+ * (in arg value) will be actual argument value
+ */
+char * xml_get_first_item(const char *doc, const char *item)
+{
+ const char *match = item;
+ int match_len = os_strlen(item);
+ const char *tag, *tagname, *end;
+ char *value;
+
+ /*
+ * This is crude: ignore any possible tag name conflicts and go right
+ * to the first tag of this name. This should be ok for the limited
+ * domain of UPnP messages.
+ */
+ for (;;) {
+ if (xml_next_tag(doc, &tag, &tagname, &end))
+ return NULL;
+ doc = end;
+ if (!os_strncasecmp(tagname, match, match_len) &&
+ *tag != '/' &&
+ (tagname[match_len] == '>' ||
+ !isgraph(tagname[match_len]))) {
+ break;
+ }
+ }
+ end = doc;
+ while (*end && *end != '<')
+ end++;
+ value = os_zalloc(1 + (end - doc));
+ if (value == NULL)
+ return NULL;
+ os_memcpy(value, doc, end - doc);
+ return value;
+}
+
+
+struct wpabuf * xml_get_base64_item(const char *data, const char *name,
+ enum http_reply_code *ret)
+{
+ char *msg;
+ struct wpabuf *buf;
+ unsigned char *decoded;
+ size_t len;
+
+ msg = xml_get_first_item(data, name);
+ if (msg == NULL) {
+ *ret = UPNP_ARG_VALUE_INVALID;
+ return NULL;
+ }
+
+ decoded = base64_decode((unsigned char *) msg, os_strlen(msg), &len);
+ os_free(msg);
+ if (decoded == NULL) {
+ *ret = UPNP_OUT_OF_MEMORY;
+ return NULL;
+ }
+
+ buf = wpabuf_alloc_ext_data(decoded, len);
+ if (buf == NULL) {
+ os_free(decoded);
+ *ret = UPNP_OUT_OF_MEMORY;
+ return NULL;
+ }
+ return buf;
+}
diff --git a/src/wps/upnp_xml.h b/src/wps/upnp_xml.h
new file mode 100644
index 0000000..62dbe60
--- /dev/null
+++ b/src/wps/upnp_xml.h
@@ -0,0 +1,23 @@
+/*
+ * UPnP XML helper routines
+ * Copyright (c) 2000-2003 Intel Corporation
+ * Copyright (c) 2006-2007 Sony Corporation
+ * Copyright (c) 2008-2009 Atheros Communications
+ * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
+ *
+ * See wps_upnp.c for more details on licensing and code history.
+ */
+
+#ifndef UPNP_XML_H
+#define UPNP_XML_H
+
+#include "http.h"
+
+void xml_data_encode(struct wpabuf *buf, const char *data, int len);
+void xml_add_tagged_data(struct wpabuf *buf, const char *tag,
+ const char *data);
+char * xml_get_first_item(const char *doc, const char *item);
+struct wpabuf * xml_get_base64_item(const char *data, const char *name,
+ enum http_reply_code *ret);
+
+#endif /* UPNP_XML_H */
diff --git a/src/wps/wps.c b/src/wps/wps.c
new file mode 100644
index 0000000..7564d60
--- /dev/null
+++ b/src/wps/wps.c
@@ -0,0 +1,627 @@
+/*
+ * Wi-Fi Protected Setup
+ * Copyright (c) 2007-2009, 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.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "crypto/dh_group5.h"
+#include "common/ieee802_11_defs.h"
+#include "wps_i.h"
+#include "wps_dev_attr.h"
+
+
+#ifdef CONFIG_WPS_TESTING
+int wps_version_number = 0x20;
+int wps_testing_dummy_cred = 0;
+#endif /* CONFIG_WPS_TESTING */
+
+
+/**
+ * wps_init - Initialize WPS Registration protocol data
+ * @cfg: WPS configuration
+ * Returns: Pointer to allocated data or %NULL on failure
+ *
+ * This function is used to initialize WPS data for a registration protocol
+ * instance (i.e., each run of registration protocol as a Registrar of
+ * Enrollee. The caller is responsible for freeing this data after the
+ * registration run has been completed by calling wps_deinit().
+ */
+struct wps_data * wps_init(const struct wps_config *cfg)
+{
+ struct wps_data *data = os_zalloc(sizeof(*data));
+ if (data == NULL)
+ return NULL;
+ data->wps = cfg->wps;
+ data->registrar = cfg->registrar;
+ if (cfg->registrar) {
+ os_memcpy(data->uuid_r, cfg->wps->uuid, WPS_UUID_LEN);
+ } else {
+ os_memcpy(data->mac_addr_e, cfg->wps->dev.mac_addr, ETH_ALEN);
+ os_memcpy(data->uuid_e, cfg->wps->uuid, WPS_UUID_LEN);
+ }
+ if (cfg->pin) {
+ data->dev_pw_id = data->wps->oob_dev_pw_id == 0 ?
+ cfg->dev_pw_id : data->wps->oob_dev_pw_id;
+ data->dev_password = os_malloc(cfg->pin_len);
+ if (data->dev_password == NULL) {
+ os_free(data);
+ return NULL;
+ }
+ os_memcpy(data->dev_password, cfg->pin, cfg->pin_len);
+ data->dev_password_len = cfg->pin_len;
+ }
+
+ data->pbc = cfg->pbc;
+ if (cfg->pbc) {
+ /* Use special PIN '00000000' for PBC */
+ data->dev_pw_id = DEV_PW_PUSHBUTTON;
+ os_free(data->dev_password);
+ data->dev_password = os_malloc(8);
+ if (data->dev_password == NULL) {
+ os_free(data);
+ return NULL;
+ }
+ os_memset(data->dev_password, '0', 8);
+ data->dev_password_len = 8;
+ }
+
+ data->state = data->registrar ? RECV_M1 : SEND_M1;
+
+ if (cfg->assoc_wps_ie) {
+ struct wps_parse_attr attr;
+ wpa_hexdump_buf(MSG_DEBUG, "WPS: WPS IE from (Re)AssocReq",
+ cfg->assoc_wps_ie);
+ if (wps_parse_msg(cfg->assoc_wps_ie, &attr) < 0) {
+ wpa_printf(MSG_DEBUG, "WPS: Failed to parse WPS IE "
+ "from (Re)AssocReq");
+ } else if (attr.request_type == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Request Type attribute "
+ "in (Re)AssocReq WPS IE");
+ } else {
+ wpa_printf(MSG_DEBUG, "WPS: Request Type (from WPS IE "
+ "in (Re)AssocReq WPS IE): %d",
+ *attr.request_type);
+ data->request_type = *attr.request_type;
+ }
+ }
+
+ if (cfg->new_ap_settings) {
+ data->new_ap_settings =
+ os_malloc(sizeof(*data->new_ap_settings));
+ if (data->new_ap_settings == NULL) {
+ os_free(data);
+ return NULL;
+ }
+ os_memcpy(data->new_ap_settings, cfg->new_ap_settings,
+ sizeof(*data->new_ap_settings));
+ }
+
+ if (cfg->peer_addr)
+ os_memcpy(data->peer_dev.mac_addr, cfg->peer_addr, ETH_ALEN);
+ if (cfg->p2p_dev_addr)
+ os_memcpy(data->p2p_dev_addr, cfg->p2p_dev_addr, ETH_ALEN);
+
+ data->use_psk_key = cfg->use_psk_key;
+
+ return data;
+}
+
+
+/**
+ * wps_deinit - Deinitialize WPS Registration protocol data
+ * @data: WPS Registration protocol data from wps_init()
+ */
+void wps_deinit(struct wps_data *data)
+{
+ if (data->wps_pin_revealed) {
+ wpa_printf(MSG_DEBUG, "WPS: Full PIN information revealed and "
+ "negotiation failed");
+ if (data->registrar)
+ wps_registrar_invalidate_pin(data->wps->registrar,
+ data->uuid_e);
+ } else if (data->registrar)
+ wps_registrar_unlock_pin(data->wps->registrar, data->uuid_e);
+
+ wpabuf_free(data->dh_privkey);
+ wpabuf_free(data->dh_pubkey_e);
+ wpabuf_free(data->dh_pubkey_r);
+ wpabuf_free(data->last_msg);
+ os_free(data->dev_password);
+ os_free(data->new_psk);
+ wps_device_data_free(&data->peer_dev);
+ os_free(data->new_ap_settings);
+ dh5_free(data->dh_ctx);
+ os_free(data);
+}
+
+
+/**
+ * wps_process_msg - Process a WPS message
+ * @wps: WPS Registration protocol data from wps_init()
+ * @op_code: Message OP Code
+ * @msg: Message data
+ * Returns: Processing result
+ *
+ * This function is used to process WPS messages with OP Codes WSC_ACK,
+ * WSC_NACK, WSC_MSG, and WSC_Done. The caller (e.g., EAP server/peer) is
+ * responsible for reassembling the messages before calling this function.
+ * Response to this message is built by calling wps_get_msg().
+ */
+enum wps_process_res wps_process_msg(struct wps_data *wps,
+ enum wsc_op_code op_code,
+ const struct wpabuf *msg)
+{
+ if (wps->registrar)
+ return wps_registrar_process_msg(wps, op_code, msg);
+ else
+ return wps_enrollee_process_msg(wps, op_code, msg);
+}
+
+
+/**
+ * wps_get_msg - Build a WPS message
+ * @wps: WPS Registration protocol data from wps_init()
+ * @op_code: Buffer for returning message OP Code
+ * Returns: The generated WPS message or %NULL on failure
+ *
+ * This function is used to build a response to a message processed by calling
+ * wps_process_msg(). The caller is responsible for freeing the buffer.
+ */
+struct wpabuf * wps_get_msg(struct wps_data *wps, enum wsc_op_code *op_code)
+{
+ if (wps->registrar)
+ return wps_registrar_get_msg(wps, op_code);
+ else
+ return wps_enrollee_get_msg(wps, op_code);
+}
+
+
+/**
+ * wps_is_selected_pbc_registrar - Check whether WPS IE indicates active PBC
+ * @msg: WPS IE contents from Beacon or Probe Response frame
+ * Returns: 1 if PBC Registrar is active, 0 if not
+ */
+int wps_is_selected_pbc_registrar(const struct wpabuf *msg)
+{
+ struct wps_parse_attr attr;
+
+ /*
+ * In theory, this could also verify that attr.sel_reg_config_methods
+ * includes WPS_CONFIG_PUSHBUTTON, but some deployed AP implementations
+ * do not set Selected Registrar Config Methods attribute properly, so
+ * it is safer to just use Device Password ID here.
+ */
+
+ if (wps_parse_msg(msg, &attr) < 0 ||
+ !attr.selected_registrar || *attr.selected_registrar == 0 ||
+ !attr.dev_password_id ||
+ WPA_GET_BE16(attr.dev_password_id) != DEV_PW_PUSHBUTTON)
+ return 0;
+
+#ifdef CONFIG_WPS_STRICT
+ if (!attr.sel_reg_config_methods ||
+ !(WPA_GET_BE16(attr.sel_reg_config_methods) &
+ WPS_CONFIG_PUSHBUTTON))
+ return 0;
+#endif /* CONFIG_WPS_STRICT */
+
+ return 1;
+}
+
+
+static int is_selected_pin_registrar(struct wps_parse_attr *attr)
+{
+ /*
+ * In theory, this could also verify that attr.sel_reg_config_methods
+ * includes WPS_CONFIG_LABEL, WPS_CONFIG_DISPLAY, or WPS_CONFIG_KEYPAD,
+ * but some deployed AP implementations do not set Selected Registrar
+ * Config Methods attribute properly, so it is safer to just use
+ * Device Password ID here.
+ */
+
+ if (!attr->selected_registrar || *attr->selected_registrar == 0)
+ return 0;
+
+ if (attr->dev_password_id != NULL &&
+ WPA_GET_BE16(attr->dev_password_id) == DEV_PW_PUSHBUTTON)
+ return 0;
+
+#ifdef CONFIG_WPS_STRICT
+ if (!attr->sel_reg_config_methods ||
+ !(WPA_GET_BE16(attr->sel_reg_config_methods) &
+ (WPS_CONFIG_LABEL | WPS_CONFIG_DISPLAY | WPS_CONFIG_KEYPAD)))
+ return 0;
+#endif /* CONFIG_WPS_STRICT */
+
+ return 1;
+}
+
+
+/**
+ * wps_is_selected_pin_registrar - Check whether WPS IE indicates active PIN
+ * @msg: WPS IE contents from Beacon or Probe Response frame
+ * Returns: 1 if PIN Registrar is active, 0 if not
+ */
+int wps_is_selected_pin_registrar(const struct wpabuf *msg)
+{
+ struct wps_parse_attr attr;
+
+ if (wps_parse_msg(msg, &attr) < 0)
+ return 0;
+
+ return is_selected_pin_registrar(&attr);
+}
+
+
+/**
+ * wps_is_addr_authorized - Check whether WPS IE authorizes MAC address
+ * @msg: WPS IE contents from Beacon or Probe Response frame
+ * @addr: MAC address to search for
+ * @ver1_compat: Whether to use version 1 compatibility mode
+ * Returns: 1 if address is authorized, 0 if not
+ */
+int wps_is_addr_authorized(const struct wpabuf *msg, const u8 *addr,
+ int ver1_compat)
+{
+ struct wps_parse_attr attr;
+ unsigned int i;
+ const u8 *pos;
+ const u8 bcast[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+
+ if (wps_parse_msg(msg, &attr) < 0)
+ return 0;
+
+ if (!attr.version2 && ver1_compat) {
+ /*
+ * Version 1.0 AP - AuthorizedMACs not used, so revert back to
+ * old mechanism of using SelectedRegistrar.
+ */
+ return is_selected_pin_registrar(&attr);
+ }
+
+ if (!attr.authorized_macs)
+ return 0;
+
+ pos = attr.authorized_macs;
+ for (i = 0; i < attr.authorized_macs_len / ETH_ALEN; i++) {
+ if (os_memcmp(pos, addr, ETH_ALEN) == 0 ||
+ os_memcmp(pos, bcast, ETH_ALEN) == 0)
+ return 1;
+ pos += ETH_ALEN;
+ }
+
+ return 0;
+}
+
+
+/**
+ * wps_ap_priority_compar - Prioritize WPS IE from two APs
+ * @wps_a: WPS IE contents from Beacon or Probe Response frame
+ * @wps_b: WPS IE contents from Beacon or Probe Response frame
+ * Returns: 1 if wps_b is considered more likely selection for WPS
+ * provisioning, -1 if wps_a is considered more like, or 0 if no preference
+ */
+int wps_ap_priority_compar(const struct wpabuf *wps_a,
+ const struct wpabuf *wps_b)
+{
+ struct wps_parse_attr attr_a, attr_b;
+ int sel_a, sel_b;
+
+ if (wps_a == NULL || wps_parse_msg(wps_a, &attr_a) < 0)
+ return 1;
+ if (wps_b == NULL || wps_parse_msg(wps_b, &attr_b) < 0)
+ return -1;
+
+ sel_a = attr_a.selected_registrar && *attr_a.selected_registrar != 0;
+ sel_b = attr_b.selected_registrar && *attr_b.selected_registrar != 0;
+
+ if (sel_a && !sel_b)
+ return -1;
+ if (!sel_a && sel_b)
+ return 1;
+
+ return 0;
+}
+
+
+/**
+ * wps_get_uuid_e - Get UUID-E from WPS IE
+ * @msg: WPS IE contents from Beacon or Probe Response frame
+ * Returns: Pointer to UUID-E or %NULL if not included
+ *
+ * The returned pointer is to the msg contents and it remains valid only as
+ * long as the msg buffer is valid.
+ */
+const u8 * wps_get_uuid_e(const struct wpabuf *msg)
+{
+ struct wps_parse_attr attr;
+
+ if (wps_parse_msg(msg, &attr) < 0)
+ return NULL;
+ return attr.uuid_e;
+}
+
+
+/**
+ * wps_build_assoc_req_ie - Build WPS IE for (Re)Association Request
+ * @req_type: Value for Request Type attribute
+ * Returns: WPS IE or %NULL on failure
+ *
+ * The caller is responsible for freeing the buffer.
+ */
+struct wpabuf * wps_build_assoc_req_ie(enum wps_request_type req_type)
+{
+ struct wpabuf *ie;
+ u8 *len;
+
+ wpa_printf(MSG_DEBUG, "WPS: Building WPS IE for (Re)Association "
+ "Request");
+ ie = wpabuf_alloc(100);
+ if (ie == NULL)
+ return NULL;
+
+ wpabuf_put_u8(ie, WLAN_EID_VENDOR_SPECIFIC);
+ len = wpabuf_put(ie, 1);
+ wpabuf_put_be32(ie, WPS_DEV_OUI_WFA);
+
+ if (wps_build_version(ie) ||
+ wps_build_req_type(ie, req_type) ||
+ wps_build_wfa_ext(ie, 0, NULL, 0)) {
+ wpabuf_free(ie);
+ return NULL;
+ }
+
+ *len = wpabuf_len(ie) - 2;
+
+ return ie;
+}
+
+
+/**
+ * wps_build_assoc_resp_ie - Build WPS IE for (Re)Association Response
+ * Returns: WPS IE or %NULL on failure
+ *
+ * The caller is responsible for freeing the buffer.
+ */
+struct wpabuf * wps_build_assoc_resp_ie(void)
+{
+ struct wpabuf *ie;
+ u8 *len;
+
+ wpa_printf(MSG_DEBUG, "WPS: Building WPS IE for (Re)Association "
+ "Response");
+ ie = wpabuf_alloc(100);
+ if (ie == NULL)
+ return NULL;
+
+ wpabuf_put_u8(ie, WLAN_EID_VENDOR_SPECIFIC);
+ len = wpabuf_put(ie, 1);
+ wpabuf_put_be32(ie, WPS_DEV_OUI_WFA);
+
+ if (wps_build_version(ie) ||
+ wps_build_resp_type(ie, WPS_RESP_AP) ||
+ wps_build_wfa_ext(ie, 0, NULL, 0)) {
+ wpabuf_free(ie);
+ return NULL;
+ }
+
+ *len = wpabuf_len(ie) - 2;
+
+ return ie;
+}
+
+
+/**
+ * wps_build_probe_req_ie - Build WPS IE for Probe Request
+ * @pbc: Whether searching for PBC mode APs
+ * @dev: Device attributes
+ * @uuid: Own UUID
+ * @req_type: Value for Request Type attribute
+ * @num_req_dev_types: Number of requested device types
+ * @req_dev_types: Requested device types (8 * num_req_dev_types octets) or
+ * %NULL if none
+ * Returns: WPS IE or %NULL on failure
+ *
+ * The caller is responsible for freeing the buffer.
+ */
+struct wpabuf * wps_build_probe_req_ie(int pbc, struct wps_device_data *dev,
+ const u8 *uuid,
+ enum wps_request_type req_type,
+ unsigned int num_req_dev_types,
+ const u8 *req_dev_types)
+{
+ struct wpabuf *ie;
+ u16 methods = 0;
+
+ wpa_printf(MSG_DEBUG, "WPS: Building WPS IE for Probe Request");
+
+ ie = wpabuf_alloc(500);
+ if (ie == NULL)
+ return NULL;
+
+ methods |= WPS_CONFIG_PUSHBUTTON;
+#ifdef CONFIG_WPS2
+ /*
+ * TODO: Should figure out whether this device has a physical or
+ * virtual pushbutton.
+ */
+ methods |= WPS_CONFIG_VIRT_PUSHBUTTON;
+#endif /* CONFIG_WPS2 */
+
+ /*
+ * TODO: Should figure out whether this Probe Request was triggered
+ * using physical or virtual display. Also, if the device has a PIN on
+ * a label, that should be indicated here.
+ */
+ methods |= WPS_CONFIG_DISPLAY |
+#ifdef CONFIG_WPS2
+ WPS_CONFIG_VIRT_DISPLAY |
+#endif /* CONFIG_WPS2 */
+ WPS_CONFIG_KEYPAD;
+#ifdef CONFIG_WPS_UFD
+ methods |= WPS_CONFIG_USBA;
+#endif /* CONFIG_WPS_UFD */
+#ifdef CONFIG_WPS_NFC
+ methods |= WPS_CONFIG_NFC_INTERFACE;
+#endif /* CONFIG_WPS_NFC */
+
+ if (wps_build_version(ie) ||
+ wps_build_req_type(ie, req_type) ||
+ wps_build_config_methods(ie, methods) ||
+ wps_build_uuid_e(ie, uuid) ||
+ wps_build_primary_dev_type(dev, ie) ||
+ wps_build_rf_bands(dev, ie) ||
+ wps_build_assoc_state(NULL, ie) ||
+ wps_build_config_error(ie, WPS_CFG_NO_ERROR) ||
+ wps_build_dev_password_id(ie, pbc ? DEV_PW_PUSHBUTTON :
+ DEV_PW_DEFAULT) ||
+#ifdef CONFIG_WPS2
+ wps_build_manufacturer(dev, ie) ||
+ wps_build_model_name(dev, ie) ||
+ wps_build_model_number(dev, ie) ||
+ wps_build_dev_name(dev, ie) ||
+ wps_build_wfa_ext(ie, req_type == WPS_REQ_ENROLLEE, NULL, 0) ||
+#endif /* CONFIG_WPS2 */
+ wps_build_req_dev_type(dev, ie, num_req_dev_types, req_dev_types)
+ ||
+ wps_build_secondary_dev_type(dev, ie)
+ ) {
+ wpabuf_free(ie);
+ return NULL;
+ }
+
+#ifndef CONFIG_WPS2
+ if (dev->p2p && wps_build_dev_name(dev, ie)) {
+ wpabuf_free(ie);
+ return NULL;
+ }
+#endif /* CONFIG_WPS2 */
+
+ return wps_ie_encapsulate(ie);
+}
+
+
+void wps_free_pending_msgs(struct upnp_pending_message *msgs)
+{
+ struct upnp_pending_message *p, *prev;
+ p = msgs;
+ while (p) {
+ prev = p;
+ p = p->next;
+ wpabuf_free(prev->msg);
+ os_free(prev);
+ }
+}
+
+
+int wps_attr_text(struct wpabuf *data, char *buf, char *end)
+{
+ struct wps_parse_attr attr;
+ char *pos = buf;
+ int ret;
+
+ if (wps_parse_msg(data, &attr) < 0)
+ return -1;
+
+ if (attr.wps_state) {
+ if (*attr.wps_state == WPS_STATE_NOT_CONFIGURED)
+ ret = os_snprintf(pos, end - pos,
+ "wps_state=unconfigured\n");
+ else if (*attr.wps_state == WPS_STATE_CONFIGURED)
+ ret = os_snprintf(pos, end - pos,
+ "wps_state=configured\n");
+ else
+ ret = 0;
+ if (ret < 0 || ret >= end - pos)
+ return pos - buf;
+ pos += ret;
+ }
+
+ if (attr.ap_setup_locked && *attr.ap_setup_locked) {
+ ret = os_snprintf(pos, end - pos,
+ "wps_ap_setup_locked=1\n");
+ if (ret < 0 || ret >= end - pos)
+ return pos - buf;
+ pos += ret;
+ }
+
+ if (attr.selected_registrar && *attr.selected_registrar) {
+ ret = os_snprintf(pos, end - pos,
+ "wps_selected_registrar=1\n");
+ if (ret < 0 || ret >= end - pos)
+ return pos - buf;
+ pos += ret;
+ }
+
+ if (attr.dev_password_id) {
+ ret = os_snprintf(pos, end - pos,
+ "wps_device_password_id=%u\n",
+ WPA_GET_BE16(attr.dev_password_id));
+ if (ret < 0 || ret >= end - pos)
+ return pos - buf;
+ pos += ret;
+ }
+
+ if (attr.sel_reg_config_methods) {
+ ret = os_snprintf(pos, end - pos,
+ "wps_selected_registrar_config_methods="
+ "0x%04x\n",
+ WPA_GET_BE16(attr.sel_reg_config_methods));
+ if (ret < 0 || ret >= end - pos)
+ return pos - buf;
+ pos += ret;
+ }
+
+ if (attr.primary_dev_type) {
+ char devtype[WPS_DEV_TYPE_BUFSIZE];
+ ret = os_snprintf(pos, end - pos,
+ "wps_primary_device_type=%s\n",
+ wps_dev_type_bin2str(attr.primary_dev_type,
+ devtype,
+ sizeof(devtype)));
+ if (ret < 0 || ret >= end - pos)
+ return pos - buf;
+ pos += ret;
+ }
+
+ if (attr.dev_name) {
+ char *str = os_malloc(attr.dev_name_len + 1);
+ size_t i;
+ if (str == NULL)
+ return pos - buf;
+ for (i = 0; i < attr.dev_name_len; i++) {
+ if (attr.dev_name[i] < 32)
+ str[i] = '_';
+ else
+ str[i] = attr.dev_name[i];
+ }
+ str[i] = '\0';
+ ret = os_snprintf(pos, end - pos, "wps_device_name=%s\n", str);
+ os_free(str);
+ if (ret < 0 || ret >= end - pos)
+ return pos - buf;
+ pos += ret;
+ }
+
+ if (attr.config_methods) {
+ ret = os_snprintf(pos, end - pos,
+ "wps_config_methods=0x%04x\n",
+ WPA_GET_BE16(attr.config_methods));
+ if (ret < 0 || ret >= end - pos)
+ return pos - buf;
+ pos += ret;
+ }
+
+ return pos - buf;
+}
diff --git a/src/wps/wps.h b/src/wps/wps.h
new file mode 100644
index 0000000..918273d
--- /dev/null
+++ b/src/wps/wps.h
@@ -0,0 +1,964 @@
+/*
+ * Wi-Fi Protected Setup
+ * Copyright (c) 2007-2009, 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.
+ */
+
+#ifndef WPS_H
+#define WPS_H
+
+#include "wps_defs.h"
+
+/**
+ * enum wsc_op_code - EAP-WSC OP-Code values
+ */
+enum wsc_op_code {
+ WSC_UPnP = 0 /* No OP Code in UPnP transport */,
+ WSC_Start = 0x01,
+ WSC_ACK = 0x02,
+ WSC_NACK = 0x03,
+ WSC_MSG = 0x04,
+ WSC_Done = 0x05,
+ WSC_FRAG_ACK = 0x06
+};
+
+struct wps_registrar;
+struct upnp_wps_device_sm;
+struct wps_er;
+
+/**
+ * struct wps_credential - WPS Credential
+ * @ssid: SSID
+ * @ssid_len: Length of SSID
+ * @auth_type: Authentication Type (WPS_AUTH_OPEN, .. flags)
+ * @encr_type: Encryption Type (WPS_ENCR_NONE, .. flags)
+ * @key_idx: Key index
+ * @key: Key
+ * @key_len: Key length in octets
+ * @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
+ */
+struct wps_credential {
+ u8 ssid[32];
+ size_t ssid_len;
+ u16 auth_type;
+ u16 encr_type;
+ u8 key_idx;
+ u8 key[64];
+ size_t key_len;
+ u8 mac_addr[ETH_ALEN];
+ const u8 *cred_attr;
+ size_t cred_attr_len;
+};
+
+#define WPS_DEV_TYPE_LEN 8
+#define WPS_DEV_TYPE_BUFSIZE 21
+#define WPS_SEC_DEV_TYPE_MAX_LEN 128
+/* maximum number of advertised WPS vendor extension attributes */
+#define MAX_WPS_VENDOR_EXTENSIONS 10
+/* maximum size of WPS Vendor extension attribute */
+#define WPS_MAX_VENDOR_EXT_LEN 1024
+/* maximum number of parsed WPS vendor extension attributes */
+#define MAX_WPS_PARSE_VENDOR_EXT 10
+
+/**
+ * struct wps_device_data - WPS Device Data
+ * @mac_addr: Device MAC address
+ * @device_name: Device Name (0..32 octets encoded in UTF-8)
+ * @manufacturer: Manufacturer (0..64 octets encoded in UTF-8)
+ * @model_name: Model Name (0..32 octets encoded in UTF-8)
+ * @model_number: Model Number (0..32 octets encoded in UTF-8)
+ * @serial_number: Serial Number (0..32 octets encoded in UTF-8)
+ * @pri_dev_type: Primary Device Type
+ * @sec_dev_type: Array of secondary device types
+ * @num_sec_dev_type: Number of secondary device types
+ * @os_version: OS Version
+ * @rf_bands: RF bands (WPS_RF_24GHZ, WPS_RF_50GHZ flags)
+ * @p2p: Whether the device is a P2P device
+ */
+struct wps_device_data {
+ u8 mac_addr[ETH_ALEN];
+ char *device_name;
+ char *manufacturer;
+ char *model_name;
+ char *model_number;
+ char *serial_number;
+ u8 pri_dev_type[WPS_DEV_TYPE_LEN];
+#define WPS_SEC_DEVICE_TYPES 5
+ u8 sec_dev_type[WPS_SEC_DEVICE_TYPES][WPS_DEV_TYPE_LEN];
+ u8 num_sec_dev_types;
+ u32 os_version;
+ u8 rf_bands;
+ struct wpabuf *vendor_ext[MAX_WPS_VENDOR_EXTENSIONS];
+
+ int p2p;
+};
+
+struct oob_conf_data {
+ enum {
+ OOB_METHOD_UNKNOWN = 0,
+ OOB_METHOD_DEV_PWD_E,
+ OOB_METHOD_DEV_PWD_R,
+ OOB_METHOD_CRED,
+ } oob_method;
+ struct wpabuf *dev_password;
+ struct wpabuf *pubkey_hash;
+};
+
+/**
+ * struct wps_config - WPS configuration for a single registration protocol run
+ */
+struct wps_config {
+ /**
+ * wps - Pointer to long term WPS context
+ */
+ struct wps_context *wps;
+
+ /**
+ * registrar - Whether this end is a Registrar
+ */
+ int registrar;
+
+ /**
+ * pin - Enrollee Device Password (%NULL for Registrar or PBC)
+ */
+ const u8 *pin;
+
+ /**
+ * pin_len - Length on pin in octets
+ */
+ size_t pin_len;
+
+ /**
+ * pbc - Whether this is protocol run uses PBC
+ */
+ int pbc;
+
+ /**
+ * assoc_wps_ie: (Re)AssocReq WPS IE (in AP; %NULL if not AP)
+ */
+ const struct wpabuf *assoc_wps_ie;
+
+ /**
+ * new_ap_settings - New AP settings (%NULL if not used)
+ *
+ * This parameter provides new AP settings when using a wireless
+ * stations as a Registrar to configure the AP. %NULL means that AP
+ * will not be reconfigured, i.e., the station will only learn the
+ * current AP settings by using AP PIN.
+ */
+ const struct wps_credential *new_ap_settings;
+
+ /**
+ * peer_addr: MAC address of the peer in AP; %NULL if not AP
+ */
+ const u8 *peer_addr;
+
+ /**
+ * use_psk_key - Use PSK format key in Credential
+ *
+ * Force PSK format to be used instead of ASCII passphrase when
+ * building Credential for an Enrollee. The PSK value is set in
+ * struct wpa_context::psk.
+ */
+ int use_psk_key;
+
+ /**
+ * dev_pw_id - Device Password ID for Enrollee when PIN is used
+ */
+ u16 dev_pw_id;
+
+ /**
+ * p2p_dev_addr - P2P Device Address from (Re)Association Request
+ *
+ * On AP/GO, this is set to the P2P Device Address of the associating
+ * P2P client if a P2P IE is included in the (Re)Association Request
+ * frame and the P2P Device Address is included. Otherwise, this is set
+ * to %NULL to indicate the station does not have a P2P Device Address.
+ */
+ const u8 *p2p_dev_addr;
+};
+
+struct wps_data * wps_init(const struct wps_config *cfg);
+
+void wps_deinit(struct wps_data *data);
+
+/**
+ * enum wps_process_res - WPS message processing result
+ */
+enum wps_process_res {
+ /**
+ * WPS_DONE - Processing done
+ */
+ WPS_DONE,
+
+ /**
+ * WPS_CONTINUE - Processing continues
+ */
+ WPS_CONTINUE,
+
+ /**
+ * WPS_FAILURE - Processing failed
+ */
+ WPS_FAILURE,
+
+ /**
+ * WPS_PENDING - Processing continues, but waiting for an external
+ * event (e.g., UPnP message from an external Registrar)
+ */
+ WPS_PENDING
+};
+enum wps_process_res wps_process_msg(struct wps_data *wps,
+ enum wsc_op_code op_code,
+ const struct wpabuf *msg);
+
+struct wpabuf * wps_get_msg(struct wps_data *wps, enum wsc_op_code *op_code);
+
+int wps_is_selected_pbc_registrar(const struct wpabuf *msg);
+int wps_is_selected_pin_registrar(const struct wpabuf *msg);
+int wps_ap_priority_compar(const struct wpabuf *wps_a,
+ const struct wpabuf *wps_b);
+int wps_is_addr_authorized(const struct wpabuf *msg, const u8 *addr,
+ int ver1_compat);
+const u8 * wps_get_uuid_e(const struct wpabuf *msg);
+
+struct wpabuf * wps_build_assoc_req_ie(enum wps_request_type req_type);
+struct wpabuf * wps_build_assoc_resp_ie(void);
+struct wpabuf * wps_build_probe_req_ie(int pbc, struct wps_device_data *dev,
+ const u8 *uuid,
+ enum wps_request_type req_type,
+ unsigned int num_req_dev_types,
+ const u8 *req_dev_types);
+
+
+/**
+ * struct wps_registrar_config - WPS Registrar configuration
+ */
+struct wps_registrar_config {
+ /**
+ * new_psk_cb - Callback for new PSK
+ * @ctx: Higher layer context data (cb_ctx)
+ * @mac_addr: MAC address of the Enrollee
+ * @psk: The new PSK
+ * @psk_len: The length of psk in octets
+ * Returns: 0 on success, -1 on failure
+ *
+ * This callback is called when a new per-device PSK is provisioned.
+ */
+ int (*new_psk_cb)(void *ctx, const u8 *mac_addr, const u8 *psk,
+ size_t psk_len);
+
+ /**
+ * set_ie_cb - Callback for WPS IE changes
+ * @ctx: Higher layer context data (cb_ctx)
+ * @beacon_ie: WPS IE for Beacon
+ * @probe_resp_ie: WPS IE for Probe Response
+ * Returns: 0 on success, -1 on failure
+ *
+ * This callback is called whenever the WPS IE in Beacon or Probe
+ * Response frames needs to be changed (AP only). Callee is responsible
+ * for freeing the buffers.
+ */
+ int (*set_ie_cb)(void *ctx, struct wpabuf *beacon_ie,
+ struct wpabuf *probe_resp_ie);
+
+ /**
+ * pin_needed_cb - Callback for requesting a PIN
+ * @ctx: Higher layer context data (cb_ctx)
+ * @uuid_e: UUID-E of the unknown Enrollee
+ * @dev: Device Data from the unknown Enrollee
+ *
+ * This callback is called whenever an unknown Enrollee requests to use
+ * PIN method and a matching PIN (Device Password) is not found in
+ * Registrar data.
+ */
+ void (*pin_needed_cb)(void *ctx, const u8 *uuid_e,
+ const struct wps_device_data *dev);
+
+ /**
+ * reg_success_cb - Callback for reporting successful registration
+ * @ctx: Higher layer context data (cb_ctx)
+ * @mac_addr: MAC address of the Enrollee
+ * @uuid_e: UUID-E of the Enrollee
+ *
+ * This callback is called whenever an Enrollee completes registration
+ * successfully.
+ */
+ void (*reg_success_cb)(void *ctx, const u8 *mac_addr,
+ const u8 *uuid_e);
+
+ /**
+ * set_sel_reg_cb - Callback for reporting selected registrar changes
+ * @ctx: Higher layer context data (cb_ctx)
+ * @sel_reg: Whether the Registrar is selected
+ * @dev_passwd_id: Device Password ID to indicate with method or
+ * specific password the Registrar intends to use
+ * @sel_reg_config_methods: Bit field of active config methods
+ *
+ * This callback is called whenever the Selected Registrar state
+ * changes (e.g., a new PIN becomes available or PBC is invoked). This
+ * callback is only used by External Registrar implementation;
+ * set_ie_cb() is used by AP implementation in similar caes, but it
+ * provides the full WPS IE data instead of just the minimal Registrar
+ * state information.
+ */
+ void (*set_sel_reg_cb)(void *ctx, int sel_reg, u16 dev_passwd_id,
+ u16 sel_reg_config_methods);
+
+ /**
+ * enrollee_seen_cb - Callback for reporting Enrollee based on ProbeReq
+ * @ctx: Higher layer context data (cb_ctx)
+ * @addr: MAC address of the Enrollee
+ * @uuid_e: UUID of the Enrollee
+ * @pri_dev_type: Primary device type
+ * @config_methods: Config Methods
+ * @dev_password_id: Device Password ID
+ * @request_type: Request Type
+ * @dev_name: Device Name (if available)
+ */
+ void (*enrollee_seen_cb)(void *ctx, const u8 *addr, const u8 *uuid_e,
+ const u8 *pri_dev_type, u16 config_methods,
+ u16 dev_password_id, u8 request_type,
+ const char *dev_name);
+
+ /**
+ * cb_ctx: Higher layer context data for Registrar callbacks
+ */
+ void *cb_ctx;
+
+ /**
+ * skip_cred_build: Do not build credential
+ *
+ * This option can be used to disable internal code that builds
+ * Credential attribute into M8 based on the current network
+ * configuration and Enrollee capabilities. The extra_cred data will
+ * then be used as the Credential(s).
+ */
+ int skip_cred_build;
+
+ /**
+ * extra_cred: Additional Credential attribute(s)
+ *
+ * This optional data (set to %NULL to disable) can be used to add
+ * Credential attribute(s) for other networks into M8. If
+ * skip_cred_build is set, this will also override the automatically
+ * generated Credential attribute.
+ */
+ const u8 *extra_cred;
+
+ /**
+ * extra_cred_len: Length of extra_cred in octets
+ */
+ size_t extra_cred_len;
+
+ /**
+ * disable_auto_conf - Disable auto-configuration on first registration
+ *
+ * By default, the AP that is started in not configured state will
+ * generate a random PSK and move to configured state when the first
+ * registration protocol run is completed successfully. This option can
+ * be used to disable this functionality and leave it up to an external
+ * program to take care of configuration. This requires the extra_cred
+ * 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;
+
+ /**
+ * dualband - Whether this is a concurrent dualband AP
+ */
+ int dualband;
+};
+
+
+/**
+ * enum wps_event - WPS event types
+ */
+enum wps_event {
+ /**
+ * WPS_EV_M2D - M2D received (Registrar did not know us)
+ */
+ WPS_EV_M2D,
+
+ /**
+ * WPS_EV_FAIL - Registration failed
+ */
+ WPS_EV_FAIL,
+
+ /**
+ * WPS_EV_SUCCESS - Registration succeeded
+ */
+ WPS_EV_SUCCESS,
+
+ /**
+ * WPS_EV_PWD_AUTH_FAIL - Password authentication failed
+ */
+ 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,
+
+ /**
+ * WPS_EV_ER_AP_ADD - ER: AP added
+ */
+ WPS_EV_ER_AP_ADD,
+
+ /**
+ * WPS_EV_ER_AP_REMOVE - ER: AP removed
+ */
+ WPS_EV_ER_AP_REMOVE,
+
+ /**
+ * WPS_EV_ER_ENROLLEE_ADD - ER: Enrollee added
+ */
+ WPS_EV_ER_ENROLLEE_ADD,
+
+ /**
+ * WPS_EV_ER_ENROLLEE_REMOVE - ER: Enrollee removed
+ */
+ WPS_EV_ER_ENROLLEE_REMOVE,
+
+ /**
+ * WPS_EV_ER_AP_SETTINGS - ER: AP Settings learned
+ */
+ WPS_EV_ER_AP_SETTINGS,
+
+ /**
+ * WPS_EV_ER_SET_SELECTED_REGISTRAR - ER: SetSelectedRegistrar event
+ */
+ WPS_EV_ER_SET_SELECTED_REGISTRAR
+};
+
+/**
+ * union wps_event_data - WPS event data
+ */
+union wps_event_data {
+ /**
+ * struct wps_event_m2d - M2D event data
+ */
+ struct wps_event_m2d {
+ u16 config_methods;
+ const u8 *manufacturer;
+ size_t manufacturer_len;
+ const u8 *model_name;
+ size_t model_name_len;
+ const u8 *model_number;
+ size_t model_number_len;
+ const u8 *serial_number;
+ size_t serial_number_len;
+ const u8 *dev_name;
+ size_t dev_name_len;
+ const u8 *primary_dev_type; /* 8 octets */
+ u16 config_error;
+ u16 dev_password_id;
+ } m2d;
+
+ /**
+ * struct wps_event_fail - Registration failure information
+ * @msg: enum wps_msg_type
+ */
+ struct wps_event_fail {
+ int msg;
+ u16 config_error;
+ u16 error_indication;
+ } fail;
+
+ struct wps_event_pwd_auth_fail {
+ int enrollee;
+ int part;
+ } pwd_auth_fail;
+
+ struct wps_event_er_ap {
+ const u8 *uuid;
+ const u8 *mac_addr;
+ const char *friendly_name;
+ const char *manufacturer;
+ const char *manufacturer_url;
+ const char *model_description;
+ const char *model_name;
+ const char *model_number;
+ const char *model_url;
+ const char *serial_number;
+ const char *upc;
+ const u8 *pri_dev_type;
+ u8 wps_state;
+ } ap;
+
+ struct wps_event_er_enrollee {
+ const u8 *uuid;
+ const u8 *mac_addr;
+ int m1_received;
+ u16 config_methods;
+ u16 dev_passwd_id;
+ const u8 *pri_dev_type;
+ const char *dev_name;
+ const char *manufacturer;
+ const char *model_name;
+ const char *model_number;
+ const char *serial_number;
+ } enrollee;
+
+ struct wps_event_er_ap_settings {
+ const u8 *uuid;
+ const struct wps_credential *cred;
+ } ap_settings;
+
+ struct wps_event_er_set_selected_registrar {
+ const u8 *uuid;
+ int sel_reg;
+ u16 dev_passwd_id;
+ u16 sel_reg_config_methods;
+ enum {
+ WPS_ER_SET_SEL_REG_START,
+ WPS_ER_SET_SEL_REG_DONE,
+ WPS_ER_SET_SEL_REG_FAILED
+ } state;
+ } set_sel_reg;
+};
+
+/**
+ * struct upnp_pending_message - Pending PutWLANResponse messages
+ * @next: Pointer to next pending message or %NULL
+ * @addr: NewWLANEventMAC
+ * @msg: NewMessage
+ * @type: Message Type
+ */
+struct upnp_pending_message {
+ struct upnp_pending_message *next;
+ u8 addr[ETH_ALEN];
+ struct wpabuf *msg;
+ enum wps_msg_type type;
+};
+
+/**
+ * struct wps_context - Long term WPS context data
+ *
+ * This data is stored at the higher layer Authenticator or Supplicant data
+ * structures and it is maintained over multiple registration protocol runs.
+ */
+struct wps_context {
+ /**
+ * ap - Whether the local end is an access point
+ */
+ int ap;
+
+ /**
+ * registrar - Pointer to WPS registrar data from wps_registrar_init()
+ */
+ struct wps_registrar *registrar;
+
+ /**
+ * wps_state - Current WPS state
+ */
+ enum wps_state wps_state;
+
+ /**
+ * ap_setup_locked - Whether AP setup is locked (only used at AP)
+ */
+ int ap_setup_locked;
+
+ /**
+ * uuid - Own UUID
+ */
+ u8 uuid[16];
+
+ /**
+ * ssid - SSID
+ *
+ * This SSID is used by the Registrar to fill in information for
+ * Credentials. In addition, AP uses it when acting as an Enrollee to
+ * notify Registrar of the current configuration.
+ */
+ u8 ssid[32];
+
+ /**
+ * ssid_len - Length of ssid in octets
+ */
+ size_t ssid_len;
+
+ /**
+ * dev - Own WPS device data
+ */
+ struct wps_device_data dev;
+
+ /**
+ * oob_conf - OOB Config data
+ */
+ struct oob_conf_data oob_conf;
+
+ /**
+ * oob_dev_pw_id - OOB Device password id
+ */
+ u16 oob_dev_pw_id;
+
+ /**
+ * dh_ctx - Context data for Diffie-Hellman operation
+ */
+ void *dh_ctx;
+
+ /**
+ * dh_privkey - Diffie-Hellman private key
+ */
+ struct wpabuf *dh_privkey;
+
+ /**
+ * dh_pubkey_oob - Diffie-Hellman public key
+ */
+ struct wpabuf *dh_pubkey;
+
+ /**
+ * config_methods - Enabled configuration methods
+ *
+ * Bit field of WPS_CONFIG_*
+ */
+ u16 config_methods;
+
+ /**
+ * encr_types - Enabled encryption types (bit field of WPS_ENCR_*)
+ */
+ u16 encr_types;
+
+ /**
+ * auth_types - Authentication types (bit field of WPS_AUTH_*)
+ */
+ u16 auth_types;
+
+ /**
+ * network_key - The current Network Key (PSK) or %NULL to generate new
+ *
+ * If %NULL, Registrar will generate per-device PSK. In addition, AP
+ * uses this when acting as an Enrollee to notify Registrar of the
+ * current configuration.
+ *
+ * When using WPA/WPA2-Person, this key can be either the ASCII
+ * passphrase (8..63 characters) or the 32-octet PSK (64 hex
+ * characters). When this is set to the ASCII passphrase, the PSK can
+ * be provided in the psk buffer and used per-Enrollee to control which
+ * key type is included in the Credential (e.g., to reduce calculation
+ * need on low-powered devices by provisioning PSK while still allowing
+ * other devices to get the passphrase).
+ */
+ u8 *network_key;
+
+ /**
+ * network_key_len - Length of network_key in octets
+ */
+ size_t network_key_len;
+
+ /**
+ * psk - The current network PSK
+ *
+ * This optional value can be used to provide the current PSK if
+ * network_key is set to the ASCII passphrase.
+ */
+ u8 psk[32];
+
+ /**
+ * psk_set - Whether psk value is set
+ */
+ int psk_set;
+
+ /**
+ * ap_settings - AP Settings override for M7 (only used at AP)
+ *
+ * If %NULL, AP Settings attributes will be generated based on the
+ * current network configuration.
+ */
+ u8 *ap_settings;
+
+ /**
+ * ap_settings_len - Length of ap_settings in octets
+ */
+ size_t ap_settings_len;
+
+ /**
+ * friendly_name - Friendly Name (required for UPnP)
+ */
+ char *friendly_name;
+
+ /**
+ * manufacturer_url - Manufacturer URL (optional for UPnP)
+ */
+ char *manufacturer_url;
+
+ /**
+ * model_description - Model Description (recommended for UPnP)
+ */
+ char *model_description;
+
+ /**
+ * model_url - Model URL (optional for UPnP)
+ */
+ char *model_url;
+
+ /**
+ * upc - Universal Product Code (optional for UPnP)
+ */
+ char *upc;
+
+ /**
+ * cred_cb - Callback to notify that new Credentials were received
+ * @ctx: Higher layer context data (cb_ctx)
+ * @cred: The received Credential
+ * Return: 0 on success, -1 on failure
+ */
+ int (*cred_cb)(void *ctx, const struct wps_credential *cred);
+
+ /**
+ * event_cb - Event callback (state information about progress)
+ * @ctx: Higher layer context data (cb_ctx)
+ * @event: Event type
+ * @data: Event data
+ */
+ void (*event_cb)(void *ctx, enum wps_event event,
+ union wps_event_data *data);
+
+ /**
+ * cb_ctx: Higher layer context data for callbacks
+ */
+ void *cb_ctx;
+
+ struct upnp_wps_device_sm *wps_upnp;
+
+ /* Pending messages from UPnP PutWLANResponse */
+ struct upnp_pending_message *upnp_msgs;
+};
+
+struct oob_device_data {
+ char *device_name;
+ char *device_path;
+ void * (*init_func)(struct wps_context *, struct oob_device_data *,
+ int);
+ struct wpabuf * (*read_func)(void *);
+ int (*write_func)(void *, struct wpabuf *);
+ void (*deinit_func)(void *);
+};
+
+struct oob_nfc_device_data {
+ int (*init_func)(char *);
+ void * (*read_func)(size_t *);
+ int (*write_func)(void *, size_t);
+ void (*deinit_func)(void);
+};
+
+struct wps_registrar *
+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 *addr,
+ const u8 *uuid, const u8 *pin, size_t pin_len,
+ int timeout);
+int wps_registrar_invalidate_pin(struct wps_registrar *reg, const u8 *uuid);
+int wps_registrar_wps_cancel(struct wps_registrar *reg);
+int wps_registrar_unlock_pin(struct wps_registrar *reg, const u8 *uuid);
+int wps_registrar_button_pushed(struct wps_registrar *reg,
+ const u8 *p2p_dev_addr);
+void wps_registrar_probe_req_rx(struct wps_registrar *reg, const u8 *addr,
+ const struct wpabuf *wps_data,
+ int p2p_wildcard);
+int wps_registrar_update_ie(struct wps_registrar *reg);
+int wps_registrar_get_info(struct wps_registrar *reg, const u8 *addr,
+ char *buf, size_t buflen);
+int wps_registrar_config_ap(struct wps_registrar *reg,
+ struct wps_credential *cred);
+
+unsigned int wps_pin_checksum(unsigned int pin);
+unsigned int wps_pin_valid(unsigned int pin);
+unsigned int wps_generate_pin(void);
+void wps_free_pending_msgs(struct upnp_pending_message *msgs);
+
+struct oob_device_data * wps_get_oob_device(char *device_type);
+struct oob_nfc_device_data * wps_get_oob_nfc_device(char *device_name);
+int wps_get_oob_method(char *method);
+int wps_process_oob(struct wps_context *wps, struct oob_device_data *oob_dev,
+ int registrar);
+int wps_attr_text(struct wpabuf *data, char *buf, char *end);
+
+struct wps_er * wps_er_init(struct wps_context *wps, const char *ifname,
+ const char *filter);
+void wps_er_refresh(struct wps_er *er);
+void wps_er_deinit(struct wps_er *er, void (*cb)(void *ctx), void *ctx);
+void wps_er_set_sel_reg(struct wps_er *er, int sel_reg, u16 dev_passwd_id,
+ u16 sel_reg_config_methods);
+int wps_er_pbc(struct wps_er *er, const u8 *uuid);
+int wps_er_learn(struct wps_er *er, const u8 *uuid, const u8 *pin,
+ size_t pin_len);
+int wps_er_set_config(struct wps_er *er, const u8 *uuid,
+ const struct wps_credential *cred);
+int wps_er_config(struct wps_er *er, const u8 *uuid, const u8 *pin,
+ size_t pin_len, const struct wps_credential *cred);
+
+int wps_dev_type_str2bin(const char *str, u8 dev_type[WPS_DEV_TYPE_LEN]);
+char * wps_dev_type_bin2str(const u8 dev_type[WPS_DEV_TYPE_LEN], char *buf,
+ size_t buf_len);
+void uuid_gen_mac_addr(const u8 *mac_addr, u8 *uuid);
+u16 wps_config_methods_str2bin(const char *str);
+
+#ifdef CONFIG_WPS_STRICT
+int wps_validate_beacon(const struct wpabuf *wps_ie);
+int wps_validate_beacon_probe_resp(const struct wpabuf *wps_ie, int probe,
+ const u8 *addr);
+int wps_validate_probe_req(const struct wpabuf *wps_ie, const u8 *addr);
+int wps_validate_assoc_req(const struct wpabuf *wps_ie);
+int wps_validate_assoc_resp(const struct wpabuf *wps_ie);
+int wps_validate_m1(const struct wpabuf *tlvs);
+int wps_validate_m2(const struct wpabuf *tlvs);
+int wps_validate_m2d(const struct wpabuf *tlvs);
+int wps_validate_m3(const struct wpabuf *tlvs);
+int wps_validate_m4(const struct wpabuf *tlvs);
+int wps_validate_m4_encr(const struct wpabuf *tlvs, int wps2);
+int wps_validate_m5(const struct wpabuf *tlvs);
+int wps_validate_m5_encr(const struct wpabuf *tlvs, int wps2);
+int wps_validate_m6(const struct wpabuf *tlvs);
+int wps_validate_m6_encr(const struct wpabuf *tlvs, int wps2);
+int wps_validate_m7(const struct wpabuf *tlvs);
+int wps_validate_m7_encr(const struct wpabuf *tlvs, int ap, int wps2);
+int wps_validate_m8(const struct wpabuf *tlvs);
+int wps_validate_m8_encr(const struct wpabuf *tlvs, int ap, int wps2);
+int wps_validate_wsc_ack(const struct wpabuf *tlvs);
+int wps_validate_wsc_nack(const struct wpabuf *tlvs);
+int wps_validate_wsc_done(const struct wpabuf *tlvs);
+int wps_validate_upnp_set_selected_registrar(const struct wpabuf *tlvs);
+#else /* CONFIG_WPS_STRICT */
+static inline int wps_validate_beacon(const struct wpabuf *wps_ie){
+ return 0;
+}
+
+static inline int wps_validate_beacon_probe_resp(const struct wpabuf *wps_ie,
+ int probe, const u8 *addr)
+{
+ return 0;
+}
+
+static inline int wps_validate_probe_req(const struct wpabuf *wps_ie,
+ const u8 *addr)
+{
+ return 0;
+}
+
+static inline int wps_validate_assoc_req(const struct wpabuf *wps_ie)
+{
+ return 0;
+}
+
+static inline int wps_validate_assoc_resp(const struct wpabuf *wps_ie)
+{
+ return 0;
+}
+
+static inline int wps_validate_m1(const struct wpabuf *tlvs)
+{
+ return 0;
+}
+
+static inline int wps_validate_m2(const struct wpabuf *tlvs)
+{
+ return 0;
+}
+
+static inline int wps_validate_m2d(const struct wpabuf *tlvs)
+{
+ return 0;
+}
+
+static inline int wps_validate_m3(const struct wpabuf *tlvs)
+{
+ return 0;
+}
+
+static inline int wps_validate_m4(const struct wpabuf *tlvs)
+{
+ return 0;
+}
+
+static inline int wps_validate_m4_encr(const struct wpabuf *tlvs, int wps2)
+{
+ return 0;
+}
+
+static inline int wps_validate_m5(const struct wpabuf *tlvs)
+{
+ return 0;
+}
+
+static inline int wps_validate_m5_encr(const struct wpabuf *tlvs, int wps2)
+{
+ return 0;
+}
+
+static inline int wps_validate_m6(const struct wpabuf *tlvs)
+{
+ return 0;
+}
+
+static inline int wps_validate_m6_encr(const struct wpabuf *tlvs, int wps2)
+{
+ return 0;
+}
+
+static inline int wps_validate_m7(const struct wpabuf *tlvs)
+{
+ return 0;
+}
+
+static inline int wps_validate_m7_encr(const struct wpabuf *tlvs, int ap,
+ int wps2)
+{
+ return 0;
+}
+
+static inline int wps_validate_m8(const struct wpabuf *tlvs)
+{
+ return 0;
+}
+
+static inline int wps_validate_m8_encr(const struct wpabuf *tlvs, int ap,
+ int wps2)
+{
+ return 0;
+}
+
+static inline int wps_validate_wsc_ack(const struct wpabuf *tlvs)
+{
+ return 0;
+}
+
+static inline int wps_validate_wsc_nack(const struct wpabuf *tlvs)
+{
+ return 0;
+}
+
+static inline int wps_validate_wsc_done(const struct wpabuf *tlvs)
+{
+ return 0;
+}
+
+static inline int wps_validate_upnp_set_selected_registrar(
+ const struct wpabuf *tlvs)
+{
+ return 0;
+}
+#endif /* CONFIG_WPS_STRICT */
+
+#endif /* WPS_H */
diff --git a/src/wps/wps_attr_build.c b/src/wps/wps_attr_build.c
new file mode 100644
index 0000000..d2ca31a
--- /dev/null
+++ b/src/wps/wps_attr_build.c
@@ -0,0 +1,422 @@
+/*
+ * Wi-Fi Protected Setup - attribute building
+ * Copyright (c) 2008, 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.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "crypto/aes_wrap.h"
+#include "crypto/crypto.h"
+#include "crypto/dh_group5.h"
+#include "crypto/sha256.h"
+#include "crypto/random.h"
+#include "common/ieee802_11_defs.h"
+#include "wps_i.h"
+
+
+int wps_build_public_key(struct wps_data *wps, struct wpabuf *msg)
+{
+ struct wpabuf *pubkey;
+
+ wpa_printf(MSG_DEBUG, "WPS: * Public Key");
+ wpabuf_free(wps->dh_privkey);
+ if (wps->dev_pw_id != DEV_PW_DEFAULT && wps->wps->dh_privkey) {
+ wpa_printf(MSG_DEBUG, "WPS: Using pre-configured DH keys");
+ wps->dh_privkey = wpabuf_dup(wps->wps->dh_privkey);
+ wps->dh_ctx = wps->wps->dh_ctx;
+ wps->wps->dh_ctx = NULL;
+ pubkey = wpabuf_dup(wps->wps->dh_pubkey);
+ } else {
+ wpa_printf(MSG_DEBUG, "WPS: Generate new DH keys");
+ wps->dh_privkey = NULL;
+ dh5_free(wps->dh_ctx);
+ wps->dh_ctx = dh5_init(&wps->dh_privkey, &pubkey);
+ pubkey = wpabuf_zeropad(pubkey, 192);
+ }
+ if (wps->dh_ctx == NULL || wps->dh_privkey == NULL || pubkey == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: Failed to initialize "
+ "Diffie-Hellman handshake");
+ wpabuf_free(pubkey);
+ return -1;
+ }
+ wpa_hexdump_buf_key(MSG_DEBUG, "WPS: DH Private Key", wps->dh_privkey);
+ wpa_hexdump_buf(MSG_DEBUG, "WPS: DH own Public Key", pubkey);
+
+ wpabuf_put_be16(msg, ATTR_PUBLIC_KEY);
+ wpabuf_put_be16(msg, wpabuf_len(pubkey));
+ wpabuf_put_buf(msg, pubkey);
+
+ if (wps->registrar) {
+ wpabuf_free(wps->dh_pubkey_r);
+ wps->dh_pubkey_r = pubkey;
+ } else {
+ wpabuf_free(wps->dh_pubkey_e);
+ wps->dh_pubkey_e = pubkey;
+ }
+
+ return 0;
+}
+
+
+int wps_build_req_type(struct wpabuf *msg, enum wps_request_type type)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * Request Type");
+ wpabuf_put_be16(msg, ATTR_REQUEST_TYPE);
+ wpabuf_put_be16(msg, 1);
+ wpabuf_put_u8(msg, type);
+ return 0;
+}
+
+
+int wps_build_resp_type(struct wpabuf *msg, enum wps_response_type type)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * Response Type (%d)", type);
+ wpabuf_put_be16(msg, ATTR_RESPONSE_TYPE);
+ wpabuf_put_be16(msg, 1);
+ wpabuf_put_u8(msg, type);
+ return 0;
+}
+
+
+int wps_build_config_methods(struct wpabuf *msg, u16 methods)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * Config Methods (%x)", methods);
+ wpabuf_put_be16(msg, ATTR_CONFIG_METHODS);
+ wpabuf_put_be16(msg, 2);
+ wpabuf_put_be16(msg, methods);
+ return 0;
+}
+
+
+int wps_build_uuid_e(struct wpabuf *msg, const u8 *uuid)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * UUID-E");
+ wpabuf_put_be16(msg, ATTR_UUID_E);
+ wpabuf_put_be16(msg, WPS_UUID_LEN);
+ wpabuf_put_data(msg, uuid, WPS_UUID_LEN);
+ return 0;
+}
+
+
+int wps_build_dev_password_id(struct wpabuf *msg, u16 id)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * Device Password ID (%d)", id);
+ wpabuf_put_be16(msg, ATTR_DEV_PASSWORD_ID);
+ wpabuf_put_be16(msg, 2);
+ wpabuf_put_be16(msg, id);
+ return 0;
+}
+
+
+int wps_build_config_error(struct wpabuf *msg, u16 err)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * Configuration Error (%d)", err);
+ wpabuf_put_be16(msg, ATTR_CONFIG_ERROR);
+ wpabuf_put_be16(msg, 2);
+ wpabuf_put_be16(msg, err);
+ return 0;
+}
+
+
+int wps_build_authenticator(struct wps_data *wps, struct wpabuf *msg)
+{
+ u8 hash[SHA256_MAC_LEN];
+ const u8 *addr[2];
+ size_t len[2];
+
+ if (wps->last_msg == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: Last message not available for "
+ "building authenticator");
+ return -1;
+ }
+
+ /* Authenticator = HMAC-SHA256_AuthKey(M_prev || M_curr*)
+ * (M_curr* is M_curr without the Authenticator attribute)
+ */
+ addr[0] = wpabuf_head(wps->last_msg);
+ len[0] = wpabuf_len(wps->last_msg);
+ addr[1] = wpabuf_head(msg);
+ len[1] = wpabuf_len(msg);
+ hmac_sha256_vector(wps->authkey, WPS_AUTHKEY_LEN, 2, addr, len, hash);
+
+ wpa_printf(MSG_DEBUG, "WPS: * Authenticator");
+ wpabuf_put_be16(msg, ATTR_AUTHENTICATOR);
+ wpabuf_put_be16(msg, WPS_AUTHENTICATOR_LEN);
+ wpabuf_put_data(msg, hash, WPS_AUTHENTICATOR_LEN);
+
+ return 0;
+}
+
+
+int wps_build_version(struct wpabuf *msg)
+{
+ /*
+ * Note: This attribute is deprecated and set to hardcoded 0x10 for
+ * backwards compatibility reasons. The real version negotiation is
+ * done with Version2.
+ */
+ wpa_printf(MSG_DEBUG, "WPS: * Version (hardcoded 0x10)");
+ wpabuf_put_be16(msg, ATTR_VERSION);
+ wpabuf_put_be16(msg, 1);
+ wpabuf_put_u8(msg, 0x10);
+ return 0;
+}
+
+
+int wps_build_wfa_ext(struct wpabuf *msg, int req_to_enroll,
+ const u8 *auth_macs, size_t auth_macs_count)
+{
+#ifdef CONFIG_WPS2
+ u8 *len;
+
+ wpabuf_put_be16(msg, ATTR_VENDOR_EXT);
+ len = wpabuf_put(msg, 2); /* to be filled */
+ wpabuf_put_be24(msg, WPS_VENDOR_ID_WFA);
+
+ wpa_printf(MSG_DEBUG, "WPS: * Version2 (0x%x)", WPS_VERSION);
+ wpabuf_put_u8(msg, WFA_ELEM_VERSION2);
+ wpabuf_put_u8(msg, 1);
+ wpabuf_put_u8(msg, WPS_VERSION);
+
+ if (req_to_enroll) {
+ wpa_printf(MSG_DEBUG, "WPS: * Request to Enroll (1)");
+ wpabuf_put_u8(msg, WFA_ELEM_REQUEST_TO_ENROLL);
+ wpabuf_put_u8(msg, 1);
+ wpabuf_put_u8(msg, 1);
+ }
+
+ if (auth_macs && auth_macs_count) {
+ size_t i;
+ wpa_printf(MSG_DEBUG, "WPS: * AuthorizedMACs (count=%d)",
+ (int) auth_macs_count);
+ wpabuf_put_u8(msg, WFA_ELEM_AUTHORIZEDMACS);
+ wpabuf_put_u8(msg, auth_macs_count * ETH_ALEN);
+ wpabuf_put_data(msg, auth_macs, auth_macs_count * ETH_ALEN);
+ for (i = 0; i < auth_macs_count; i++)
+ wpa_printf(MSG_DEBUG, "WPS: AuthorizedMAC: " MACSTR,
+ MAC2STR(&auth_macs[i * ETH_ALEN]));
+ }
+
+ WPA_PUT_BE16(len, (u8 *) wpabuf_put(msg, 0) - len - 2);
+#endif /* CONFIG_WPS2 */
+
+#ifdef CONFIG_WPS_TESTING
+ if (WPS_VERSION > 0x20) {
+ wpa_printf(MSG_DEBUG, "WPS: * Extensibility Testing - extra "
+ "attribute");
+ wpabuf_put_be16(msg, ATTR_EXTENSIBILITY_TEST);
+ wpabuf_put_be16(msg, 1);
+ wpabuf_put_u8(msg, 42);
+ }
+#endif /* CONFIG_WPS_TESTING */
+ return 0;
+}
+
+
+int wps_build_msg_type(struct wpabuf *msg, enum wps_msg_type msg_type)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * Message Type (%d)", msg_type);
+ wpabuf_put_be16(msg, ATTR_MSG_TYPE);
+ wpabuf_put_be16(msg, 1);
+ wpabuf_put_u8(msg, msg_type);
+ return 0;
+}
+
+
+int wps_build_enrollee_nonce(struct wps_data *wps, struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * Enrollee Nonce");
+ wpabuf_put_be16(msg, ATTR_ENROLLEE_NONCE);
+ wpabuf_put_be16(msg, WPS_NONCE_LEN);
+ wpabuf_put_data(msg, wps->nonce_e, WPS_NONCE_LEN);
+ return 0;
+}
+
+
+int wps_build_registrar_nonce(struct wps_data *wps, struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * Registrar Nonce");
+ wpabuf_put_be16(msg, ATTR_REGISTRAR_NONCE);
+ wpabuf_put_be16(msg, WPS_NONCE_LEN);
+ wpabuf_put_data(msg, wps->nonce_r, WPS_NONCE_LEN);
+ return 0;
+}
+
+
+int wps_build_auth_type_flags(struct wps_data *wps, struct wpabuf *msg)
+{
+ u16 auth_types = WPS_AUTH_TYPES;
+#ifdef CONFIG_WPS2
+ auth_types &= ~WPS_AUTH_SHARED;
+#endif /* CONFIG_WPS2 */
+ wpa_printf(MSG_DEBUG, "WPS: * Authentication Type Flags");
+ wpabuf_put_be16(msg, ATTR_AUTH_TYPE_FLAGS);
+ wpabuf_put_be16(msg, 2);
+ wpabuf_put_be16(msg, auth_types);
+ return 0;
+}
+
+
+int wps_build_encr_type_flags(struct wps_data *wps, struct wpabuf *msg)
+{
+ u16 encr_types = WPS_ENCR_TYPES;
+#ifdef CONFIG_WPS2
+ encr_types &= ~WPS_ENCR_WEP;
+#endif /* CONFIG_WPS2 */
+ wpa_printf(MSG_DEBUG, "WPS: * Encryption Type Flags");
+ wpabuf_put_be16(msg, ATTR_ENCR_TYPE_FLAGS);
+ wpabuf_put_be16(msg, 2);
+ wpabuf_put_be16(msg, encr_types);
+ return 0;
+}
+
+
+int wps_build_conn_type_flags(struct wps_data *wps, struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * Connection Type Flags");
+ wpabuf_put_be16(msg, ATTR_CONN_TYPE_FLAGS);
+ wpabuf_put_be16(msg, 1);
+ wpabuf_put_u8(msg, WPS_CONN_ESS);
+ return 0;
+}
+
+
+int wps_build_assoc_state(struct wps_data *wps, struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * Association State");
+ wpabuf_put_be16(msg, ATTR_ASSOC_STATE);
+ wpabuf_put_be16(msg, 2);
+ wpabuf_put_be16(msg, WPS_ASSOC_NOT_ASSOC);
+ return 0;
+}
+
+
+int wps_build_key_wrap_auth(struct wps_data *wps, struct wpabuf *msg)
+{
+ u8 hash[SHA256_MAC_LEN];
+
+ wpa_printf(MSG_DEBUG, "WPS: * Key Wrap Authenticator");
+ hmac_sha256(wps->authkey, WPS_AUTHKEY_LEN, wpabuf_head(msg),
+ wpabuf_len(msg), hash);
+
+ wpabuf_put_be16(msg, ATTR_KEY_WRAP_AUTH);
+ wpabuf_put_be16(msg, WPS_KWA_LEN);
+ wpabuf_put_data(msg, hash, WPS_KWA_LEN);
+ return 0;
+}
+
+
+int wps_build_encr_settings(struct wps_data *wps, struct wpabuf *msg,
+ struct wpabuf *plain)
+{
+ size_t pad_len;
+ const size_t block_size = 16;
+ u8 *iv, *data;
+
+ wpa_printf(MSG_DEBUG, "WPS: * Encrypted Settings");
+
+ /* PKCS#5 v2.0 pad */
+ pad_len = block_size - wpabuf_len(plain) % block_size;
+ os_memset(wpabuf_put(plain, pad_len), pad_len, pad_len);
+
+ wpabuf_put_be16(msg, ATTR_ENCR_SETTINGS);
+ wpabuf_put_be16(msg, block_size + wpabuf_len(plain));
+
+ iv = wpabuf_put(msg, block_size);
+ if (random_get_bytes(iv, block_size) < 0)
+ return -1;
+
+ data = wpabuf_put(msg, 0);
+ wpabuf_put_buf(msg, plain);
+ if (aes_128_cbc_encrypt(wps->keywrapkey, iv, data, wpabuf_len(plain)))
+ return -1;
+
+ return 0;
+}
+
+
+#ifdef CONFIG_WPS_OOB
+int wps_build_oob_dev_password(struct wpabuf *msg, struct wps_context *wps)
+{
+ size_t hash_len;
+ const u8 *addr[1];
+ u8 pubkey_hash[WPS_HASH_LEN];
+ u8 dev_password_bin[WPS_OOB_DEVICE_PASSWORD_LEN];
+
+ wpa_printf(MSG_DEBUG, "WPS: * OOB Device Password");
+
+ addr[0] = wpabuf_head(wps->dh_pubkey);
+ hash_len = wpabuf_len(wps->dh_pubkey);
+ sha256_vector(1, addr, &hash_len, pubkey_hash);
+
+ if (os_get_random((u8 *) &wps->oob_dev_pw_id, sizeof(u16)) < 0) {
+ wpa_printf(MSG_ERROR, "WPS: device password id "
+ "generation error");
+ return -1;
+ }
+ wps->oob_dev_pw_id |= 0x0010;
+
+ if (random_get_bytes(dev_password_bin, WPS_OOB_DEVICE_PASSWORD_LEN) <
+ 0) {
+ wpa_printf(MSG_ERROR, "WPS: OOB device password "
+ "generation error");
+ return -1;
+ }
+
+ wpabuf_put_be16(msg, ATTR_OOB_DEVICE_PASSWORD);
+ wpabuf_put_be16(msg, WPS_OOB_DEVICE_PASSWORD_ATTR_LEN);
+ wpabuf_put_data(msg, pubkey_hash, WPS_OOB_PUBKEY_HASH_LEN);
+ wpabuf_put_be16(msg, wps->oob_dev_pw_id);
+ wpabuf_put_data(msg, dev_password_bin, WPS_OOB_DEVICE_PASSWORD_LEN);
+
+ wpa_snprintf_hex_uppercase(
+ wpabuf_put(wps->oob_conf.dev_password,
+ wpabuf_size(wps->oob_conf.dev_password)),
+ wpabuf_size(wps->oob_conf.dev_password),
+ dev_password_bin, WPS_OOB_DEVICE_PASSWORD_LEN);
+
+ return 0;
+}
+#endif /* CONFIG_WPS_OOB */
+
+
+/* Encapsulate WPS IE data with one (or more, if needed) IE headers */
+struct wpabuf * wps_ie_encapsulate(struct wpabuf *data)
+{
+ struct wpabuf *ie;
+ const u8 *pos, *end;
+
+ ie = wpabuf_alloc(wpabuf_len(data) + 100);
+ if (ie == NULL) {
+ wpabuf_free(data);
+ return NULL;
+ }
+
+ pos = wpabuf_head(data);
+ end = pos + wpabuf_len(data);
+
+ while (end > pos) {
+ size_t frag_len = end - pos;
+ if (frag_len > 251)
+ frag_len = 251;
+ wpabuf_put_u8(ie, WLAN_EID_VENDOR_SPECIFIC);
+ wpabuf_put_u8(ie, 4 + frag_len);
+ wpabuf_put_be32(ie, WPS_DEV_OUI_WFA);
+ wpabuf_put_data(ie, pos, frag_len);
+ pos += frag_len;
+ }
+
+ wpabuf_free(data);
+
+ return ie;
+}
diff --git a/src/wps/wps_attr_parse.c b/src/wps/wps_attr_parse.c
new file mode 100644
index 0000000..7b462d3
--- /dev/null
+++ b/src/wps/wps_attr_parse.c
@@ -0,0 +1,630 @@
+/*
+ * Wi-Fi Protected Setup - attribute parsing
+ * Copyright (c) 2008, 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.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "wps_i.h"
+
+#ifndef CONFIG_WPS_STRICT
+#define WPS_WORKAROUNDS
+#endif /* CONFIG_WPS_STRICT */
+
+
+static int wps_set_vendor_ext_wfa_subelem(struct wps_parse_attr *attr,
+ u8 id, u8 len, const u8 *pos)
+{
+ wpa_printf(MSG_EXCESSIVE, "WPS: WFA subelement id=%u len=%u",
+ id, len);
+ switch (id) {
+ case WFA_ELEM_VERSION2:
+ if (len != 1) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Version2 length "
+ "%u", len);
+ return -1;
+ }
+ attr->version2 = pos;
+ break;
+ case WFA_ELEM_AUTHORIZEDMACS:
+ attr->authorized_macs = pos;
+ attr->authorized_macs_len = len;
+ break;
+ case WFA_ELEM_NETWORK_KEY_SHAREABLE:
+ if (len != 1) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Network Key "
+ "Shareable length %u", len);
+ return -1;
+ }
+ attr->network_key_shareable = pos;
+ break;
+ case WFA_ELEM_REQUEST_TO_ENROLL:
+ if (len != 1) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Request to Enroll "
+ "length %u", len);
+ return -1;
+ }
+ attr->request_to_enroll = pos;
+ break;
+ case WFA_ELEM_SETTINGS_DELAY_TIME:
+ if (len != 1) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Settings Delay "
+ "Time length %u", len);
+ return -1;
+ }
+ attr->settings_delay_time = pos;
+ break;
+ default:
+ wpa_printf(MSG_MSGDUMP, "WPS: Skipped unknown WFA Vendor "
+ "Extension subelement %u", id);
+ break;
+ }
+
+ return 0;
+}
+
+
+static int wps_parse_vendor_ext_wfa(struct wps_parse_attr *attr, const u8 *pos,
+ u16 len)
+{
+ const u8 *end = pos + len;
+ u8 id, elen;
+
+ while (pos + 2 < end) {
+ id = *pos++;
+ elen = *pos++;
+ if (pos + elen > end)
+ break;
+ if (wps_set_vendor_ext_wfa_subelem(attr, id, elen, pos) < 0)
+ return -1;
+ pos += elen;
+ }
+
+ return 0;
+}
+
+
+static int wps_parse_vendor_ext(struct wps_parse_attr *attr, const u8 *pos,
+ u16 len)
+{
+ u32 vendor_id;
+
+ if (len < 3) {
+ wpa_printf(MSG_DEBUG, "WPS: Skip invalid Vendor Extension");
+ return 0;
+ }
+
+ vendor_id = WPA_GET_BE24(pos);
+ switch (vendor_id) {
+ case WPS_VENDOR_ID_WFA:
+ return wps_parse_vendor_ext_wfa(attr, pos + 3, len - 3);
+ }
+
+ /* Handle unknown vendor extensions */
+
+ wpa_printf(MSG_MSGDUMP, "WPS: Unknown Vendor Extension (Vendor ID %u)",
+ vendor_id);
+
+ if (len > WPS_MAX_VENDOR_EXT_LEN) {
+ wpa_printf(MSG_DEBUG, "WPS: Too long Vendor Extension (%u)",
+ len);
+ return -1;
+ }
+
+ if (attr->num_vendor_ext >= MAX_WPS_PARSE_VENDOR_EXT) {
+ wpa_printf(MSG_DEBUG, "WPS: Skipped Vendor Extension "
+ "attribute (max %d vendor extensions)",
+ MAX_WPS_PARSE_VENDOR_EXT);
+ return -1;
+ }
+ attr->vendor_ext[attr->num_vendor_ext] = pos;
+ attr->vendor_ext_len[attr->num_vendor_ext] = len;
+ attr->num_vendor_ext++;
+
+ return 0;
+}
+
+
+static int wps_set_attr(struct wps_parse_attr *attr, u16 type,
+ const u8 *pos, u16 len)
+{
+ switch (type) {
+ case ATTR_VERSION:
+ if (len != 1) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Version length %u",
+ len);
+ return -1;
+ }
+ attr->version = pos;
+ break;
+ case ATTR_MSG_TYPE:
+ if (len != 1) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Message Type "
+ "length %u", len);
+ return -1;
+ }
+ attr->msg_type = pos;
+ break;
+ case ATTR_ENROLLEE_NONCE:
+ if (len != WPS_NONCE_LEN) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Enrollee Nonce "
+ "length %u", len);
+ return -1;
+ }
+ attr->enrollee_nonce = pos;
+ break;
+ case ATTR_REGISTRAR_NONCE:
+ if (len != WPS_NONCE_LEN) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Registrar Nonce "
+ "length %u", len);
+ return -1;
+ }
+ attr->registrar_nonce = pos;
+ break;
+ case ATTR_UUID_E:
+ if (len != WPS_UUID_LEN) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid UUID-E length %u",
+ len);
+ return -1;
+ }
+ attr->uuid_e = pos;
+ break;
+ case ATTR_UUID_R:
+ if (len != WPS_UUID_LEN) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid UUID-R length %u",
+ len);
+ return -1;
+ }
+ attr->uuid_r = pos;
+ break;
+ case ATTR_AUTH_TYPE_FLAGS:
+ if (len != 2) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Authentication "
+ "Type Flags length %u", len);
+ return -1;
+ }
+ attr->auth_type_flags = pos;
+ break;
+ case ATTR_ENCR_TYPE_FLAGS:
+ if (len != 2) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Encryption Type "
+ "Flags length %u", len);
+ return -1;
+ }
+ attr->encr_type_flags = pos;
+ break;
+ case ATTR_CONN_TYPE_FLAGS:
+ if (len != 1) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Connection Type "
+ "Flags length %u", len);
+ return -1;
+ }
+ attr->conn_type_flags = pos;
+ break;
+ case ATTR_CONFIG_METHODS:
+ if (len != 2) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Config Methods "
+ "length %u", len);
+ return -1;
+ }
+ attr->config_methods = pos;
+ break;
+ case ATTR_SELECTED_REGISTRAR_CONFIG_METHODS:
+ if (len != 2) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Selected "
+ "Registrar Config Methods length %u", len);
+ return -1;
+ }
+ attr->sel_reg_config_methods = pos;
+ break;
+ case ATTR_PRIMARY_DEV_TYPE:
+ if (len != WPS_DEV_TYPE_LEN) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Primary Device "
+ "Type length %u", len);
+ return -1;
+ }
+ attr->primary_dev_type = pos;
+ break;
+ case ATTR_RF_BANDS:
+ if (len != 1) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid RF Bands length "
+ "%u", len);
+ return -1;
+ }
+ attr->rf_bands = pos;
+ break;
+ case ATTR_ASSOC_STATE:
+ if (len != 2) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Association State "
+ "length %u", len);
+ return -1;
+ }
+ attr->assoc_state = pos;
+ break;
+ case ATTR_CONFIG_ERROR:
+ if (len != 2) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Configuration "
+ "Error length %u", len);
+ return -1;
+ }
+ attr->config_error = pos;
+ break;
+ case ATTR_DEV_PASSWORD_ID:
+ if (len != 2) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Device Password "
+ "ID length %u", len);
+ return -1;
+ }
+ attr->dev_password_id = pos;
+ break;
+ case ATTR_OOB_DEVICE_PASSWORD:
+ if (len != WPS_OOB_DEVICE_PASSWORD_ATTR_LEN) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid OOB Device "
+ "Password length %u", len);
+ return -1;
+ }
+ attr->oob_dev_password = pos;
+ break;
+ case ATTR_OS_VERSION:
+ if (len != 4) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid OS Version length "
+ "%u", len);
+ return -1;
+ }
+ attr->os_version = pos;
+ break;
+ case ATTR_WPS_STATE:
+ if (len != 1) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Wi-Fi Protected "
+ "Setup State length %u", len);
+ return -1;
+ }
+ attr->wps_state = pos;
+ break;
+ case ATTR_AUTHENTICATOR:
+ if (len != WPS_AUTHENTICATOR_LEN) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Authenticator "
+ "length %u", len);
+ return -1;
+ }
+ attr->authenticator = pos;
+ break;
+ case ATTR_R_HASH1:
+ if (len != WPS_HASH_LEN) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid R-Hash1 length %u",
+ len);
+ return -1;
+ }
+ attr->r_hash1 = pos;
+ break;
+ case ATTR_R_HASH2:
+ if (len != WPS_HASH_LEN) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid R-Hash2 length %u",
+ len);
+ return -1;
+ }
+ attr->r_hash2 = pos;
+ break;
+ case ATTR_E_HASH1:
+ if (len != WPS_HASH_LEN) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid E-Hash1 length %u",
+ len);
+ return -1;
+ }
+ attr->e_hash1 = pos;
+ break;
+ case ATTR_E_HASH2:
+ if (len != WPS_HASH_LEN) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid E-Hash2 length %u",
+ len);
+ return -1;
+ }
+ attr->e_hash2 = pos;
+ break;
+ case ATTR_R_SNONCE1:
+ if (len != WPS_SECRET_NONCE_LEN) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid R-SNonce1 length "
+ "%u", len);
+ return -1;
+ }
+ attr->r_snonce1 = pos;
+ break;
+ case ATTR_R_SNONCE2:
+ if (len != WPS_SECRET_NONCE_LEN) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid R-SNonce2 length "
+ "%u", len);
+ return -1;
+ }
+ attr->r_snonce2 = pos;
+ break;
+ case ATTR_E_SNONCE1:
+ if (len != WPS_SECRET_NONCE_LEN) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid E-SNonce1 length "
+ "%u", len);
+ return -1;
+ }
+ attr->e_snonce1 = pos;
+ break;
+ case ATTR_E_SNONCE2:
+ if (len != WPS_SECRET_NONCE_LEN) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid E-SNonce2 length "
+ "%u", len);
+ return -1;
+ }
+ attr->e_snonce2 = pos;
+ break;
+ case ATTR_KEY_WRAP_AUTH:
+ if (len != WPS_KWA_LEN) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Key Wrap "
+ "Authenticator length %u", len);
+ return -1;
+ }
+ attr->key_wrap_auth = pos;
+ break;
+ case ATTR_AUTH_TYPE:
+ if (len != 2) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Authentication "
+ "Type length %u", len);
+ return -1;
+ }
+ attr->auth_type = pos;
+ break;
+ case ATTR_ENCR_TYPE:
+ if (len != 2) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Encryption "
+ "Type length %u", len);
+ return -1;
+ }
+ attr->encr_type = pos;
+ break;
+ case ATTR_NETWORK_INDEX:
+ if (len != 1) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Network Index "
+ "length %u", len);
+ return -1;
+ }
+ attr->network_idx = pos;
+ break;
+ case ATTR_NETWORK_KEY_INDEX:
+ if (len != 1) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Network Key Index "
+ "length %u", len);
+ return -1;
+ }
+ attr->network_key_idx = pos;
+ break;
+ case ATTR_MAC_ADDR:
+ if (len != ETH_ALEN) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid MAC Address "
+ "length %u", len);
+ return -1;
+ }
+ attr->mac_addr = pos;
+ break;
+ case ATTR_KEY_PROVIDED_AUTO:
+ if (len != 1) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Key Provided "
+ "Automatically length %u", len);
+ return -1;
+ }
+ attr->key_prov_auto = pos;
+ break;
+ case ATTR_802_1X_ENABLED:
+ if (len != 1) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid 802.1X Enabled "
+ "length %u", len);
+ return -1;
+ }
+ attr->dot1x_enabled = pos;
+ break;
+ case ATTR_SELECTED_REGISTRAR:
+ if (len != 1) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Selected Registrar"
+ " length %u", len);
+ return -1;
+ }
+ attr->selected_registrar = pos;
+ break;
+ case ATTR_REQUEST_TYPE:
+ if (len != 1) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Request Type "
+ "length %u", len);
+ return -1;
+ }
+ attr->request_type = pos;
+ break;
+ case ATTR_RESPONSE_TYPE:
+ if (len != 1) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Response Type "
+ "length %u", len);
+ return -1;
+ }
+ attr->response_type = pos;
+ break;
+ case ATTR_MANUFACTURER:
+ attr->manufacturer = pos;
+ attr->manufacturer_len = len;
+ break;
+ case ATTR_MODEL_NAME:
+ attr->model_name = pos;
+ attr->model_name_len = len;
+ break;
+ case ATTR_MODEL_NUMBER:
+ attr->model_number = pos;
+ attr->model_number_len = len;
+ break;
+ case ATTR_SERIAL_NUMBER:
+ attr->serial_number = pos;
+ attr->serial_number_len = len;
+ break;
+ case ATTR_DEV_NAME:
+ attr->dev_name = pos;
+ attr->dev_name_len = len;
+ break;
+ case ATTR_PUBLIC_KEY:
+ attr->public_key = pos;
+ attr->public_key_len = len;
+ break;
+ case ATTR_ENCR_SETTINGS:
+ attr->encr_settings = pos;
+ attr->encr_settings_len = len;
+ break;
+ case ATTR_CRED:
+ if (attr->num_cred >= MAX_CRED_COUNT) {
+ wpa_printf(MSG_DEBUG, "WPS: Skipped Credential "
+ "attribute (max %d credentials)",
+ MAX_CRED_COUNT);
+ break;
+ }
+ attr->cred[attr->num_cred] = pos;
+ attr->cred_len[attr->num_cred] = len;
+ attr->num_cred++;
+ break;
+ case ATTR_SSID:
+ attr->ssid = pos;
+ attr->ssid_len = len;
+ break;
+ case ATTR_NETWORK_KEY:
+ attr->network_key = pos;
+ attr->network_key_len = len;
+ break;
+ case ATTR_EAP_TYPE:
+ attr->eap_type = pos;
+ attr->eap_type_len = len;
+ break;
+ case ATTR_EAP_IDENTITY:
+ 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;
+ case ATTR_REQUESTED_DEV_TYPE:
+ if (len != WPS_DEV_TYPE_LEN) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Requested Device "
+ "Type length %u", len);
+ return -1;
+ }
+ if (attr->num_req_dev_type >= MAX_REQ_DEV_TYPE_COUNT) {
+ wpa_printf(MSG_DEBUG, "WPS: Skipped Requested Device "
+ "Type attribute (max %u types)",
+ MAX_REQ_DEV_TYPE_COUNT);
+ break;
+ }
+ attr->req_dev_type[attr->num_req_dev_type] = pos;
+ attr->num_req_dev_type++;
+ break;
+ case ATTR_SECONDARY_DEV_TYPE_LIST:
+ if (len > WPS_SEC_DEV_TYPE_MAX_LEN ||
+ (len % WPS_DEV_TYPE_LEN) > 0) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Secondary Device "
+ "Type length %u", len);
+ return -1;
+ }
+ attr->sec_dev_type_list = pos;
+ attr->sec_dev_type_list_len = len;
+ break;
+ case ATTR_VENDOR_EXT:
+ if (wps_parse_vendor_ext(attr, pos, len) < 0)
+ return -1;
+ break;
+ default:
+ wpa_printf(MSG_DEBUG, "WPS: Unsupported attribute type 0x%x "
+ "len=%u", type, len);
+ break;
+ }
+
+ return 0;
+}
+
+
+int wps_parse_msg(const struct wpabuf *msg, struct wps_parse_attr *attr)
+{
+ const u8 *pos, *end;
+ u16 type, len;
+ u16 prev_type = 0;
+
+ os_memset(attr, 0, sizeof(*attr));
+ pos = wpabuf_head(msg);
+ end = pos + wpabuf_len(msg);
+
+ while (pos < end) {
+ if (end - pos < 4) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid message - "
+ "%lu bytes remaining",
+ (unsigned long) (end - pos));
+ return -1;
+ }
+
+ type = WPA_GET_BE16(pos);
+ pos += 2;
+ len = WPA_GET_BE16(pos);
+ pos += 2;
+ wpa_printf(MSG_EXCESSIVE, "WPS: attr type=0x%x len=%u",
+ type, len);
+ if (len > end - pos) {
+ wpa_printf(MSG_DEBUG, "WPS: Attribute overflow");
+ wpa_hexdump_buf(MSG_MSGDUMP, "WPS: Message data", msg);
+#ifdef WPS_WORKAROUNDS
+ /*
+ * Some deployed APs seem to have a bug in encoding of
+ * Network Key attribute in the Credential attribute
+ * where they add an extra octet after the Network Key
+ * attribute at least when open network is being
+ * provisioned.
+ */
+ if ((type & 0xff00) != 0x1000 &&
+ prev_type == ATTR_NETWORK_KEY) {
+ wpa_printf(MSG_DEBUG, "WPS: Workaround - try "
+ "to skip unexpected octet after "
+ "Network Key");
+ pos -= 3;
+ continue;
+ }
+#endif /* WPS_WORKAROUNDS */
+ return -1;
+ }
+
+#ifdef WPS_WORKAROUNDS
+ if (type == 0 && len == 0) {
+ /*
+ * Mac OS X 10.6 seems to be adding 0x00 padding to the
+ * end of M1. Skip those to avoid interop issues.
+ */
+ int i;
+ for (i = 0; i < end - pos; i++) {
+ if (pos[i])
+ break;
+ }
+ if (i == end - pos) {
+ wpa_printf(MSG_DEBUG, "WPS: Workaround - skip "
+ "unexpected message padding");
+ break;
+ }
+ }
+#endif /* WPS_WORKAROUNDS */
+
+ if (wps_set_attr(attr, type, pos, len) < 0)
+ return -1;
+
+ prev_type = type;
+ pos += len;
+ }
+
+ return 0;
+}
diff --git a/src/wps/wps_attr_process.c b/src/wps/wps_attr_process.c
new file mode 100644
index 0000000..07e087d
--- /dev/null
+++ b/src/wps/wps_attr_process.c
@@ -0,0 +1,335 @@
+/*
+ * Wi-Fi Protected Setup - attribute processing
+ * Copyright (c) 2008, 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.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "crypto/sha256.h"
+#include "wps_i.h"
+
+
+int wps_process_authenticator(struct wps_data *wps, const u8 *authenticator,
+ const struct wpabuf *msg)
+{
+ u8 hash[SHA256_MAC_LEN];
+ const u8 *addr[2];
+ size_t len[2];
+
+ if (authenticator == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Authenticator attribute "
+ "included");
+ return -1;
+ }
+
+ if (wps->last_msg == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: Last message not available for "
+ "validating authenticator");
+ return -1;
+ }
+
+ /* Authenticator = HMAC-SHA256_AuthKey(M_prev || M_curr*)
+ * (M_curr* is M_curr without the Authenticator attribute)
+ */
+ addr[0] = wpabuf_head(wps->last_msg);
+ len[0] = wpabuf_len(wps->last_msg);
+ addr[1] = wpabuf_head(msg);
+ len[1] = wpabuf_len(msg) - 4 - WPS_AUTHENTICATOR_LEN;
+ hmac_sha256_vector(wps->authkey, WPS_AUTHKEY_LEN, 2, addr, len, hash);
+
+ if (os_memcmp(hash, authenticator, WPS_AUTHENTICATOR_LEN) != 0) {
+ wpa_printf(MSG_DEBUG, "WPS: Incorrect Authenticator");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+int wps_process_key_wrap_auth(struct wps_data *wps, struct wpabuf *msg,
+ const u8 *key_wrap_auth)
+{
+ u8 hash[SHA256_MAC_LEN];
+ const u8 *head;
+ size_t len;
+
+ if (key_wrap_auth == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No KWA in decrypted attribute");
+ return -1;
+ }
+
+ head = wpabuf_head(msg);
+ len = wpabuf_len(msg) - 4 - WPS_KWA_LEN;
+ if (head + len != key_wrap_auth - 4) {
+ wpa_printf(MSG_DEBUG, "WPS: KWA not in the end of the "
+ "decrypted attribute");
+ return -1;
+ }
+
+ hmac_sha256(wps->authkey, WPS_AUTHKEY_LEN, head, len, hash);
+ if (os_memcmp(hash, key_wrap_auth, WPS_KWA_LEN) != 0) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid KWA");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static int wps_process_cred_network_idx(struct wps_credential *cred,
+ const u8 *idx)
+{
+ if (idx == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: Credential did not include "
+ "Network Index");
+ return -1;
+ }
+
+ wpa_printf(MSG_DEBUG, "WPS: Network Index: %d", *idx);
+
+ return 0;
+}
+
+
+static int wps_process_cred_ssid(struct wps_credential *cred, const u8 *ssid,
+ size_t ssid_len)
+{
+ if (ssid == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: Credential did not include SSID");
+ return -1;
+ }
+
+ /* Remove zero-padding since some Registrar implementations seem to use
+ * hardcoded 32-octet length for this attribute */
+ while (ssid_len > 0 && ssid[ssid_len - 1] == 0)
+ ssid_len--;
+
+ wpa_hexdump_ascii(MSG_DEBUG, "WPS: SSID", ssid, ssid_len);
+ if (ssid_len <= sizeof(cred->ssid)) {
+ os_memcpy(cred->ssid, ssid, ssid_len);
+ cred->ssid_len = ssid_len;
+ }
+
+ return 0;
+}
+
+
+static int wps_process_cred_auth_type(struct wps_credential *cred,
+ const u8 *auth_type)
+{
+ if (auth_type == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: Credential did not include "
+ "Authentication Type");
+ return -1;
+ }
+
+ cred->auth_type = WPA_GET_BE16(auth_type);
+ wpa_printf(MSG_DEBUG, "WPS: Authentication Type: 0x%x",
+ cred->auth_type);
+
+ return 0;
+}
+
+
+static int wps_process_cred_encr_type(struct wps_credential *cred,
+ const u8 *encr_type)
+{
+ if (encr_type == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: Credential did not include "
+ "Encryption Type");
+ return -1;
+ }
+
+ cred->encr_type = WPA_GET_BE16(encr_type);
+ wpa_printf(MSG_DEBUG, "WPS: Encryption Type: 0x%x",
+ cred->encr_type);
+
+ return 0;
+}
+
+
+static int wps_process_cred_network_key_idx(struct wps_credential *cred,
+ const u8 *key_idx)
+{
+ if (key_idx == NULL)
+ return 0; /* optional attribute */
+
+ wpa_printf(MSG_DEBUG, "WPS: Network Key Index: %d", *key_idx);
+ cred->key_idx = *key_idx;
+
+ return 0;
+}
+
+
+static int wps_process_cred_network_key(struct wps_credential *cred,
+ const u8 *key, size_t key_len)
+{
+ if (key == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: Credential did not include "
+ "Network Key");
+ if (cred->auth_type == WPS_AUTH_OPEN &&
+ cred->encr_type == WPS_ENCR_NONE) {
+ wpa_printf(MSG_DEBUG, "WPS: Workaround - Allow "
+ "missing mandatory Network Key attribute "
+ "for open network");
+ return 0;
+ }
+ return -1;
+ }
+
+ wpa_hexdump_key(MSG_DEBUG, "WPS: Network Key", key, key_len);
+ if (key_len <= sizeof(cred->key)) {
+ os_memcpy(cred->key, key, key_len);
+ cred->key_len = key_len;
+ }
+
+ return 0;
+}
+
+
+static int wps_process_cred_mac_addr(struct wps_credential *cred,
+ const u8 *mac_addr)
+{
+ if (mac_addr == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: Credential did not include "
+ "MAC Address");
+ return -1;
+ }
+
+ wpa_printf(MSG_DEBUG, "WPS: MAC Address " MACSTR, MAC2STR(mac_addr));
+ os_memcpy(cred->mac_addr, mac_addr, ETH_ALEN);
+
+ return 0;
+}
+
+
+static int wps_process_cred_eap_type(struct wps_credential *cred,
+ const u8 *eap_type, size_t eap_type_len)
+{
+ if (eap_type == NULL)
+ return 0; /* optional attribute */
+
+ wpa_hexdump(MSG_DEBUG, "WPS: EAP Type", eap_type, eap_type_len);
+
+ return 0;
+}
+
+
+static int wps_process_cred_eap_identity(struct wps_credential *cred,
+ const u8 *identity,
+ size_t identity_len)
+{
+ if (identity == NULL)
+ return 0; /* optional attribute */
+
+ wpa_hexdump_ascii(MSG_DEBUG, "WPS: EAP Identity",
+ identity, identity_len);
+
+ return 0;
+}
+
+
+static int wps_process_cred_key_prov_auto(struct wps_credential *cred,
+ const u8 *key_prov_auto)
+{
+ if (key_prov_auto == NULL)
+ return 0; /* optional attribute */
+
+ wpa_printf(MSG_DEBUG, "WPS: Key Provided Automatically: %d",
+ *key_prov_auto);
+
+ return 0;
+}
+
+
+static int wps_process_cred_802_1x_enabled(struct wps_credential *cred,
+ const u8 *dot1x_enabled)
+{
+ if (dot1x_enabled == NULL)
+ return 0; /* optional attribute */
+
+ wpa_printf(MSG_DEBUG, "WPS: 802.1X Enabled: %d", *dot1x_enabled);
+
+ return 0;
+}
+
+
+static int wps_workaround_cred_key(struct wps_credential *cred)
+{
+ if (cred->auth_type & (WPS_AUTH_WPAPSK | WPS_AUTH_WPA2PSK) &&
+ cred->key_len > 8 && cred->key_len < 64 &&
+ cred->key[cred->key_len - 1] == 0) {
+#ifdef CONFIG_WPS_STRICT
+ wpa_printf(MSG_INFO, "WPS: WPA/WPA2-Personal passphrase uses "
+ "forbidden NULL termination");
+ wpa_hexdump_ascii_key(MSG_INFO, "WPS: Network Key",
+ cred->key, cred->key_len);
+ return -1;
+#else /* CONFIG_WPS_STRICT */
+ /*
+ * A deployed external registrar is known to encode ASCII
+ * passphrases incorrectly. Remove the extra NULL termination
+ * to fix the encoding.
+ */
+ wpa_printf(MSG_DEBUG, "WPS: Workaround - remove NULL "
+ "termination from ASCII passphrase");
+ cred->key_len--;
+#endif /* CONFIG_WPS_STRICT */
+ }
+ return 0;
+}
+
+
+int wps_process_cred(struct wps_parse_attr *attr,
+ struct wps_credential *cred)
+{
+ wpa_printf(MSG_DEBUG, "WPS: Process Credential");
+
+ /* TODO: support multiple Network Keys */
+ if (wps_process_cred_network_idx(cred, attr->network_idx) ||
+ wps_process_cred_ssid(cred, attr->ssid, attr->ssid_len) ||
+ wps_process_cred_auth_type(cred, attr->auth_type) ||
+ wps_process_cred_encr_type(cred, attr->encr_type) ||
+ wps_process_cred_network_key_idx(cred, attr->network_key_idx) ||
+ wps_process_cred_network_key(cred, attr->network_key,
+ attr->network_key_len) ||
+ wps_process_cred_mac_addr(cred, attr->mac_addr) ||
+ wps_process_cred_eap_type(cred, attr->eap_type,
+ attr->eap_type_len) ||
+ wps_process_cred_eap_identity(cred, attr->eap_identity,
+ attr->eap_identity_len) ||
+ wps_process_cred_key_prov_auto(cred, attr->key_prov_auto) ||
+ wps_process_cred_802_1x_enabled(cred, attr->dot1x_enabled))
+ return -1;
+
+ return wps_workaround_cred_key(cred);
+}
+
+
+int wps_process_ap_settings(struct wps_parse_attr *attr,
+ struct wps_credential *cred)
+{
+ wpa_printf(MSG_DEBUG, "WPS: Processing AP Settings");
+ os_memset(cred, 0, sizeof(*cred));
+ /* TODO: optional attributes New Password and Device Password ID */
+ if (wps_process_cred_ssid(cred, attr->ssid, attr->ssid_len) ||
+ wps_process_cred_auth_type(cred, attr->auth_type) ||
+ wps_process_cred_encr_type(cred, attr->encr_type) ||
+ wps_process_cred_network_key_idx(cred, attr->network_key_idx) ||
+ wps_process_cred_network_key(cred, attr->network_key,
+ attr->network_key_len) ||
+ wps_process_cred_mac_addr(cred, attr->mac_addr))
+ return -1;
+
+ return wps_workaround_cred_key(cred);
+}
diff --git a/src/wps/wps_common.c b/src/wps/wps_common.c
new file mode 100644
index 0000000..5d0508c
--- /dev/null
+++ b/src/wps/wps_common.c
@@ -0,0 +1,704 @@
+/*
+ * Wi-Fi Protected Setup - common functionality
+ * Copyright (c) 2008-2009, 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.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "crypto/aes_wrap.h"
+#include "crypto/crypto.h"
+#include "crypto/dh_group5.h"
+#include "crypto/sha1.h"
+#include "crypto/sha256.h"
+#include "crypto/random.h"
+#include "wps_i.h"
+#include "wps_dev_attr.h"
+
+
+void wps_kdf(const u8 *key, const u8 *label_prefix, size_t label_prefix_len,
+ const char *label, u8 *res, size_t res_len)
+{
+ u8 i_buf[4], key_bits[4];
+ const u8 *addr[4];
+ size_t len[4];
+ int i, iter;
+ u8 hash[SHA256_MAC_LEN], *opos;
+ size_t left;
+
+ WPA_PUT_BE32(key_bits, res_len * 8);
+
+ addr[0] = i_buf;
+ len[0] = sizeof(i_buf);
+ addr[1] = label_prefix;
+ len[1] = label_prefix_len;
+ addr[2] = (const u8 *) label;
+ len[2] = os_strlen(label);
+ addr[3] = key_bits;
+ len[3] = sizeof(key_bits);
+
+ iter = (res_len + SHA256_MAC_LEN - 1) / SHA256_MAC_LEN;
+ opos = res;
+ left = res_len;
+
+ for (i = 1; i <= iter; i++) {
+ WPA_PUT_BE32(i_buf, i);
+ hmac_sha256_vector(key, SHA256_MAC_LEN, 4, addr, len, hash);
+ if (i < iter) {
+ os_memcpy(opos, hash, SHA256_MAC_LEN);
+ opos += SHA256_MAC_LEN;
+ left -= SHA256_MAC_LEN;
+ } else
+ os_memcpy(opos, hash, left);
+ }
+}
+
+
+int wps_derive_keys(struct wps_data *wps)
+{
+ struct wpabuf *pubkey, *dh_shared;
+ u8 dhkey[SHA256_MAC_LEN], kdk[SHA256_MAC_LEN];
+ const u8 *addr[3];
+ size_t len[3];
+ u8 keys[WPS_AUTHKEY_LEN + WPS_KEYWRAPKEY_LEN + WPS_EMSK_LEN];
+
+ if (wps->dh_privkey == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: Own DH private key not available");
+ return -1;
+ }
+
+ pubkey = wps->registrar ? wps->dh_pubkey_e : wps->dh_pubkey_r;
+ if (pubkey == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: Peer DH public key not available");
+ return -1;
+ }
+
+ wpa_hexdump_buf_key(MSG_DEBUG, "WPS: DH Private Key", wps->dh_privkey);
+ wpa_hexdump_buf(MSG_DEBUG, "WPS: DH peer Public Key", pubkey);
+ dh_shared = dh5_derive_shared(wps->dh_ctx, pubkey, wps->dh_privkey);
+ dh5_free(wps->dh_ctx);
+ wps->dh_ctx = NULL;
+ dh_shared = wpabuf_zeropad(dh_shared, 192);
+ if (dh_shared == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: Failed to derive DH shared key");
+ return -1;
+ }
+
+ /* Own DH private key is not needed anymore */
+ wpabuf_free(wps->dh_privkey);
+ wps->dh_privkey = NULL;
+
+ wpa_hexdump_buf_key(MSG_DEBUG, "WPS: DH shared key", dh_shared);
+
+ /* DHKey = SHA-256(g^AB mod p) */
+ addr[0] = wpabuf_head(dh_shared);
+ len[0] = wpabuf_len(dh_shared);
+ sha256_vector(1, addr, len, dhkey);
+ wpa_hexdump_key(MSG_DEBUG, "WPS: DHKey", dhkey, sizeof(dhkey));
+ wpabuf_free(dh_shared);
+
+ /* KDK = HMAC-SHA-256_DHKey(N1 || EnrolleeMAC || N2) */
+ addr[0] = wps->nonce_e;
+ len[0] = WPS_NONCE_LEN;
+ addr[1] = wps->mac_addr_e;
+ len[1] = ETH_ALEN;
+ addr[2] = wps->nonce_r;
+ len[2] = WPS_NONCE_LEN;
+ hmac_sha256_vector(dhkey, sizeof(dhkey), 3, addr, len, kdk);
+ wpa_hexdump_key(MSG_DEBUG, "WPS: KDK", kdk, sizeof(kdk));
+
+ wps_kdf(kdk, NULL, 0, "Wi-Fi Easy and Secure Key Derivation",
+ keys, sizeof(keys));
+ os_memcpy(wps->authkey, keys, WPS_AUTHKEY_LEN);
+ os_memcpy(wps->keywrapkey, keys + WPS_AUTHKEY_LEN, WPS_KEYWRAPKEY_LEN);
+ os_memcpy(wps->emsk, keys + WPS_AUTHKEY_LEN + WPS_KEYWRAPKEY_LEN,
+ WPS_EMSK_LEN);
+
+ wpa_hexdump_key(MSG_DEBUG, "WPS: AuthKey",
+ wps->authkey, WPS_AUTHKEY_LEN);
+ wpa_hexdump_key(MSG_DEBUG, "WPS: KeyWrapKey",
+ wps->keywrapkey, WPS_KEYWRAPKEY_LEN);
+ wpa_hexdump_key(MSG_DEBUG, "WPS: EMSK", wps->emsk, WPS_EMSK_LEN);
+
+ return 0;
+}
+
+
+void wps_derive_psk(struct wps_data *wps, const u8 *dev_passwd,
+ size_t dev_passwd_len)
+{
+ u8 hash[SHA256_MAC_LEN];
+
+ hmac_sha256(wps->authkey, WPS_AUTHKEY_LEN, dev_passwd,
+ (dev_passwd_len + 1) / 2, hash);
+ os_memcpy(wps->psk1, hash, WPS_PSK_LEN);
+ hmac_sha256(wps->authkey, WPS_AUTHKEY_LEN,
+ dev_passwd + (dev_passwd_len + 1) / 2,
+ dev_passwd_len / 2, hash);
+ os_memcpy(wps->psk2, hash, WPS_PSK_LEN);
+
+ wpa_hexdump_ascii_key(MSG_DEBUG, "WPS: Device Password",
+ dev_passwd, dev_passwd_len);
+ wpa_hexdump_key(MSG_DEBUG, "WPS: PSK1", wps->psk1, WPS_PSK_LEN);
+ wpa_hexdump_key(MSG_DEBUG, "WPS: PSK2", wps->psk2, WPS_PSK_LEN);
+}
+
+
+struct wpabuf * wps_decrypt_encr_settings(struct wps_data *wps, const u8 *encr,
+ size_t encr_len)
+{
+ struct wpabuf *decrypted;
+ const size_t block_size = 16;
+ size_t i;
+ u8 pad;
+ const u8 *pos;
+
+ /* AES-128-CBC */
+ if (encr == NULL || encr_len < 2 * block_size || encr_len % block_size)
+ {
+ wpa_printf(MSG_DEBUG, "WPS: No Encrypted Settings received");
+ return NULL;
+ }
+
+ decrypted = wpabuf_alloc(encr_len - block_size);
+ if (decrypted == NULL)
+ return NULL;
+
+ wpa_hexdump(MSG_MSGDUMP, "WPS: Encrypted Settings", encr, encr_len);
+ wpabuf_put_data(decrypted, encr + block_size, encr_len - block_size);
+ if (aes_128_cbc_decrypt(wps->keywrapkey, encr, wpabuf_mhead(decrypted),
+ wpabuf_len(decrypted))) {
+ wpabuf_free(decrypted);
+ return NULL;
+ }
+
+ wpa_hexdump_buf_key(MSG_MSGDUMP, "WPS: Decrypted Encrypted Settings",
+ decrypted);
+
+ pos = wpabuf_head_u8(decrypted) + wpabuf_len(decrypted) - 1;
+ pad = *pos;
+ if (pad > wpabuf_len(decrypted)) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid PKCS#5 v2.0 pad value");
+ wpabuf_free(decrypted);
+ return NULL;
+ }
+ for (i = 0; i < pad; i++) {
+ if (*pos-- != pad) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid PKCS#5 v2.0 pad "
+ "string");
+ wpabuf_free(decrypted);
+ return NULL;
+ }
+ }
+ decrypted->used -= pad;
+
+ return decrypted;
+}
+
+
+/**
+ * wps_pin_checksum - Compute PIN checksum
+ * @pin: Seven digit PIN (i.e., eight digit PIN without the checksum digit)
+ * Returns: Checksum digit
+ */
+unsigned int wps_pin_checksum(unsigned int pin)
+{
+ unsigned int accum = 0;
+ while (pin) {
+ accum += 3 * (pin % 10);
+ pin /= 10;
+ accum += pin % 10;
+ pin /= 10;
+ }
+
+ return (10 - accum % 10) % 10;
+}
+
+
+/**
+ * wps_pin_valid - Check whether a PIN has a valid checksum
+ * @pin: Eight digit PIN (i.e., including the checksum digit)
+ * Returns: 1 if checksum digit is valid, or 0 if not
+ */
+unsigned int wps_pin_valid(unsigned int pin)
+{
+ return wps_pin_checksum(pin / 10) == (pin % 10);
+}
+
+
+/**
+ * wps_generate_pin - Generate a random PIN
+ * Returns: Eight digit PIN (i.e., including the checksum digit)
+ */
+unsigned int wps_generate_pin(void)
+{
+ unsigned int val;
+
+ /* Generate seven random digits for the PIN */
+ if (random_get_bytes((unsigned char *) &val, sizeof(val)) < 0) {
+ struct os_time now;
+ os_get_time(&now);
+ val = os_random() ^ now.sec ^ now.usec;
+ }
+ val %= 10000000;
+
+ /* Append checksum digit */
+ return val * 10 + wps_pin_checksum(val);
+}
+
+
+void wps_fail_event(struct wps_context *wps, enum wps_msg_type msg,
+ u16 config_error, u16 error_indication)
+{
+ union wps_event_data data;
+
+ if (wps->event_cb == NULL)
+ return;
+
+ os_memset(&data, 0, sizeof(data));
+ data.fail.msg = msg;
+ data.fail.config_error = config_error;
+ data.fail.error_indication = error_indication;
+ wps->event_cb(wps->cb_ctx, WPS_EV_FAIL, &data);
+}
+
+
+void wps_success_event(struct wps_context *wps)
+{
+ if (wps->event_cb == NULL)
+ return;
+
+ wps->event_cb(wps->cb_ctx, WPS_EV_SUCCESS, NULL);
+}
+
+
+void wps_pwd_auth_fail_event(struct wps_context *wps, int enrollee, int part)
+{
+ union wps_event_data data;
+
+ if (wps->event_cb == NULL)
+ return;
+
+ os_memset(&data, 0, sizeof(data));
+ data.pwd_auth_fail.enrollee = enrollee;
+ 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);
+}
+
+
+#ifdef CONFIG_WPS_OOB
+
+static struct wpabuf * wps_get_oob_cred(struct wps_context *wps)
+{
+ struct wps_data data;
+ struct wpabuf *plain;
+
+ plain = wpabuf_alloc(500);
+ if (plain == NULL) {
+ wpa_printf(MSG_ERROR, "WPS: Failed to allocate memory for OOB "
+ "credential");
+ return NULL;
+ }
+
+ os_memset(&data, 0, sizeof(data));
+ data.wps = wps;
+ data.auth_type = wps->auth_types;
+ data.encr_type = wps->encr_types;
+ if (wps_build_version(plain) ||
+ wps_build_cred(&data, plain) ||
+ wps_build_wfa_ext(plain, 0, NULL, 0)) {
+ wpabuf_free(plain);
+ return NULL;
+ }
+
+ return plain;
+}
+
+
+static struct wpabuf * wps_get_oob_dev_pwd(struct wps_context *wps)
+{
+ struct wpabuf *data;
+
+ data = wpabuf_alloc(9 + WPS_OOB_DEVICE_PASSWORD_ATTR_LEN);
+ if (data == NULL) {
+ wpa_printf(MSG_ERROR, "WPS: Failed to allocate memory for OOB "
+ "device password attribute");
+ return NULL;
+ }
+
+ wpabuf_free(wps->oob_conf.dev_password);
+ wps->oob_conf.dev_password =
+ wpabuf_alloc(WPS_OOB_DEVICE_PASSWORD_LEN * 2 + 1);
+ if (wps->oob_conf.dev_password == NULL) {
+ wpa_printf(MSG_ERROR, "WPS: Failed to allocate memory for OOB "
+ "device password");
+ wpabuf_free(data);
+ return NULL;
+ }
+
+ if (wps_build_version(data) ||
+ wps_build_oob_dev_password(data, wps) ||
+ wps_build_wfa_ext(data, 0, NULL, 0)) {
+ wpa_printf(MSG_ERROR, "WPS: Build OOB device password "
+ "attribute error");
+ wpabuf_free(data);
+ return NULL;
+ }
+
+ return data;
+}
+
+
+static int wps_parse_oob_dev_pwd(struct wps_context *wps,
+ struct wpabuf *data)
+{
+ struct oob_conf_data *oob_conf = &wps->oob_conf;
+ struct wps_parse_attr attr;
+ const u8 *pos;
+
+ if (wps_parse_msg(data, &attr) < 0 ||
+ attr.oob_dev_password == NULL) {
+ wpa_printf(MSG_ERROR, "WPS: OOB device password not found");
+ return -1;
+ }
+
+ pos = attr.oob_dev_password;
+
+ oob_conf->pubkey_hash =
+ wpabuf_alloc_copy(pos, WPS_OOB_PUBKEY_HASH_LEN);
+ if (oob_conf->pubkey_hash == NULL) {
+ wpa_printf(MSG_ERROR, "WPS: Failed to allocate memory for OOB "
+ "public key hash");
+ return -1;
+ }
+ pos += WPS_OOB_PUBKEY_HASH_LEN;
+
+ wps->oob_dev_pw_id = WPA_GET_BE16(pos);
+ pos += sizeof(wps->oob_dev_pw_id);
+
+ oob_conf->dev_password =
+ wpabuf_alloc(WPS_OOB_DEVICE_PASSWORD_LEN * 2 + 1);
+ if (oob_conf->dev_password == NULL) {
+ wpa_printf(MSG_ERROR, "WPS: Failed to allocate memory for OOB "
+ "device password");
+ return -1;
+ }
+ wpa_snprintf_hex_uppercase(wpabuf_put(oob_conf->dev_password,
+ wpabuf_size(oob_conf->dev_password)),
+ wpabuf_size(oob_conf->dev_password), pos,
+ WPS_OOB_DEVICE_PASSWORD_LEN);
+
+ return 0;
+}
+
+
+static int wps_parse_oob_cred(struct wps_context *wps, struct wpabuf *data)
+{
+ struct wpabuf msg;
+ struct wps_parse_attr attr;
+ size_t i;
+
+ if (wps_parse_msg(data, &attr) < 0 || attr.num_cred <= 0) {
+ wpa_printf(MSG_ERROR, "WPS: OOB credential not found");
+ return -1;
+ }
+
+ for (i = 0; i < attr.num_cred; i++) {
+ struct wps_credential local_cred;
+ struct wps_parse_attr cattr;
+
+ os_memset(&local_cred, 0, sizeof(local_cred));
+ wpabuf_set(&msg, attr.cred[i], attr.cred_len[i]);
+ if (wps_parse_msg(&msg, &cattr) < 0 ||
+ wps_process_cred(&cattr, &local_cred)) {
+ wpa_printf(MSG_ERROR, "WPS: Failed to parse OOB "
+ "credential");
+ return -1;
+ }
+ wps->cred_cb(wps->cb_ctx, &local_cred);
+ }
+
+ return 0;
+}
+
+
+int wps_process_oob(struct wps_context *wps, struct oob_device_data *oob_dev,
+ int registrar)
+{
+ struct wpabuf *data;
+ int ret, write_f, oob_method = wps->oob_conf.oob_method;
+ void *oob_priv;
+
+ write_f = oob_method == OOB_METHOD_DEV_PWD_E ? !registrar : registrar;
+
+ oob_priv = oob_dev->init_func(wps, oob_dev, registrar);
+ if (oob_priv == NULL) {
+ wpa_printf(MSG_ERROR, "WPS: Failed to initialize OOB device");
+ return -1;
+ }
+
+ if (write_f) {
+ if (oob_method == OOB_METHOD_CRED)
+ data = wps_get_oob_cred(wps);
+ else
+ data = wps_get_oob_dev_pwd(wps);
+
+ ret = 0;
+ if (data == NULL || oob_dev->write_func(oob_priv, data) < 0)
+ ret = -1;
+ } else {
+ data = oob_dev->read_func(oob_priv);
+ if (data == NULL)
+ ret = -1;
+ else {
+ if (oob_method == OOB_METHOD_CRED)
+ ret = wps_parse_oob_cred(wps, data);
+ else
+ ret = wps_parse_oob_dev_pwd(wps, data);
+ }
+ }
+ wpabuf_free(data);
+ oob_dev->deinit_func(oob_priv);
+
+ if (ret < 0) {
+ wpa_printf(MSG_ERROR, "WPS: Failed to process OOB data");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+struct oob_device_data * wps_get_oob_device(char *device_type)
+{
+#ifdef CONFIG_WPS_UFD
+ if (os_strstr(device_type, "ufd") != NULL)
+ return &oob_ufd_device_data;
+#endif /* CONFIG_WPS_UFD */
+#ifdef CONFIG_WPS_NFC
+ if (os_strstr(device_type, "nfc") != NULL)
+ return &oob_nfc_device_data;
+#endif /* CONFIG_WPS_NFC */
+
+ return NULL;
+}
+
+
+#ifdef CONFIG_WPS_NFC
+struct oob_nfc_device_data * wps_get_oob_nfc_device(char *device_name)
+{
+ if (device_name == NULL)
+ return NULL;
+#ifdef CONFIG_WPS_NFC_PN531
+ if (os_strstr(device_name, "pn531") != NULL)
+ return &oob_nfc_pn531_device_data;
+#endif /* CONFIG_WPS_NFC_PN531 */
+
+ return NULL;
+}
+#endif /* CONFIG_WPS_NFC */
+
+
+int wps_get_oob_method(char *method)
+{
+ if (os_strstr(method, "pin-e") != NULL)
+ return OOB_METHOD_DEV_PWD_E;
+ if (os_strstr(method, "pin-r") != NULL)
+ return OOB_METHOD_DEV_PWD_R;
+ if (os_strstr(method, "cred") != NULL)
+ return OOB_METHOD_CRED;
+ return OOB_METHOD_UNKNOWN;
+}
+
+#endif /* CONFIG_WPS_OOB */
+
+
+int wps_dev_type_str2bin(const char *str, u8 dev_type[WPS_DEV_TYPE_LEN])
+{
+ const char *pos;
+
+ /* <categ>-<OUI>-<subcateg> */
+ WPA_PUT_BE16(dev_type, atoi(str));
+ pos = os_strchr(str, '-');
+ if (pos == NULL)
+ return -1;
+ pos++;
+ if (hexstr2bin(pos, &dev_type[2], 4))
+ return -1;
+ pos = os_strchr(pos, '-');
+ if (pos == NULL)
+ return -1;
+ pos++;
+ WPA_PUT_BE16(&dev_type[6], atoi(pos));
+
+
+ return 0;
+}
+
+
+char * wps_dev_type_bin2str(const u8 dev_type[WPS_DEV_TYPE_LEN], char *buf,
+ size_t buf_len)
+{
+ int ret;
+
+ ret = os_snprintf(buf, buf_len, "%u-%08X-%u",
+ WPA_GET_BE16(dev_type), WPA_GET_BE32(&dev_type[2]),
+ WPA_GET_BE16(&dev_type[6]));
+ if (ret < 0 || (unsigned int) ret >= buf_len)
+ return NULL;
+
+ return buf;
+}
+
+
+void uuid_gen_mac_addr(const u8 *mac_addr, u8 *uuid)
+{
+ const u8 *addr[2];
+ size_t len[2];
+ u8 hash[SHA1_MAC_LEN];
+ u8 nsid[16] = {
+ 0x52, 0x64, 0x80, 0xf8,
+ 0xc9, 0x9b,
+ 0x4b, 0xe5,
+ 0xa6, 0x55,
+ 0x58, 0xed, 0x5f, 0x5d, 0x60, 0x84
+ };
+
+ addr[0] = nsid;
+ len[0] = sizeof(nsid);
+ addr[1] = mac_addr;
+ len[1] = 6;
+ sha1_vector(2, addr, len, hash);
+ os_memcpy(uuid, hash, 16);
+
+ /* Version: 5 = named-based version using SHA-1 */
+ uuid[6] = (5 << 4) | (uuid[6] & 0x0f);
+
+ /* Variant specified in RFC 4122 */
+ uuid[8] = 0x80 | (uuid[8] & 0x3f);
+}
+
+
+u16 wps_config_methods_str2bin(const char *str)
+{
+ u16 methods = 0;
+
+ if (str == NULL) {
+ /* Default to enabling methods based on build configuration */
+ methods |= WPS_CONFIG_DISPLAY | WPS_CONFIG_KEYPAD;
+#ifdef CONFIG_WPS2
+ methods |= WPS_CONFIG_VIRT_DISPLAY;
+#endif /* CONFIG_WPS2 */
+#ifdef CONFIG_WPS_UFD
+ methods |= WPS_CONFIG_USBA;
+#endif /* CONFIG_WPS_UFD */
+#ifdef CONFIG_WPS_NFC
+ methods |= WPS_CONFIG_NFC_INTERFACE;
+#endif /* CONFIG_WPS_NFC */
+ } else {
+ if (os_strstr(str, "usba"))
+ methods |= WPS_CONFIG_USBA;
+ if (os_strstr(str, "ethernet"))
+ methods |= WPS_CONFIG_ETHERNET;
+ if (os_strstr(str, "label"))
+ methods |= WPS_CONFIG_LABEL;
+ if (os_strstr(str, "display"))
+ methods |= WPS_CONFIG_DISPLAY;
+ if (os_strstr(str, "ext_nfc_token"))
+ methods |= WPS_CONFIG_EXT_NFC_TOKEN;
+ if (os_strstr(str, "int_nfc_token"))
+ methods |= WPS_CONFIG_INT_NFC_TOKEN;
+ if (os_strstr(str, "nfc_interface"))
+ methods |= WPS_CONFIG_NFC_INTERFACE;
+ if (os_strstr(str, "push_button"))
+ methods |= WPS_CONFIG_PUSHBUTTON;
+ if (os_strstr(str, "keypad"))
+ methods |= WPS_CONFIG_KEYPAD;
+#ifdef CONFIG_WPS2
+ if (os_strstr(str, "virtual_display"))
+ methods |= WPS_CONFIG_VIRT_DISPLAY;
+ if (os_strstr(str, "physical_display"))
+ methods |= WPS_CONFIG_PHY_DISPLAY;
+ if (os_strstr(str, "virtual_push_button"))
+ methods |= WPS_CONFIG_VIRT_PUSHBUTTON;
+ if (os_strstr(str, "physical_push_button"))
+ methods |= WPS_CONFIG_PHY_PUSHBUTTON;
+#endif /* CONFIG_WPS2 */
+ }
+
+ return methods;
+}
+
+
+struct wpabuf * wps_build_wsc_ack(struct wps_data *wps)
+{
+ struct wpabuf *msg;
+
+ wpa_printf(MSG_DEBUG, "WPS: Building Message WSC_ACK");
+
+ msg = wpabuf_alloc(1000);
+ if (msg == NULL)
+ return NULL;
+
+ if (wps_build_version(msg) ||
+ wps_build_msg_type(msg, WPS_WSC_ACK) ||
+ wps_build_enrollee_nonce(wps, msg) ||
+ wps_build_registrar_nonce(wps, msg) ||
+ wps_build_wfa_ext(msg, 0, NULL, 0)) {
+ wpabuf_free(msg);
+ return NULL;
+ }
+
+ return msg;
+}
+
+
+struct wpabuf * wps_build_wsc_nack(struct wps_data *wps)
+{
+ struct wpabuf *msg;
+
+ wpa_printf(MSG_DEBUG, "WPS: Building Message WSC_NACK");
+
+ msg = wpabuf_alloc(1000);
+ if (msg == NULL)
+ return NULL;
+
+ if (wps_build_version(msg) ||
+ wps_build_msg_type(msg, WPS_WSC_NACK) ||
+ wps_build_enrollee_nonce(wps, msg) ||
+ wps_build_registrar_nonce(wps, msg) ||
+ wps_build_config_error(msg, wps->config_error) ||
+ wps_build_wfa_ext(msg, 0, NULL, 0)) {
+ wpabuf_free(msg);
+ return NULL;
+ }
+
+ return msg;
+}
diff --git a/src/wps/wps_defs.h b/src/wps/wps_defs.h
new file mode 100644
index 0000000..43311f3
--- /dev/null
+++ b/src/wps/wps_defs.h
@@ -0,0 +1,336 @@
+/*
+ * Wi-Fi Protected Setup - message definitions
+ * Copyright (c) 2008, 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.
+ */
+
+#ifndef WPS_DEFS_H
+#define WPS_DEFS_H
+
+#ifdef CONFIG_WPS_TESTING
+
+extern int wps_version_number;
+extern int wps_testing_dummy_cred;
+#define WPS_VERSION wps_version_number
+
+#else /* CONFIG_WPS_TESTING */
+
+#ifdef CONFIG_WPS2
+#define WPS_VERSION 0x20
+#else /* CONFIG_WPS2 */
+#define WPS_VERSION 0x10
+#endif /* CONFIG_WPS2 */
+
+#endif /* CONFIG_WPS_TESTING */
+
+/* Diffie-Hellman 1536-bit MODP Group; RFC 3526, Group 5 */
+#define WPS_DH_GROUP 5
+
+#define WPS_UUID_LEN 16
+#define WPS_NONCE_LEN 16
+#define WPS_AUTHENTICATOR_LEN 8
+#define WPS_AUTHKEY_LEN 32
+#define WPS_KEYWRAPKEY_LEN 16
+#define WPS_EMSK_LEN 32
+#define WPS_PSK_LEN 16
+#define WPS_SECRET_NONCE_LEN 16
+#define WPS_HASH_LEN 32
+#define WPS_KWA_LEN 8
+#define WPS_MGMTAUTHKEY_LEN 32
+#define WPS_MGMTENCKEY_LEN 16
+#define WPS_MGMT_KEY_ID_LEN 16
+#define WPS_OOB_DEVICE_PASSWORD_ATTR_LEN 54
+#define WPS_OOB_DEVICE_PASSWORD_LEN 32
+#define WPS_OOB_PUBKEY_HASH_LEN 20
+
+/* Attribute Types */
+enum wps_attribute {
+ ATTR_AP_CHANNEL = 0x1001,
+ ATTR_ASSOC_STATE = 0x1002,
+ ATTR_AUTH_TYPE = 0x1003,
+ ATTR_AUTH_TYPE_FLAGS = 0x1004,
+ ATTR_AUTHENTICATOR = 0x1005,
+ ATTR_CONFIG_METHODS = 0x1008,
+ ATTR_CONFIG_ERROR = 0x1009,
+ ATTR_CONFIRM_URL4 = 0x100a,
+ ATTR_CONFIRM_URL6 = 0x100b,
+ ATTR_CONN_TYPE = 0x100c,
+ ATTR_CONN_TYPE_FLAGS = 0x100d,
+ ATTR_CRED = 0x100e,
+ ATTR_ENCR_TYPE = 0x100f,
+ ATTR_ENCR_TYPE_FLAGS = 0x1010,
+ ATTR_DEV_NAME = 0x1011,
+ ATTR_DEV_PASSWORD_ID = 0x1012,
+ ATTR_E_HASH1 = 0x1014,
+ ATTR_E_HASH2 = 0x1015,
+ ATTR_E_SNONCE1 = 0x1016,
+ ATTR_E_SNONCE2 = 0x1017,
+ ATTR_ENCR_SETTINGS = 0x1018,
+ ATTR_ENROLLEE_NONCE = 0x101a,
+ ATTR_FEATURE_ID = 0x101b,
+ ATTR_IDENTITY = 0x101c,
+ ATTR_IDENTITY_PROOF = 0x101d,
+ ATTR_KEY_WRAP_AUTH = 0x101e,
+ ATTR_KEY_ID = 0x101f,
+ ATTR_MAC_ADDR = 0x1020,
+ ATTR_MANUFACTURER = 0x1021,
+ ATTR_MSG_TYPE = 0x1022,
+ ATTR_MODEL_NAME = 0x1023,
+ ATTR_MODEL_NUMBER = 0x1024,
+ ATTR_NETWORK_INDEX = 0x1026,
+ ATTR_NETWORK_KEY = 0x1027,
+ ATTR_NETWORK_KEY_INDEX = 0x1028,
+ ATTR_NEW_DEVICE_NAME = 0x1029,
+ ATTR_NEW_PASSWORD = 0x102a,
+ ATTR_OOB_DEVICE_PASSWORD = 0x102c,
+ ATTR_OS_VERSION = 0x102d,
+ ATTR_POWER_LEVEL = 0x102f,
+ ATTR_PSK_CURRENT = 0x1030,
+ ATTR_PSK_MAX = 0x1031,
+ ATTR_PUBLIC_KEY = 0x1032,
+ ATTR_RADIO_ENABLE = 0x1033,
+ ATTR_REBOOT = 0x1034,
+ ATTR_REGISTRAR_CURRENT = 0x1035,
+ ATTR_REGISTRAR_ESTABLISHED = 0x1036,
+ ATTR_REGISTRAR_LIST = 0x1037,
+ ATTR_REGISTRAR_MAX = 0x1038,
+ ATTR_REGISTRAR_NONCE = 0x1039,
+ ATTR_REQUEST_TYPE = 0x103a,
+ ATTR_RESPONSE_TYPE = 0x103b,
+ ATTR_RF_BANDS = 0x103c,
+ ATTR_R_HASH1 = 0x103d,
+ ATTR_R_HASH2 = 0x103e,
+ ATTR_R_SNONCE1 = 0x103f,
+ ATTR_R_SNONCE2 = 0x1040,
+ ATTR_SELECTED_REGISTRAR = 0x1041,
+ ATTR_SERIAL_NUMBER = 0x1042,
+ ATTR_WPS_STATE = 0x1044,
+ ATTR_SSID = 0x1045,
+ ATTR_TOTAL_NETWORKS = 0x1046,
+ ATTR_UUID_E = 0x1047,
+ ATTR_UUID_R = 0x1048,
+ ATTR_VENDOR_EXT = 0x1049,
+ ATTR_VERSION = 0x104a,
+ ATTR_X509_CERT_REQ = 0x104b,
+ ATTR_X509_CERT = 0x104c,
+ ATTR_EAP_IDENTITY = 0x104d,
+ ATTR_MSG_COUNTER = 0x104e,
+ ATTR_PUBKEY_HASH = 0x104f,
+ ATTR_REKEY_KEY = 0x1050,
+ ATTR_KEY_LIFETIME = 0x1051,
+ ATTR_PERMITTED_CFG_METHODS = 0x1052,
+ ATTR_SELECTED_REGISTRAR_CONFIG_METHODS = 0x1053,
+ ATTR_PRIMARY_DEV_TYPE = 0x1054,
+ ATTR_SECONDARY_DEV_TYPE_LIST = 0x1055,
+ ATTR_PORTABLE_DEV = 0x1056,
+ ATTR_AP_SETUP_LOCKED = 0x1057,
+ ATTR_APPLICATION_EXT = 0x1058,
+ ATTR_EAP_TYPE = 0x1059,
+ ATTR_IV = 0x1060,
+ ATTR_KEY_PROVIDED_AUTO = 0x1061,
+ ATTR_802_1X_ENABLED = 0x1062,
+ ATTR_APPSESSIONKEY = 0x1063,
+ ATTR_WEPTRANSMITKEY = 0x1064,
+ ATTR_REQUESTED_DEV_TYPE = 0x106a,
+ ATTR_EXTENSIBILITY_TEST = 0x10fa /* _NOT_ defined in the spec */
+};
+
+#define WPS_VENDOR_ID_WFA 14122
+
+/* WFA Vendor Extension subelements */
+enum {
+ WFA_ELEM_VERSION2 = 0x00,
+ WFA_ELEM_AUTHORIZEDMACS = 0x01,
+ WFA_ELEM_NETWORK_KEY_SHAREABLE = 0x02,
+ WFA_ELEM_REQUEST_TO_ENROLL = 0x03,
+ WFA_ELEM_SETTINGS_DELAY_TIME = 0x04
+};
+
+/* Device Password ID */
+enum wps_dev_password_id {
+ DEV_PW_DEFAULT = 0x0000,
+ DEV_PW_USER_SPECIFIED = 0x0001,
+ DEV_PW_MACHINE_SPECIFIED = 0x0002,
+ DEV_PW_REKEY = 0x0003,
+ DEV_PW_PUSHBUTTON = 0x0004,
+ DEV_PW_REGISTRAR_SPECIFIED = 0x0005
+};
+
+/* Message Type */
+enum wps_msg_type {
+ WPS_Beacon = 0x01,
+ WPS_ProbeRequest = 0x02,
+ WPS_ProbeResponse = 0x03,
+ WPS_M1 = 0x04,
+ WPS_M2 = 0x05,
+ WPS_M2D = 0x06,
+ WPS_M3 = 0x07,
+ WPS_M4 = 0x08,
+ WPS_M5 = 0x09,
+ WPS_M6 = 0x0a,
+ WPS_M7 = 0x0b,
+ WPS_M8 = 0x0c,
+ WPS_WSC_ACK = 0x0d,
+ WPS_WSC_NACK = 0x0e,
+ WPS_WSC_DONE = 0x0f
+};
+
+/* Authentication Type Flags */
+#define WPS_AUTH_OPEN 0x0001
+#define WPS_AUTH_WPAPSK 0x0002
+#define WPS_AUTH_SHARED 0x0004
+#define WPS_AUTH_WPA 0x0008
+#define WPS_AUTH_WPA2 0x0010
+#define WPS_AUTH_WPA2PSK 0x0020
+#define WPS_AUTH_TYPES (WPS_AUTH_OPEN | WPS_AUTH_WPAPSK | WPS_AUTH_SHARED | \
+ WPS_AUTH_WPA | WPS_AUTH_WPA2 | WPS_AUTH_WPA2PSK)
+
+/* Encryption Type Flags */
+#define WPS_ENCR_NONE 0x0001
+#define WPS_ENCR_WEP 0x0002
+#define WPS_ENCR_TKIP 0x0004
+#define WPS_ENCR_AES 0x0008
+#define WPS_ENCR_TYPES (WPS_ENCR_NONE | WPS_ENCR_WEP | WPS_ENCR_TKIP | \
+ WPS_ENCR_AES)
+
+/* Configuration Error */
+enum wps_config_error {
+ WPS_CFG_NO_ERROR = 0,
+ WPS_CFG_OOB_IFACE_READ_ERROR = 1,
+ WPS_CFG_DECRYPTION_CRC_FAILURE = 2,
+ WPS_CFG_24_CHAN_NOT_SUPPORTED = 3,
+ WPS_CFG_50_CHAN_NOT_SUPPORTED = 4,
+ WPS_CFG_SIGNAL_TOO_WEAK = 5,
+ WPS_CFG_NETWORK_AUTH_FAILURE = 6,
+ WPS_CFG_NETWORK_ASSOC_FAILURE = 7,
+ WPS_CFG_NO_DHCP_RESPONSE = 8,
+ WPS_CFG_FAILED_DHCP_CONFIG = 9,
+ WPS_CFG_IP_ADDR_CONFLICT = 10,
+ WPS_CFG_NO_CONN_TO_REGISTRAR = 11,
+ WPS_CFG_MULTIPLE_PBC_DETECTED = 12,
+ WPS_CFG_ROGUE_SUSPECTED = 13,
+ WPS_CFG_DEVICE_BUSY = 14,
+ WPS_CFG_SETUP_LOCKED = 15,
+ WPS_CFG_MSG_TIMEOUT = 16,
+ WPS_CFG_REG_SESS_TIMEOUT = 17,
+ WPS_CFG_DEV_PASSWORD_AUTH_FAILURE = 18
+};
+
+/* Vendor specific Error Indication for WPS event messages */
+enum wps_error_indication {
+ WPS_EI_NO_ERROR,
+ WPS_EI_SECURITY_TKIP_ONLY_PROHIBITED,
+ WPS_EI_SECURITY_WEP_PROHIBITED,
+ NUM_WPS_EI_VALUES
+};
+
+/* RF Bands */
+#define WPS_RF_24GHZ 0x01
+#define WPS_RF_50GHZ 0x02
+
+/* Config Methods */
+#define WPS_CONFIG_USBA 0x0001
+#define WPS_CONFIG_ETHERNET 0x0002
+#define WPS_CONFIG_LABEL 0x0004
+#define WPS_CONFIG_DISPLAY 0x0008
+#define WPS_CONFIG_EXT_NFC_TOKEN 0x0010
+#define WPS_CONFIG_INT_NFC_TOKEN 0x0020
+#define WPS_CONFIG_NFC_INTERFACE 0x0040
+#define WPS_CONFIG_PUSHBUTTON 0x0080
+#define WPS_CONFIG_KEYPAD 0x0100
+#ifdef CONFIG_WPS2
+#define WPS_CONFIG_VIRT_PUSHBUTTON 0x0280
+#define WPS_CONFIG_PHY_PUSHBUTTON 0x0480
+#define WPS_CONFIG_VIRT_DISPLAY 0x2008
+#define WPS_CONFIG_PHY_DISPLAY 0x4008
+#endif /* CONFIG_WPS2 */
+
+/* Connection Type Flags */
+#define WPS_CONN_ESS 0x01
+#define WPS_CONN_IBSS 0x02
+
+/* Wi-Fi Protected Setup State */
+enum wps_state {
+ WPS_STATE_NOT_CONFIGURED = 1,
+ WPS_STATE_CONFIGURED = 2
+};
+
+/* Association State */
+enum wps_assoc_state {
+ WPS_ASSOC_NOT_ASSOC = 0,
+ WPS_ASSOC_CONN_SUCCESS = 1,
+ WPS_ASSOC_CFG_FAILURE = 2,
+ WPS_ASSOC_FAILURE = 3,
+ WPS_ASSOC_IP_FAILURE = 4
+};
+
+
+#define WPS_DEV_OUI_WFA 0x0050f204
+
+enum wps_dev_categ {
+ WPS_DEV_COMPUTER = 1,
+ WPS_DEV_INPUT = 2,
+ WPS_DEV_PRINTER = 3,
+ WPS_DEV_CAMERA = 4,
+ WPS_DEV_STORAGE = 5,
+ WPS_DEV_NETWORK_INFRA = 6,
+ WPS_DEV_DISPLAY = 7,
+ WPS_DEV_MULTIMEDIA = 8,
+ WPS_DEV_GAMING = 9,
+ WPS_DEV_PHONE = 10
+};
+
+enum wps_dev_subcateg {
+ WPS_DEV_COMPUTER_PC = 1,
+ WPS_DEV_COMPUTER_SERVER = 2,
+ WPS_DEV_COMPUTER_MEDIA_CENTER = 3,
+ WPS_DEV_PRINTER_PRINTER = 1,
+ WPS_DEV_PRINTER_SCANNER = 2,
+ WPS_DEV_CAMERA_DIGITAL_STILL_CAMERA = 1,
+ WPS_DEV_STORAGE_NAS = 1,
+ WPS_DEV_NETWORK_INFRA_AP = 1,
+ WPS_DEV_NETWORK_INFRA_ROUTER = 2,
+ WPS_DEV_NETWORK_INFRA_SWITCH = 3,
+ WPS_DEV_DISPLAY_TV = 1,
+ WPS_DEV_DISPLAY_PICTURE_FRAME = 2,
+ WPS_DEV_DISPLAY_PROJECTOR = 3,
+ WPS_DEV_MULTIMEDIA_DAR = 1,
+ WPS_DEV_MULTIMEDIA_PVR = 2,
+ WPS_DEV_MULTIMEDIA_MCX = 3,
+ WPS_DEV_GAMING_XBOX = 1,
+ WPS_DEV_GAMING_XBOX360 = 2,
+ WPS_DEV_GAMING_PLAYSTATION = 3,
+ WPS_DEV_PHONE_WINDOWS_MOBILE = 1
+};
+
+
+/* Request Type */
+enum wps_request_type {
+ WPS_REQ_ENROLLEE_INFO = 0,
+ WPS_REQ_ENROLLEE = 1,
+ WPS_REQ_REGISTRAR = 2,
+ WPS_REQ_WLAN_MANAGER_REGISTRAR = 3
+};
+
+/* Response Type */
+enum wps_response_type {
+ WPS_RESP_ENROLLEE_INFO = 0,
+ WPS_RESP_ENROLLEE = 1,
+ WPS_RESP_REGISTRAR = 2,
+ WPS_RESP_AP = 3
+};
+
+/* Walk Time for push button configuration (in seconds) */
+#define WPS_PBC_WALK_TIME 120
+
+#define WPS_MAX_AUTHORIZED_MACS 5
+
+#endif /* WPS_DEFS_H */
diff --git a/src/wps/wps_dev_attr.c b/src/wps/wps_dev_attr.c
new file mode 100644
index 0000000..f2fb03a
--- /dev/null
+++ b/src/wps/wps_dev_attr.c
@@ -0,0 +1,444 @@
+/*
+ * Wi-Fi Protected Setup - device attributes
+ * Copyright (c) 2008, 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.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "wps_i.h"
+#include "wps_dev_attr.h"
+
+
+int wps_build_manufacturer(struct wps_device_data *dev, struct wpabuf *msg)
+{
+ size_t len;
+ wpa_printf(MSG_DEBUG, "WPS: * Manufacturer");
+ wpabuf_put_be16(msg, ATTR_MANUFACTURER);
+ len = dev->manufacturer ? os_strlen(dev->manufacturer) : 0;
+#ifndef CONFIG_WPS_STRICT
+ if (len == 0) {
+ /*
+ * Some deployed WPS implementations fail to parse zero-length
+ * attributes. As a workaround, send a space character if the
+ * device attribute string is empty.
+ */
+ wpabuf_put_be16(msg, 1);
+ wpabuf_put_u8(msg, ' ');
+ return 0;
+ }
+#endif /* CONFIG_WPS_STRICT */
+ wpabuf_put_be16(msg, len);
+ wpabuf_put_data(msg, dev->manufacturer, len);
+ return 0;
+}
+
+
+int wps_build_model_name(struct wps_device_data *dev, struct wpabuf *msg)
+{
+ size_t len;
+ wpa_printf(MSG_DEBUG, "WPS: * Model Name");
+ wpabuf_put_be16(msg, ATTR_MODEL_NAME);
+ len = dev->model_name ? os_strlen(dev->model_name) : 0;
+#ifndef CONFIG_WPS_STRICT
+ if (len == 0) {
+ /*
+ * Some deployed WPS implementations fail to parse zero-length
+ * attributes. As a workaround, send a space character if the
+ * device attribute string is empty.
+ */
+ wpabuf_put_be16(msg, 1);
+ wpabuf_put_u8(msg, ' ');
+ return 0;
+ }
+#endif /* CONFIG_WPS_STRICT */
+ wpabuf_put_be16(msg, len);
+ wpabuf_put_data(msg, dev->model_name, len);
+ return 0;
+}
+
+
+int wps_build_model_number(struct wps_device_data *dev, struct wpabuf *msg)
+{
+ size_t len;
+ wpa_printf(MSG_DEBUG, "WPS: * Model Number");
+ wpabuf_put_be16(msg, ATTR_MODEL_NUMBER);
+ len = dev->model_number ? os_strlen(dev->model_number) : 0;
+#ifndef CONFIG_WPS_STRICT
+ if (len == 0) {
+ /*
+ * Some deployed WPS implementations fail to parse zero-length
+ * attributes. As a workaround, send a space character if the
+ * device attribute string is empty.
+ */
+ wpabuf_put_be16(msg, 1);
+ wpabuf_put_u8(msg, ' ');
+ return 0;
+ }
+#endif /* CONFIG_WPS_STRICT */
+ wpabuf_put_be16(msg, len);
+ wpabuf_put_data(msg, dev->model_number, len);
+ return 0;
+}
+
+
+static int wps_build_serial_number(struct wps_device_data *dev,
+ struct wpabuf *msg)
+{
+ size_t len;
+ wpa_printf(MSG_DEBUG, "WPS: * Serial Number");
+ wpabuf_put_be16(msg, ATTR_SERIAL_NUMBER);
+ len = dev->serial_number ? os_strlen(dev->serial_number) : 0;
+#ifndef CONFIG_WPS_STRICT
+ if (len == 0) {
+ /*
+ * Some deployed WPS implementations fail to parse zero-length
+ * attributes. As a workaround, send a space character if the
+ * device attribute string is empty.
+ */
+ wpabuf_put_be16(msg, 1);
+ wpabuf_put_u8(msg, ' ');
+ return 0;
+ }
+#endif /* CONFIG_WPS_STRICT */
+ wpabuf_put_be16(msg, len);
+ wpabuf_put_data(msg, dev->serial_number, len);
+ return 0;
+}
+
+
+int wps_build_primary_dev_type(struct wps_device_data *dev, struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * Primary Device Type");
+ wpabuf_put_be16(msg, ATTR_PRIMARY_DEV_TYPE);
+ wpabuf_put_be16(msg, WPS_DEV_TYPE_LEN);
+ wpabuf_put_data(msg, dev->pri_dev_type, WPS_DEV_TYPE_LEN);
+ return 0;
+}
+
+
+int wps_build_secondary_dev_type(struct wps_device_data *dev,
+ struct wpabuf *msg)
+{
+ if (!dev->num_sec_dev_types)
+ return 0;
+
+ wpa_printf(MSG_DEBUG, "WPS: * Secondary Device Type");
+ wpabuf_put_be16(msg, ATTR_SECONDARY_DEV_TYPE_LIST);
+ wpabuf_put_be16(msg, WPS_DEV_TYPE_LEN * dev->num_sec_dev_types);
+ wpabuf_put_data(msg, dev->sec_dev_type,
+ WPS_DEV_TYPE_LEN * dev->num_sec_dev_types);
+
+ return 0;
+}
+
+
+int wps_build_req_dev_type(struct wps_device_data *dev, struct wpabuf *msg,
+ unsigned int num_req_dev_types,
+ const u8 *req_dev_types)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_req_dev_types; i++) {
+ wpa_hexdump(MSG_DEBUG, "WPS: * Requested Device Type",
+ req_dev_types + i * WPS_DEV_TYPE_LEN,
+ WPS_DEV_TYPE_LEN);
+ wpabuf_put_be16(msg, ATTR_REQUESTED_DEV_TYPE);
+ wpabuf_put_be16(msg, WPS_DEV_TYPE_LEN);
+ wpabuf_put_data(msg, req_dev_types + i * WPS_DEV_TYPE_LEN,
+ WPS_DEV_TYPE_LEN);
+ }
+
+ return 0;
+}
+
+
+int wps_build_dev_name(struct wps_device_data *dev, struct wpabuf *msg)
+{
+ size_t len;
+ wpa_printf(MSG_DEBUG, "WPS: * Device Name");
+ wpabuf_put_be16(msg, ATTR_DEV_NAME);
+ len = dev->device_name ? os_strlen(dev->device_name) : 0;
+#ifndef CONFIG_WPS_STRICT
+ if (len == 0) {
+ /*
+ * Some deployed WPS implementations fail to parse zero-length
+ * attributes. As a workaround, send a space character if the
+ * device attribute string is empty.
+ */
+ wpabuf_put_be16(msg, 1);
+ wpabuf_put_u8(msg, ' ');
+ return 0;
+ }
+#endif /* CONFIG_WPS_STRICT */
+ wpabuf_put_be16(msg, len);
+ wpabuf_put_data(msg, dev->device_name, len);
+ return 0;
+}
+
+
+int wps_build_device_attrs(struct wps_device_data *dev, struct wpabuf *msg)
+{
+ if (wps_build_manufacturer(dev, msg) ||
+ wps_build_model_name(dev, msg) ||
+ wps_build_model_number(dev, msg) ||
+ wps_build_serial_number(dev, msg) ||
+ wps_build_primary_dev_type(dev, msg) ||
+ wps_build_dev_name(dev, msg))
+ return -1;
+ return 0;
+}
+
+
+int wps_build_os_version(struct wps_device_data *dev, struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * OS Version");
+ wpabuf_put_be16(msg, ATTR_OS_VERSION);
+ wpabuf_put_be16(msg, 4);
+ wpabuf_put_be32(msg, 0x80000000 | dev->os_version);
+ return 0;
+}
+
+
+int wps_build_rf_bands(struct wps_device_data *dev, struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * RF Bands (%x)", dev->rf_bands);
+ wpabuf_put_be16(msg, ATTR_RF_BANDS);
+ wpabuf_put_be16(msg, 1);
+ wpabuf_put_u8(msg, dev->rf_bands);
+ return 0;
+}
+
+
+int wps_build_vendor_ext(struct wps_device_data *dev, struct wpabuf *msg)
+{
+ int i;
+
+ for (i = 0; i < MAX_WPS_VENDOR_EXTENSIONS; i++) {
+ if (dev->vendor_ext[i] == NULL)
+ continue;
+ wpa_hexdump(MSG_DEBUG, "WPS: * Vendor Extension",
+ wpabuf_head_u8(dev->vendor_ext[i]),
+ wpabuf_len(dev->vendor_ext[i]));
+ wpabuf_put_be16(msg, ATTR_VENDOR_EXT);
+ wpabuf_put_be16(msg, wpabuf_len(dev->vendor_ext[i]));
+ wpabuf_put_buf(msg, dev->vendor_ext[i]);
+ }
+
+ return 0;
+}
+
+
+static int wps_process_manufacturer(struct wps_device_data *dev, const u8 *str,
+ size_t str_len)
+{
+ if (str == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Manufacturer received");
+ return -1;
+ }
+
+ wpa_hexdump_ascii(MSG_DEBUG, "WPS: Manufacturer", str, str_len);
+
+ os_free(dev->manufacturer);
+ dev->manufacturer = os_malloc(str_len + 1);
+ if (dev->manufacturer == NULL)
+ return -1;
+ os_memcpy(dev->manufacturer, str, str_len);
+ dev->manufacturer[str_len] = '\0';
+
+ return 0;
+}
+
+
+static int wps_process_model_name(struct wps_device_data *dev, const u8 *str,
+ size_t str_len)
+{
+ if (str == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Model Name received");
+ return -1;
+ }
+
+ wpa_hexdump_ascii(MSG_DEBUG, "WPS: Model Name", str, str_len);
+
+ os_free(dev->model_name);
+ dev->model_name = os_malloc(str_len + 1);
+ if (dev->model_name == NULL)
+ return -1;
+ os_memcpy(dev->model_name, str, str_len);
+ dev->model_name[str_len] = '\0';
+
+ return 0;
+}
+
+
+static int wps_process_model_number(struct wps_device_data *dev, const u8 *str,
+ size_t str_len)
+{
+ if (str == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Model Number received");
+ return -1;
+ }
+
+ wpa_hexdump_ascii(MSG_DEBUG, "WPS: Model Number", str, str_len);
+
+ os_free(dev->model_number);
+ dev->model_number = os_malloc(str_len + 1);
+ if (dev->model_number == NULL)
+ return -1;
+ os_memcpy(dev->model_number, str, str_len);
+ dev->model_number[str_len] = '\0';
+
+ return 0;
+}
+
+
+static int wps_process_serial_number(struct wps_device_data *dev,
+ const u8 *str, size_t str_len)
+{
+ if (str == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Serial Number received");
+ return -1;
+ }
+
+ wpa_hexdump_ascii(MSG_DEBUG, "WPS: Serial Number", str, str_len);
+
+ os_free(dev->serial_number);
+ dev->serial_number = os_malloc(str_len + 1);
+ if (dev->serial_number == NULL)
+ return -1;
+ os_memcpy(dev->serial_number, str, str_len);
+ dev->serial_number[str_len] = '\0';
+
+ return 0;
+}
+
+
+static int wps_process_dev_name(struct wps_device_data *dev, const u8 *str,
+ size_t str_len)
+{
+ if (str == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Device Name received");
+ return -1;
+ }
+
+ wpa_hexdump_ascii(MSG_DEBUG, "WPS: Device Name", str, str_len);
+
+ os_free(dev->device_name);
+ dev->device_name = os_malloc(str_len + 1);
+ if (dev->device_name == NULL)
+ return -1;
+ os_memcpy(dev->device_name, str, str_len);
+ dev->device_name[str_len] = '\0';
+
+ return 0;
+}
+
+
+static int wps_process_primary_dev_type(struct wps_device_data *dev,
+ const u8 *dev_type)
+{
+#ifndef CONFIG_NO_STDOUT_DEBUG
+ char devtype[WPS_DEV_TYPE_BUFSIZE];
+#endif /* CONFIG_NO_STDOUT_DEBUG */
+
+ if (dev_type == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Primary Device Type received");
+ return -1;
+ }
+
+ os_memcpy(dev->pri_dev_type, dev_type, WPS_DEV_TYPE_LEN);
+ wpa_printf(MSG_DEBUG, "WPS: Primary Device Type: %s",
+ wps_dev_type_bin2str(dev->pri_dev_type, devtype,
+ sizeof(devtype)));
+
+ return 0;
+}
+
+
+int wps_process_device_attrs(struct wps_device_data *dev,
+ struct wps_parse_attr *attr)
+{
+ if (wps_process_manufacturer(dev, attr->manufacturer,
+ attr->manufacturer_len) ||
+ wps_process_model_name(dev, attr->model_name,
+ attr->model_name_len) ||
+ wps_process_model_number(dev, attr->model_number,
+ attr->model_number_len) ||
+ wps_process_serial_number(dev, attr->serial_number,
+ attr->serial_number_len) ||
+ wps_process_primary_dev_type(dev, attr->primary_dev_type) ||
+ wps_process_dev_name(dev, attr->dev_name, attr->dev_name_len))
+ return -1;
+ return 0;
+}
+
+
+int wps_process_os_version(struct wps_device_data *dev, const u8 *ver)
+{
+ if (ver == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No OS Version received");
+ return -1;
+ }
+
+ dev->os_version = WPA_GET_BE32(ver);
+ wpa_printf(MSG_DEBUG, "WPS: OS Version %08x", dev->os_version);
+
+ return 0;
+}
+
+
+int wps_process_rf_bands(struct wps_device_data *dev, const u8 *bands)
+{
+ if (bands == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No RF Bands received");
+ return -1;
+ }
+
+ dev->rf_bands = *bands;
+ wpa_printf(MSG_DEBUG, "WPS: Enrollee RF Bands 0x%x", dev->rf_bands);
+
+ return 0;
+}
+
+
+void wps_device_data_dup(struct wps_device_data *dst,
+ const struct wps_device_data *src)
+{
+ if (src->device_name)
+ dst->device_name = os_strdup(src->device_name);
+ if (src->manufacturer)
+ dst->manufacturer = os_strdup(src->manufacturer);
+ if (src->model_name)
+ dst->model_name = os_strdup(src->model_name);
+ if (src->model_number)
+ dst->model_number = os_strdup(src->model_number);
+ if (src->serial_number)
+ dst->serial_number = os_strdup(src->serial_number);
+ os_memcpy(dst->pri_dev_type, src->pri_dev_type, WPS_DEV_TYPE_LEN);
+ dst->os_version = src->os_version;
+ dst->rf_bands = src->rf_bands;
+}
+
+
+void wps_device_data_free(struct wps_device_data *dev)
+{
+ os_free(dev->device_name);
+ dev->device_name = NULL;
+ os_free(dev->manufacturer);
+ dev->manufacturer = NULL;
+ os_free(dev->model_name);
+ dev->model_name = NULL;
+ os_free(dev->model_number);
+ dev->model_number = NULL;
+ os_free(dev->serial_number);
+ dev->serial_number = NULL;
+}
diff --git a/src/wps/wps_dev_attr.h b/src/wps/wps_dev_attr.h
new file mode 100644
index 0000000..f26a05b
--- /dev/null
+++ b/src/wps/wps_dev_attr.h
@@ -0,0 +1,44 @@
+/*
+ * Wi-Fi Protected Setup - device attributes
+ * Copyright (c) 2008, 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.
+ */
+
+#ifndef WPS_DEV_ATTR_H
+#define WPS_DEV_ATTR_H
+
+struct wps_parse_attr;
+
+int wps_build_manufacturer(struct wps_device_data *dev, struct wpabuf *msg);
+int wps_build_model_name(struct wps_device_data *dev, struct wpabuf *msg);
+int wps_build_model_number(struct wps_device_data *dev, struct wpabuf *msg);
+int wps_build_dev_name(struct wps_device_data *dev, struct wpabuf *msg);
+int wps_build_device_attrs(struct wps_device_data *dev, struct wpabuf *msg);
+int wps_build_os_version(struct wps_device_data *dev, struct wpabuf *msg);
+int wps_build_rf_bands(struct wps_device_data *dev, struct wpabuf *msg);
+int wps_build_primary_dev_type(struct wps_device_data *dev,
+ struct wpabuf *msg);
+int wps_build_secondary_dev_type(struct wps_device_data *dev,
+ struct wpabuf *msg);
+int wps_build_dev_name(struct wps_device_data *dev, struct wpabuf *msg);
+int wps_process_device_attrs(struct wps_device_data *dev,
+ struct wps_parse_attr *attr);
+int wps_process_os_version(struct wps_device_data *dev, const u8 *ver);
+int wps_process_rf_bands(struct wps_device_data *dev, const u8 *bands);
+void wps_device_data_dup(struct wps_device_data *dst,
+ const struct wps_device_data *src);
+void wps_device_data_free(struct wps_device_data *dev);
+int wps_build_vendor_ext(struct wps_device_data *dev, struct wpabuf *msg);
+int wps_build_req_dev_type(struct wps_device_data *dev, struct wpabuf *msg,
+ unsigned int num_req_dev_types,
+ const u8 *req_dev_types);
+
+#endif /* WPS_DEV_ATTR_H */
diff --git a/src/wps/wps_enrollee.c b/src/wps/wps_enrollee.c
new file mode 100644
index 0000000..390254e
--- /dev/null
+++ b/src/wps/wps_enrollee.c
@@ -0,0 +1,1350 @@
+/*
+ * Wi-Fi Protected Setup - Enrollee
+ * Copyright (c) 2008, 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.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "crypto/crypto.h"
+#include "crypto/sha256.h"
+#include "crypto/random.h"
+#include "wps_i.h"
+#include "wps_dev_attr.h"
+
+
+static int wps_build_mac_addr(struct wps_data *wps, struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * MAC Address");
+ wpabuf_put_be16(msg, ATTR_MAC_ADDR);
+ wpabuf_put_be16(msg, ETH_ALEN);
+ wpabuf_put_data(msg, wps->mac_addr_e, ETH_ALEN);
+ return 0;
+}
+
+
+static int wps_build_wps_state(struct wps_data *wps, struct wpabuf *msg)
+{
+ u8 state;
+ if (wps->wps->ap)
+ state = wps->wps->wps_state;
+ else
+ state = WPS_STATE_NOT_CONFIGURED;
+ wpa_printf(MSG_DEBUG, "WPS: * Wi-Fi Protected Setup State (%d)",
+ state);
+ wpabuf_put_be16(msg, ATTR_WPS_STATE);
+ wpabuf_put_be16(msg, 1);
+ wpabuf_put_u8(msg, state);
+ return 0;
+}
+
+
+static int wps_build_e_hash(struct wps_data *wps, struct wpabuf *msg)
+{
+ u8 *hash;
+ const u8 *addr[4];
+ size_t len[4];
+
+ if (random_get_bytes(wps->snonce, 2 * WPS_SECRET_NONCE_LEN) < 0)
+ return -1;
+ wpa_hexdump(MSG_DEBUG, "WPS: E-S1", wps->snonce, WPS_SECRET_NONCE_LEN);
+ wpa_hexdump(MSG_DEBUG, "WPS: E-S2",
+ wps->snonce + WPS_SECRET_NONCE_LEN, WPS_SECRET_NONCE_LEN);
+
+ if (wps->dh_pubkey_e == NULL || wps->dh_pubkey_r == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: DH public keys not available for "
+ "E-Hash derivation");
+ return -1;
+ }
+
+ wpa_printf(MSG_DEBUG, "WPS: * E-Hash1");
+ wpabuf_put_be16(msg, ATTR_E_HASH1);
+ wpabuf_put_be16(msg, SHA256_MAC_LEN);
+ hash = wpabuf_put(msg, SHA256_MAC_LEN);
+ /* E-Hash1 = HMAC_AuthKey(E-S1 || PSK1 || PK_E || PK_R) */
+ addr[0] = wps->snonce;
+ len[0] = WPS_SECRET_NONCE_LEN;
+ addr[1] = wps->psk1;
+ len[1] = WPS_PSK_LEN;
+ addr[2] = wpabuf_head(wps->dh_pubkey_e);
+ len[2] = wpabuf_len(wps->dh_pubkey_e);
+ addr[3] = wpabuf_head(wps->dh_pubkey_r);
+ len[3] = wpabuf_len(wps->dh_pubkey_r);
+ hmac_sha256_vector(wps->authkey, WPS_AUTHKEY_LEN, 4, addr, len, hash);
+ wpa_hexdump(MSG_DEBUG, "WPS: E-Hash1", hash, SHA256_MAC_LEN);
+
+ wpa_printf(MSG_DEBUG, "WPS: * E-Hash2");
+ wpabuf_put_be16(msg, ATTR_E_HASH2);
+ wpabuf_put_be16(msg, SHA256_MAC_LEN);
+ hash = wpabuf_put(msg, SHA256_MAC_LEN);
+ /* E-Hash2 = HMAC_AuthKey(E-S2 || PSK2 || PK_E || PK_R) */
+ addr[0] = wps->snonce + WPS_SECRET_NONCE_LEN;
+ addr[1] = wps->psk2;
+ hmac_sha256_vector(wps->authkey, WPS_AUTHKEY_LEN, 4, addr, len, hash);
+ wpa_hexdump(MSG_DEBUG, "WPS: E-Hash2", hash, SHA256_MAC_LEN);
+
+ return 0;
+}
+
+
+static int wps_build_e_snonce1(struct wps_data *wps, struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * E-SNonce1");
+ wpabuf_put_be16(msg, ATTR_E_SNONCE1);
+ wpabuf_put_be16(msg, WPS_SECRET_NONCE_LEN);
+ wpabuf_put_data(msg, wps->snonce, WPS_SECRET_NONCE_LEN);
+ return 0;
+}
+
+
+static int wps_build_e_snonce2(struct wps_data *wps, struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * E-SNonce2");
+ wpabuf_put_be16(msg, ATTR_E_SNONCE2);
+ wpabuf_put_be16(msg, WPS_SECRET_NONCE_LEN);
+ wpabuf_put_data(msg, wps->snonce + WPS_SECRET_NONCE_LEN,
+ WPS_SECRET_NONCE_LEN);
+ return 0;
+}
+
+
+static struct wpabuf * wps_build_m1(struct wps_data *wps)
+{
+ struct wpabuf *msg;
+ u16 config_methods;
+
+ if (random_get_bytes(wps->nonce_e, WPS_NONCE_LEN) < 0)
+ return NULL;
+ wpa_hexdump(MSG_DEBUG, "WPS: Enrollee Nonce",
+ wps->nonce_e, WPS_NONCE_LEN);
+
+ wpa_printf(MSG_DEBUG, "WPS: Building Message M1");
+ msg = wpabuf_alloc(1000);
+ if (msg == NULL)
+ return NULL;
+
+ config_methods = wps->wps->config_methods;
+ if (wps->wps->ap) {
+ /*
+ * These are the methods that the AP supports as an Enrollee
+ * for adding external Registrars, so remove PushButton.
+ */
+ config_methods &= ~WPS_CONFIG_PUSHBUTTON;
+#ifdef CONFIG_WPS2
+ config_methods &= ~(WPS_CONFIG_VIRT_PUSHBUTTON |
+ WPS_CONFIG_PHY_PUSHBUTTON);
+#endif /* CONFIG_WPS2 */
+ }
+
+ if (wps_build_version(msg) ||
+ wps_build_msg_type(msg, WPS_M1) ||
+ wps_build_uuid_e(msg, wps->uuid_e) ||
+ wps_build_mac_addr(wps, msg) ||
+ wps_build_enrollee_nonce(wps, msg) ||
+ wps_build_public_key(wps, msg) ||
+ wps_build_auth_type_flags(wps, msg) ||
+ wps_build_encr_type_flags(wps, msg) ||
+ wps_build_conn_type_flags(wps, msg) ||
+ wps_build_config_methods(msg, config_methods) ||
+ wps_build_wps_state(wps, msg) ||
+ wps_build_device_attrs(&wps->wps->dev, msg) ||
+ wps_build_rf_bands(&wps->wps->dev, msg) ||
+ wps_build_assoc_state(wps, msg) ||
+ wps_build_dev_password_id(msg, wps->dev_pw_id) ||
+ wps_build_config_error(msg, WPS_CFG_NO_ERROR) ||
+ wps_build_os_version(&wps->wps->dev, msg) ||
+ wps_build_wfa_ext(msg, 0, NULL, 0)) {
+ wpabuf_free(msg);
+ return NULL;
+ }
+
+ wps->state = RECV_M2;
+ return msg;
+}
+
+
+static struct wpabuf * wps_build_m3(struct wps_data *wps)
+{
+ struct wpabuf *msg;
+
+ wpa_printf(MSG_DEBUG, "WPS: Building Message M3");
+
+ if (wps->dev_password == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Device Password available");
+ return NULL;
+ }
+ wps_derive_psk(wps, wps->dev_password, wps->dev_password_len);
+
+ msg = wpabuf_alloc(1000);
+ if (msg == NULL)
+ return NULL;
+
+ if (wps_build_version(msg) ||
+ wps_build_msg_type(msg, WPS_M3) ||
+ wps_build_registrar_nonce(wps, msg) ||
+ wps_build_e_hash(wps, msg) ||
+ wps_build_wfa_ext(msg, 0, NULL, 0) ||
+ wps_build_authenticator(wps, msg)) {
+ wpabuf_free(msg);
+ return NULL;
+ }
+
+ wps->state = RECV_M4;
+ return msg;
+}
+
+
+static struct wpabuf * wps_build_m5(struct wps_data *wps)
+{
+ struct wpabuf *msg, *plain;
+
+ wpa_printf(MSG_DEBUG, "WPS: Building Message M5");
+
+ plain = wpabuf_alloc(200);
+ if (plain == NULL)
+ return NULL;
+
+ msg = wpabuf_alloc(1000);
+ if (msg == NULL) {
+ wpabuf_free(plain);
+ return NULL;
+ }
+
+ if (wps_build_version(msg) ||
+ wps_build_msg_type(msg, WPS_M5) ||
+ wps_build_registrar_nonce(wps, msg) ||
+ wps_build_e_snonce1(wps, plain) ||
+ wps_build_key_wrap_auth(wps, plain) ||
+ wps_build_encr_settings(wps, msg, plain) ||
+ wps_build_wfa_ext(msg, 0, NULL, 0) ||
+ wps_build_authenticator(wps, msg)) {
+ wpabuf_free(plain);
+ wpabuf_free(msg);
+ return NULL;
+ }
+ wpabuf_free(plain);
+
+ wps->state = RECV_M6;
+ return msg;
+}
+
+
+static int wps_build_cred_ssid(struct wps_data *wps, struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * SSID");
+ wpabuf_put_be16(msg, ATTR_SSID);
+ wpabuf_put_be16(msg, wps->wps->ssid_len);
+ wpabuf_put_data(msg, wps->wps->ssid, wps->wps->ssid_len);
+ return 0;
+}
+
+
+static int wps_build_cred_auth_type(struct wps_data *wps, struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * Authentication Type");
+ wpabuf_put_be16(msg, ATTR_AUTH_TYPE);
+ wpabuf_put_be16(msg, 2);
+ wpabuf_put_be16(msg, wps->wps->auth_types);
+ return 0;
+}
+
+
+static int wps_build_cred_encr_type(struct wps_data *wps, struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * Encryption Type");
+ wpabuf_put_be16(msg, ATTR_ENCR_TYPE);
+ wpabuf_put_be16(msg, 2);
+ wpabuf_put_be16(msg, wps->wps->encr_types);
+ return 0;
+}
+
+
+static int wps_build_cred_network_key(struct wps_data *wps, struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * Network Key");
+ wpabuf_put_be16(msg, ATTR_NETWORK_KEY);
+ wpabuf_put_be16(msg, wps->wps->network_key_len);
+ wpabuf_put_data(msg, wps->wps->network_key, wps->wps->network_key_len);
+ return 0;
+}
+
+
+static int wps_build_cred_mac_addr(struct wps_data *wps, struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * MAC Address (AP BSSID)");
+ wpabuf_put_be16(msg, ATTR_MAC_ADDR);
+ wpabuf_put_be16(msg, ETH_ALEN);
+ wpabuf_put_data(msg, wps->wps->dev.mac_addr, ETH_ALEN);
+ return 0;
+}
+
+
+static int wps_build_ap_settings(struct wps_data *wps, struct wpabuf *plain)
+{
+ if (wps->wps->ap_settings) {
+ wpa_printf(MSG_DEBUG, "WPS: * AP Settings (pre-configured)");
+ wpabuf_put_data(plain, wps->wps->ap_settings,
+ wps->wps->ap_settings_len);
+ return 0;
+ }
+
+ return wps_build_cred_ssid(wps, plain) ||
+ wps_build_cred_mac_addr(wps, plain) ||
+ wps_build_cred_auth_type(wps, plain) ||
+ wps_build_cred_encr_type(wps, plain) ||
+ wps_build_cred_network_key(wps, plain);
+}
+
+
+static struct wpabuf * wps_build_m7(struct wps_data *wps)
+{
+ struct wpabuf *msg, *plain;
+
+ wpa_printf(MSG_DEBUG, "WPS: Building Message M7");
+
+ plain = wpabuf_alloc(500 + wps->wps->ap_settings_len);
+ if (plain == NULL)
+ return NULL;
+
+ msg = wpabuf_alloc(1000 + wps->wps->ap_settings_len);
+ if (msg == NULL) {
+ wpabuf_free(plain);
+ return NULL;
+ }
+
+ if (wps_build_version(msg) ||
+ wps_build_msg_type(msg, WPS_M7) ||
+ wps_build_registrar_nonce(wps, msg) ||
+ wps_build_e_snonce2(wps, plain) ||
+ (wps->wps->ap && wps_build_ap_settings(wps, plain)) ||
+ wps_build_key_wrap_auth(wps, plain) ||
+ wps_build_encr_settings(wps, msg, plain) ||
+ wps_build_wfa_ext(msg, 0, NULL, 0) ||
+ wps_build_authenticator(wps, msg)) {
+ wpabuf_free(plain);
+ wpabuf_free(msg);
+ return NULL;
+ }
+ wpabuf_free(plain);
+
+ if (wps->wps->ap && wps->wps->registrar) {
+ /*
+ * If the Registrar is only learning our current configuration,
+ * it may not continue protocol run to successful completion.
+ * Store information here to make sure it remains available.
+ */
+ wps_device_store(wps->wps->registrar, &wps->peer_dev,
+ wps->uuid_r);
+ }
+
+ wps->state = RECV_M8;
+ return msg;
+}
+
+
+static struct wpabuf * wps_build_wsc_done(struct wps_data *wps)
+{
+ struct wpabuf *msg;
+
+ wpa_printf(MSG_DEBUG, "WPS: Building Message WSC_Done");
+
+ msg = wpabuf_alloc(1000);
+ if (msg == NULL)
+ return NULL;
+
+ if (wps_build_version(msg) ||
+ wps_build_msg_type(msg, WPS_WSC_DONE) ||
+ wps_build_enrollee_nonce(wps, msg) ||
+ wps_build_registrar_nonce(wps, msg) ||
+ wps_build_wfa_ext(msg, 0, NULL, 0)) {
+ wpabuf_free(msg);
+ return NULL;
+ }
+
+ if (wps->wps->ap)
+ wps->state = RECV_ACK;
+ else {
+ wps_success_event(wps->wps);
+ wps->state = WPS_FINISHED;
+ }
+ return msg;
+}
+
+
+struct wpabuf * wps_enrollee_get_msg(struct wps_data *wps,
+ enum wsc_op_code *op_code)
+{
+ struct wpabuf *msg;
+
+ switch (wps->state) {
+ case SEND_M1:
+ msg = wps_build_m1(wps);
+ *op_code = WSC_MSG;
+ break;
+ case SEND_M3:
+ msg = wps_build_m3(wps);
+ *op_code = WSC_MSG;
+ break;
+ case SEND_M5:
+ msg = wps_build_m5(wps);
+ *op_code = WSC_MSG;
+ break;
+ case SEND_M7:
+ msg = wps_build_m7(wps);
+ *op_code = WSC_MSG;
+ break;
+ case RECEIVED_M2D:
+ if (wps->wps->ap) {
+ msg = wps_build_wsc_nack(wps);
+ *op_code = WSC_NACK;
+ break;
+ }
+ msg = wps_build_wsc_ack(wps);
+ *op_code = WSC_ACK;
+ if (msg) {
+ /* Another M2/M2D may be received */
+ wps->state = RECV_M2;
+ }
+ break;
+ case SEND_WSC_NACK:
+ msg = wps_build_wsc_nack(wps);
+ *op_code = WSC_NACK;
+ break;
+ case WPS_MSG_DONE:
+ msg = wps_build_wsc_done(wps);
+ *op_code = WSC_Done;
+ break;
+ default:
+ wpa_printf(MSG_DEBUG, "WPS: Unsupported state %d for building "
+ "a message", wps->state);
+ msg = NULL;
+ break;
+ }
+
+ if (*op_code == WSC_MSG && msg) {
+ /* Save a copy of the last message for Authenticator derivation
+ */
+ wpabuf_free(wps->last_msg);
+ wps->last_msg = wpabuf_dup(msg);
+ }
+
+ return msg;
+}
+
+
+static int wps_process_registrar_nonce(struct wps_data *wps, const u8 *r_nonce)
+{
+ if (r_nonce == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Registrar Nonce received");
+ return -1;
+ }
+
+ os_memcpy(wps->nonce_r, r_nonce, WPS_NONCE_LEN);
+ wpa_hexdump(MSG_DEBUG, "WPS: Registrar Nonce",
+ wps->nonce_r, WPS_NONCE_LEN);
+
+ return 0;
+}
+
+
+static int wps_process_enrollee_nonce(struct wps_data *wps, const u8 *e_nonce)
+{
+ if (e_nonce == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Enrollee Nonce received");
+ return -1;
+ }
+
+ if (os_memcmp(wps->nonce_e, e_nonce, WPS_NONCE_LEN) != 0) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Enrollee Nonce received");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static int wps_process_uuid_r(struct wps_data *wps, const u8 *uuid_r)
+{
+ if (uuid_r == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No UUID-R received");
+ return -1;
+ }
+
+ os_memcpy(wps->uuid_r, uuid_r, WPS_UUID_LEN);
+ wpa_hexdump(MSG_DEBUG, "WPS: UUID-R", wps->uuid_r, WPS_UUID_LEN);
+
+ return 0;
+}
+
+
+static int wps_process_pubkey(struct wps_data *wps, const u8 *pk,
+ size_t pk_len)
+{
+ if (pk == NULL || pk_len == 0) {
+ wpa_printf(MSG_DEBUG, "WPS: No Public Key received");
+ return -1;
+ }
+
+#ifdef CONFIG_WPS_OOB
+ if (wps->dev_pw_id != DEV_PW_DEFAULT &&
+ wps->wps->oob_conf.pubkey_hash) {
+ const u8 *addr[1];
+ u8 hash[WPS_HASH_LEN];
+
+ addr[0] = pk;
+ sha256_vector(1, addr, &pk_len, hash);
+ if (os_memcmp(hash,
+ wpabuf_head(wps->wps->oob_conf.pubkey_hash),
+ WPS_OOB_PUBKEY_HASH_LEN) != 0) {
+ wpa_printf(MSG_ERROR, "WPS: Public Key hash error");
+ return -1;
+ }
+ }
+#endif /* CONFIG_WPS_OOB */
+
+ wpabuf_free(wps->dh_pubkey_r);
+ wps->dh_pubkey_r = wpabuf_alloc_copy(pk, pk_len);
+ if (wps->dh_pubkey_r == NULL)
+ return -1;
+
+ if (wps_derive_keys(wps) < 0)
+ return -1;
+
+ return 0;
+}
+
+
+static int wps_process_r_hash1(struct wps_data *wps, const u8 *r_hash1)
+{
+ if (r_hash1 == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No R-Hash1 received");
+ return -1;
+ }
+
+ os_memcpy(wps->peer_hash1, r_hash1, WPS_HASH_LEN);
+ wpa_hexdump(MSG_DEBUG, "WPS: R-Hash1", wps->peer_hash1, WPS_HASH_LEN);
+
+ return 0;
+}
+
+
+static int wps_process_r_hash2(struct wps_data *wps, const u8 *r_hash2)
+{
+ if (r_hash2 == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No R-Hash2 received");
+ return -1;
+ }
+
+ os_memcpy(wps->peer_hash2, r_hash2, WPS_HASH_LEN);
+ wpa_hexdump(MSG_DEBUG, "WPS: R-Hash2", wps->peer_hash2, WPS_HASH_LEN);
+
+ return 0;
+}
+
+
+static int wps_process_r_snonce1(struct wps_data *wps, const u8 *r_snonce1)
+{
+ u8 hash[SHA256_MAC_LEN];
+ const u8 *addr[4];
+ size_t len[4];
+
+ if (r_snonce1 == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No R-SNonce1 received");
+ return -1;
+ }
+
+ wpa_hexdump_key(MSG_DEBUG, "WPS: R-SNonce1", r_snonce1,
+ WPS_SECRET_NONCE_LEN);
+
+ /* R-Hash1 = HMAC_AuthKey(R-S1 || PSK1 || PK_E || PK_R) */
+ addr[0] = r_snonce1;
+ len[0] = WPS_SECRET_NONCE_LEN;
+ addr[1] = wps->psk1;
+ len[1] = WPS_PSK_LEN;
+ addr[2] = wpabuf_head(wps->dh_pubkey_e);
+ len[2] = wpabuf_len(wps->dh_pubkey_e);
+ addr[3] = wpabuf_head(wps->dh_pubkey_r);
+ len[3] = wpabuf_len(wps->dh_pubkey_r);
+ hmac_sha256_vector(wps->authkey, WPS_AUTHKEY_LEN, 4, addr, len, hash);
+
+ if (os_memcmp(wps->peer_hash1, hash, WPS_HASH_LEN) != 0) {
+ wpa_printf(MSG_DEBUG, "WPS: R-Hash1 derived from R-S1 does "
+ "not match with the pre-committed value");
+ wps->config_error = WPS_CFG_DEV_PASSWORD_AUTH_FAILURE;
+ wps_pwd_auth_fail_event(wps->wps, 1, 1);
+ return -1;
+ }
+
+ wpa_printf(MSG_DEBUG, "WPS: Registrar proved knowledge of the first "
+ "half of the device password");
+
+ return 0;
+}
+
+
+static int wps_process_r_snonce2(struct wps_data *wps, const u8 *r_snonce2)
+{
+ u8 hash[SHA256_MAC_LEN];
+ const u8 *addr[4];
+ size_t len[4];
+
+ if (r_snonce2 == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No R-SNonce2 received");
+ return -1;
+ }
+
+ wpa_hexdump_key(MSG_DEBUG, "WPS: R-SNonce2", r_snonce2,
+ WPS_SECRET_NONCE_LEN);
+
+ /* R-Hash2 = HMAC_AuthKey(R-S2 || PSK2 || PK_E || PK_R) */
+ addr[0] = r_snonce2;
+ len[0] = WPS_SECRET_NONCE_LEN;
+ addr[1] = wps->psk2;
+ len[1] = WPS_PSK_LEN;
+ addr[2] = wpabuf_head(wps->dh_pubkey_e);
+ len[2] = wpabuf_len(wps->dh_pubkey_e);
+ addr[3] = wpabuf_head(wps->dh_pubkey_r);
+ len[3] = wpabuf_len(wps->dh_pubkey_r);
+ hmac_sha256_vector(wps->authkey, WPS_AUTHKEY_LEN, 4, addr, len, hash);
+
+ if (os_memcmp(wps->peer_hash2, hash, WPS_HASH_LEN) != 0) {
+ wpa_printf(MSG_DEBUG, "WPS: R-Hash2 derived from R-S2 does "
+ "not match with the pre-committed value");
+ wps->config_error = WPS_CFG_DEV_PASSWORD_AUTH_FAILURE;
+ wps_pwd_auth_fail_event(wps->wps, 1, 2);
+ return -1;
+ }
+
+ wpa_printf(MSG_DEBUG, "WPS: Registrar proved knowledge of the second "
+ "half of the device password");
+
+ return 0;
+}
+
+
+static int wps_process_cred_e(struct wps_data *wps, const u8 *cred,
+ size_t cred_len, int wps2)
+{
+ struct wps_parse_attr attr;
+ struct wpabuf msg;
+
+ wpa_printf(MSG_DEBUG, "WPS: Received Credential");
+ os_memset(&wps->cred, 0, sizeof(wps->cred));
+ wpabuf_set(&msg, cred, cred_len);
+ if (wps_parse_msg(&msg, &attr) < 0 ||
+ 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.
+ */
+#ifdef CONFIG_WPS_STRICT
+ if (wps2) {
+ wpa_printf(MSG_INFO, "WPS: Do not accept incorrect "
+ "MAC Address in AP Settings");
+ return -1;
+ }
+#endif /* CONFIG_WPS_STRICT */
+ }
+
+#ifdef CONFIG_WPS2
+ if (!(wps->cred.encr_type &
+ (WPS_ENCR_NONE | WPS_ENCR_TKIP | WPS_ENCR_AES))) {
+ if (wps->cred.encr_type & WPS_ENCR_WEP) {
+ wpa_printf(MSG_INFO, "WPS: Reject Credential "
+ "due to WEP configuration");
+ wps->error_indication = WPS_EI_SECURITY_WEP_PROHIBITED;
+ return -2;
+ }
+
+ wpa_printf(MSG_INFO, "WPS: Reject Credential due to "
+ "invalid encr_type 0x%x", wps->cred.encr_type);
+ return -1;
+ }
+#endif /* CONFIG_WPS2 */
+
+ if (wps->wps->cred_cb) {
+ wps->cred.cred_attr = cred - 4;
+ wps->cred.cred_attr_len = cred_len + 4;
+ wps->wps->cred_cb(wps->wps->cb_ctx, &wps->cred);
+ wps->cred.cred_attr = NULL;
+ wps->cred.cred_attr_len = 0;
+ }
+
+ return 0;
+}
+
+
+static int wps_process_creds(struct wps_data *wps, const u8 *cred[],
+ size_t cred_len[], size_t num_cred, int wps2)
+{
+ size_t i;
+ int ok = 0;
+
+ if (wps->wps->ap)
+ return 0;
+
+ if (num_cred == 0) {
+ wpa_printf(MSG_DEBUG, "WPS: No Credential attributes "
+ "received");
+ return -1;
+ }
+
+ for (i = 0; i < num_cred; i++) {
+ int res;
+ res = wps_process_cred_e(wps, cred[i], cred_len[i], wps2);
+ if (res == 0)
+ ok++;
+ else if (res == -2)
+ wpa_printf(MSG_DEBUG, "WPS: WEP credential skipped");
+ else
+ return -1;
+ }
+
+ if (ok == 0) {
+ wpa_printf(MSG_DEBUG, "WPS: No valid Credential attribute "
+ "received");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static int wps_process_ap_settings_e(struct wps_data *wps,
+ struct wps_parse_attr *attr,
+ struct wpabuf *attrs, int wps2)
+{
+ struct wps_credential cred;
+
+ if (!wps->wps->ap)
+ return 0;
+
+ if (wps_process_ap_settings(attr, &cred) < 0)
+ return -1;
+
+ 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.
+ */
+#ifdef CONFIG_WPS_STRICT
+ if (wps2) {
+ wpa_printf(MSG_INFO, "WPS: Do not accept incorrect "
+ "MAC Address in AP Settings");
+ return -1;
+ }
+#endif /* CONFIG_WPS_STRICT */
+ }
+
+#ifdef CONFIG_WPS2
+ if (!(cred.encr_type & (WPS_ENCR_NONE | WPS_ENCR_TKIP | WPS_ENCR_AES)))
+ {
+ if (cred.encr_type & WPS_ENCR_WEP) {
+ wpa_printf(MSG_INFO, "WPS: Reject new AP settings "
+ "due to WEP configuration");
+ wps->error_indication = WPS_EI_SECURITY_WEP_PROHIBITED;
+ return -1;
+ }
+
+ wpa_printf(MSG_INFO, "WPS: Reject new AP settings due to "
+ "invalid encr_type 0x%x", cred.encr_type);
+ return -1;
+ }
+#endif /* CONFIG_WPS2 */
+
+#ifdef CONFIG_WPS_STRICT
+ if (wps2) {
+ if ((cred.encr_type & (WPS_ENCR_TKIP | WPS_ENCR_AES)) ==
+ WPS_ENCR_TKIP ||
+ (cred.auth_type & (WPS_AUTH_WPAPSK | WPS_AUTH_WPA2PSK)) ==
+ WPS_AUTH_WPAPSK) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid WSC 2.0 "
+ "AP Settings: WPA-Personal/TKIP only");
+ wps->error_indication =
+ WPS_EI_SECURITY_TKIP_ONLY_PROHIBITED;
+ return -1;
+ }
+ }
+#endif /* CONFIG_WPS_STRICT */
+
+#ifdef CONFIG_WPS2
+ if ((cred.encr_type & (WPS_ENCR_TKIP | WPS_ENCR_AES)) == WPS_ENCR_TKIP)
+ {
+ wpa_printf(MSG_DEBUG, "WPS: Upgrade encr_type TKIP -> "
+ "TKIP+AES");
+ cred.encr_type |= WPS_ENCR_AES;
+ }
+
+ if ((cred.auth_type & (WPS_AUTH_WPAPSK | WPS_AUTH_WPA2PSK)) ==
+ WPS_AUTH_WPAPSK) {
+ wpa_printf(MSG_DEBUG, "WPS: Upgrade auth_type WPAPSK -> "
+ "WPAPSK+WPA2PSK");
+ cred.auth_type |= WPS_AUTH_WPA2PSK;
+ }
+#endif /* CONFIG_WPS2 */
+
+ if (wps->wps->cred_cb) {
+ cred.cred_attr = wpabuf_head(attrs);
+ cred.cred_attr_len = wpabuf_len(attrs);
+ wps->wps->cred_cb(wps->wps->cb_ctx, &cred);
+ }
+
+ return 0;
+}
+
+
+static enum wps_process_res wps_process_m2(struct wps_data *wps,
+ const struct wpabuf *msg,
+ struct wps_parse_attr *attr)
+{
+ wpa_printf(MSG_DEBUG, "WPS: Received M2");
+
+ if (wps->state != RECV_M2) {
+ wpa_printf(MSG_DEBUG, "WPS: Unexpected state (%d) for "
+ "receiving M2", wps->state);
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ if (wps_process_registrar_nonce(wps, attr->registrar_nonce) ||
+ wps_process_enrollee_nonce(wps, attr->enrollee_nonce) ||
+ wps_process_uuid_r(wps, attr->uuid_r)) {
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ /*
+ * Stop here on an AP as an Enrollee if AP Setup is locked unless the
+ * special locked mode is used to allow protocol run up to M7 in order
+ * to support external Registrars that only learn the current AP
+ * configuration without changing it.
+ */
+ if (wps->wps->ap &&
+ ((wps->wps->ap_setup_locked && wps->wps->ap_setup_locked != 2) ||
+ wps->dev_password == NULL)) {
+ wpa_printf(MSG_DEBUG, "WPS: AP Setup is locked - refuse "
+ "registration of a new Registrar");
+ wps->config_error = WPS_CFG_SETUP_LOCKED;
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ if (wps_process_pubkey(wps, attr->public_key, attr->public_key_len) ||
+ wps_process_authenticator(wps, attr->authenticator, msg) ||
+ wps_process_device_attrs(&wps->peer_dev, attr)) {
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ wps->state = SEND_M3;
+ return WPS_CONTINUE;
+}
+
+
+static enum wps_process_res wps_process_m2d(struct wps_data *wps,
+ struct wps_parse_attr *attr)
+{
+ wpa_printf(MSG_DEBUG, "WPS: Received M2D");
+
+ if (wps->state != RECV_M2) {
+ wpa_printf(MSG_DEBUG, "WPS: Unexpected state (%d) for "
+ "receiving M2D", wps->state);
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ wpa_hexdump_ascii(MSG_DEBUG, "WPS: Manufacturer",
+ attr->manufacturer, attr->manufacturer_len);
+ wpa_hexdump_ascii(MSG_DEBUG, "WPS: Model Name",
+ attr->model_name, attr->model_name_len);
+ wpa_hexdump_ascii(MSG_DEBUG, "WPS: Model Number",
+ attr->model_number, attr->model_number_len);
+ wpa_hexdump_ascii(MSG_DEBUG, "WPS: Serial Number",
+ attr->serial_number, attr->serial_number_len);
+ wpa_hexdump_ascii(MSG_DEBUG, "WPS: Device Name",
+ attr->dev_name, attr->dev_name_len);
+
+ if (wps->wps->event_cb) {
+ union wps_event_data data;
+ struct wps_event_m2d *m2d = &data.m2d;
+ os_memset(&data, 0, sizeof(data));
+ if (attr->config_methods)
+ m2d->config_methods =
+ WPA_GET_BE16(attr->config_methods);
+ m2d->manufacturer = attr->manufacturer;
+ m2d->manufacturer_len = attr->manufacturer_len;
+ m2d->model_name = attr->model_name;
+ m2d->model_name_len = attr->model_name_len;
+ m2d->model_number = attr->model_number;
+ m2d->model_number_len = attr->model_number_len;
+ m2d->serial_number = attr->serial_number;
+ m2d->serial_number_len = attr->serial_number_len;
+ m2d->dev_name = attr->dev_name;
+ m2d->dev_name_len = attr->dev_name_len;
+ m2d->primary_dev_type = attr->primary_dev_type;
+ if (attr->config_error)
+ m2d->config_error =
+ WPA_GET_BE16(attr->config_error);
+ if (attr->dev_password_id)
+ m2d->dev_password_id =
+ WPA_GET_BE16(attr->dev_password_id);
+ wps->wps->event_cb(wps->wps->cb_ctx, WPS_EV_M2D, &data);
+ }
+
+ wps->state = RECEIVED_M2D;
+ return WPS_CONTINUE;
+}
+
+
+static enum wps_process_res wps_process_m4(struct wps_data *wps,
+ const struct wpabuf *msg,
+ struct wps_parse_attr *attr)
+{
+ struct wpabuf *decrypted;
+ struct wps_parse_attr eattr;
+
+ wpa_printf(MSG_DEBUG, "WPS: Received M4");
+
+ if (wps->state != RECV_M4) {
+ wpa_printf(MSG_DEBUG, "WPS: Unexpected state (%d) for "
+ "receiving M4", wps->state);
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ if (wps_process_enrollee_nonce(wps, attr->enrollee_nonce) ||
+ wps_process_authenticator(wps, attr->authenticator, msg) ||
+ wps_process_r_hash1(wps, attr->r_hash1) ||
+ wps_process_r_hash2(wps, attr->r_hash2)) {
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ decrypted = wps_decrypt_encr_settings(wps, attr->encr_settings,
+ attr->encr_settings_len);
+ if (decrypted == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: Failed to decrypted Encrypted "
+ "Settings attribute");
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ if (wps_validate_m4_encr(decrypted, attr->version2 != 0) < 0) {
+ wpabuf_free(decrypted);
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ wpa_printf(MSG_DEBUG, "WPS: Processing decrypted Encrypted Settings "
+ "attribute");
+ if (wps_parse_msg(decrypted, &eattr) < 0 ||
+ wps_process_key_wrap_auth(wps, decrypted, eattr.key_wrap_auth) ||
+ wps_process_r_snonce1(wps, eattr.r_snonce1)) {
+ wpabuf_free(decrypted);
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+ wpabuf_free(decrypted);
+
+ wps->state = SEND_M5;
+ return WPS_CONTINUE;
+}
+
+
+static enum wps_process_res wps_process_m6(struct wps_data *wps,
+ const struct wpabuf *msg,
+ struct wps_parse_attr *attr)
+{
+ struct wpabuf *decrypted;
+ struct wps_parse_attr eattr;
+
+ wpa_printf(MSG_DEBUG, "WPS: Received M6");
+
+ if (wps->state != RECV_M6) {
+ wpa_printf(MSG_DEBUG, "WPS: Unexpected state (%d) for "
+ "receiving M6", wps->state);
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ if (wps_process_enrollee_nonce(wps, attr->enrollee_nonce) ||
+ wps_process_authenticator(wps, attr->authenticator, msg)) {
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ decrypted = wps_decrypt_encr_settings(wps, attr->encr_settings,
+ attr->encr_settings_len);
+ if (decrypted == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: Failed to decrypted Encrypted "
+ "Settings attribute");
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ if (wps_validate_m6_encr(decrypted, attr->version2 != 0) < 0) {
+ wpabuf_free(decrypted);
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ wpa_printf(MSG_DEBUG, "WPS: Processing decrypted Encrypted Settings "
+ "attribute");
+ if (wps_parse_msg(decrypted, &eattr) < 0 ||
+ wps_process_key_wrap_auth(wps, decrypted, eattr.key_wrap_auth) ||
+ wps_process_r_snonce2(wps, eattr.r_snonce2)) {
+ wpabuf_free(decrypted);
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+ wpabuf_free(decrypted);
+
+ wps->state = SEND_M7;
+ return WPS_CONTINUE;
+}
+
+
+static enum wps_process_res wps_process_m8(struct wps_data *wps,
+ const struct wpabuf *msg,
+ struct wps_parse_attr *attr)
+{
+ struct wpabuf *decrypted;
+ struct wps_parse_attr eattr;
+
+ wpa_printf(MSG_DEBUG, "WPS: Received M8");
+
+ if (wps->state != RECV_M8) {
+ wpa_printf(MSG_DEBUG, "WPS: Unexpected state (%d) for "
+ "receiving M8", wps->state);
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ if (wps_process_enrollee_nonce(wps, attr->enrollee_nonce) ||
+ wps_process_authenticator(wps, attr->authenticator, msg)) {
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ if (wps->wps->ap && wps->wps->ap_setup_locked) {
+ /*
+ * Stop here if special ap_setup_locked == 2 mode allowed the
+ * protocol to continue beyond M2. This allows ER to learn the
+ * current AP settings without changing them.
+ */
+ wpa_printf(MSG_DEBUG, "WPS: AP Setup is locked - refuse "
+ "registration of a new Registrar");
+ wps->config_error = WPS_CFG_SETUP_LOCKED;
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ decrypted = wps_decrypt_encr_settings(wps, attr->encr_settings,
+ attr->encr_settings_len);
+ if (decrypted == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: Failed to decrypted Encrypted "
+ "Settings attribute");
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ if (wps_validate_m8_encr(decrypted, wps->wps->ap, attr->version2 != 0)
+ < 0) {
+ wpabuf_free(decrypted);
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ wpa_printf(MSG_DEBUG, "WPS: Processing decrypted Encrypted Settings "
+ "attribute");
+ if (wps_parse_msg(decrypted, &eattr) < 0 ||
+ wps_process_key_wrap_auth(wps, decrypted, eattr.key_wrap_auth) ||
+ wps_process_creds(wps, eattr.cred, eattr.cred_len,
+ eattr.num_cred, attr->version2 != NULL) ||
+ wps_process_ap_settings_e(wps, &eattr, decrypted,
+ attr->version2 != NULL)) {
+ wpabuf_free(decrypted);
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+ wpabuf_free(decrypted);
+
+ wps->state = WPS_MSG_DONE;
+ return WPS_CONTINUE;
+}
+
+
+static enum wps_process_res wps_process_wsc_msg(struct wps_data *wps,
+ const struct wpabuf *msg)
+{
+ struct wps_parse_attr attr;
+ enum wps_process_res ret = WPS_CONTINUE;
+
+ wpa_printf(MSG_DEBUG, "WPS: Received WSC_MSG");
+
+ if (wps_parse_msg(msg, &attr) < 0)
+ return WPS_FAILURE;
+
+ if (attr.enrollee_nonce == NULL ||
+ os_memcmp(wps->nonce_e, attr.enrollee_nonce, WPS_NONCE_LEN != 0)) {
+ wpa_printf(MSG_DEBUG, "WPS: Mismatch in enrollee nonce");
+ return WPS_FAILURE;
+ }
+
+ if (attr.msg_type == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Message Type attribute");
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ switch (*attr.msg_type) {
+ case WPS_M2:
+ if (wps_validate_m2(msg) < 0)
+ return WPS_FAILURE;
+ ret = wps_process_m2(wps, msg, &attr);
+ break;
+ case WPS_M2D:
+ if (wps_validate_m2d(msg) < 0)
+ return WPS_FAILURE;
+ ret = wps_process_m2d(wps, &attr);
+ break;
+ case WPS_M4:
+ if (wps_validate_m4(msg) < 0)
+ return WPS_FAILURE;
+ ret = wps_process_m4(wps, msg, &attr);
+ if (ret == WPS_FAILURE || wps->state == SEND_WSC_NACK)
+ wps_fail_event(wps->wps, WPS_M4, wps->config_error,
+ wps->error_indication);
+ break;
+ case WPS_M6:
+ if (wps_validate_m6(msg) < 0)
+ return WPS_FAILURE;
+ ret = wps_process_m6(wps, msg, &attr);
+ if (ret == WPS_FAILURE || wps->state == SEND_WSC_NACK)
+ wps_fail_event(wps->wps, WPS_M6, wps->config_error,
+ wps->error_indication);
+ break;
+ case WPS_M8:
+ if (wps_validate_m8(msg) < 0)
+ return WPS_FAILURE;
+ ret = wps_process_m8(wps, msg, &attr);
+ if (ret == WPS_FAILURE || wps->state == SEND_WSC_NACK)
+ wps_fail_event(wps->wps, WPS_M8, wps->config_error,
+ wps->error_indication);
+ break;
+ default:
+ wpa_printf(MSG_DEBUG, "WPS: Unsupported Message Type %d",
+ *attr.msg_type);
+ return WPS_FAILURE;
+ }
+
+ /*
+ * Save a copy of the last message for Authenticator derivation if we
+ * are continuing. However, skip M2D since it is not authenticated and
+ * neither is the ACK/NACK response frame. This allows the possibly
+ * following M2 to be processed correctly by using the previously sent
+ * M1 in Authenticator derivation.
+ */
+ if (ret == WPS_CONTINUE && *attr.msg_type != WPS_M2D) {
+ /* Save a copy of the last message for Authenticator derivation
+ */
+ wpabuf_free(wps->last_msg);
+ wps->last_msg = wpabuf_dup(msg);
+ }
+
+ return ret;
+}
+
+
+static enum wps_process_res wps_process_wsc_ack(struct wps_data *wps,
+ const struct wpabuf *msg)
+{
+ struct wps_parse_attr attr;
+
+ wpa_printf(MSG_DEBUG, "WPS: Received WSC_ACK");
+
+ if (wps_parse_msg(msg, &attr) < 0)
+ return WPS_FAILURE;
+
+ if (attr.msg_type == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Message Type attribute");
+ return WPS_FAILURE;
+ }
+
+ if (*attr.msg_type != WPS_WSC_ACK) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Message Type %d",
+ *attr.msg_type);
+ return WPS_FAILURE;
+ }
+
+ if (attr.registrar_nonce == NULL ||
+ os_memcmp(wps->nonce_r, attr.registrar_nonce, WPS_NONCE_LEN != 0))
+ {
+ wpa_printf(MSG_DEBUG, "WPS: Mismatch in registrar nonce");
+ return WPS_FAILURE;
+ }
+
+ if (attr.enrollee_nonce == NULL ||
+ os_memcmp(wps->nonce_e, attr.enrollee_nonce, WPS_NONCE_LEN != 0)) {
+ wpa_printf(MSG_DEBUG, "WPS: Mismatch in enrollee nonce");
+ return WPS_FAILURE;
+ }
+
+ if (wps->state == RECV_ACK && wps->wps->ap) {
+ wpa_printf(MSG_DEBUG, "WPS: External Registrar registration "
+ "completed successfully");
+ wps_success_event(wps->wps);
+ wps->state = WPS_FINISHED;
+ return WPS_DONE;
+ }
+
+ return WPS_FAILURE;
+}
+
+
+static enum wps_process_res wps_process_wsc_nack(struct wps_data *wps,
+ const struct wpabuf *msg)
+{
+ struct wps_parse_attr attr;
+ u16 config_error;
+
+ wpa_printf(MSG_DEBUG, "WPS: Received WSC_NACK");
+
+ if (wps_parse_msg(msg, &attr) < 0)
+ return WPS_FAILURE;
+
+ if (attr.msg_type == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Message Type attribute");
+ return WPS_FAILURE;
+ }
+
+ if (*attr.msg_type != WPS_WSC_NACK) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Message Type %d",
+ *attr.msg_type);
+ return WPS_FAILURE;
+ }
+
+ if (attr.registrar_nonce == NULL ||
+ os_memcmp(wps->nonce_r, attr.registrar_nonce, WPS_NONCE_LEN != 0))
+ {
+ wpa_printf(MSG_DEBUG, "WPS: Mismatch in registrar nonce");
+ wpa_hexdump(MSG_DEBUG, "WPS: Received Registrar Nonce",
+ attr.registrar_nonce, WPS_NONCE_LEN);
+ wpa_hexdump(MSG_DEBUG, "WPS: Expected Registrar Nonce",
+ wps->nonce_r, WPS_NONCE_LEN);
+ return WPS_FAILURE;
+ }
+
+ if (attr.enrollee_nonce == NULL ||
+ os_memcmp(wps->nonce_e, attr.enrollee_nonce, WPS_NONCE_LEN != 0)) {
+ wpa_printf(MSG_DEBUG, "WPS: Mismatch in enrollee nonce");
+ wpa_hexdump(MSG_DEBUG, "WPS: Received Enrollee Nonce",
+ attr.enrollee_nonce, WPS_NONCE_LEN);
+ wpa_hexdump(MSG_DEBUG, "WPS: Expected Enrollee Nonce",
+ wps->nonce_e, WPS_NONCE_LEN);
+ return WPS_FAILURE;
+ }
+
+ if (attr.config_error == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Configuration Error attribute "
+ "in WSC_NACK");
+ return WPS_FAILURE;
+ }
+
+ config_error = WPA_GET_BE16(attr.config_error);
+ wpa_printf(MSG_DEBUG, "WPS: Registrar terminated negotiation with "
+ "Configuration Error %d", config_error);
+
+ switch (wps->state) {
+ case RECV_M4:
+ wps_fail_event(wps->wps, WPS_M3, config_error,
+ wps->error_indication);
+ break;
+ case RECV_M6:
+ wps_fail_event(wps->wps, WPS_M5, config_error,
+ wps->error_indication);
+ break;
+ case RECV_M8:
+ wps_fail_event(wps->wps, WPS_M7, config_error,
+ wps->error_indication);
+ break;
+ default:
+ break;
+ }
+
+ /* Followed by NACK if Enrollee is Supplicant or EAP-Failure if
+ * Enrollee is Authenticator */
+ wps->state = SEND_WSC_NACK;
+
+ return WPS_FAILURE;
+}
+
+
+enum wps_process_res wps_enrollee_process_msg(struct wps_data *wps,
+ enum wsc_op_code op_code,
+ const struct wpabuf *msg)
+{
+
+ wpa_printf(MSG_DEBUG, "WPS: Processing received message (len=%lu "
+ "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:
+ return wps_process_wsc_msg(wps, msg);
+ case WSC_ACK:
+ if (wps_validate_wsc_ack(msg) < 0)
+ return WPS_FAILURE;
+ return wps_process_wsc_ack(wps, msg);
+ case WSC_NACK:
+ if (wps_validate_wsc_nack(msg) < 0)
+ return WPS_FAILURE;
+ return wps_process_wsc_nack(wps, msg);
+ default:
+ wpa_printf(MSG_DEBUG, "WPS: Unsupported op_code %d", op_code);
+ return WPS_FAILURE;
+ }
+}
diff --git a/src/wps/wps_er.c b/src/wps/wps_er.c
new file mode 100644
index 0000000..a461836
--- /dev/null
+++ b/src/wps/wps_er.c
@@ -0,0 +1,1959 @@
+/*
+ * Wi-Fi Protected Setup - External Registrar
+ * Copyright (c) 2009, 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.
+ */
+
+#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)
+{
+ 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))
+ 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)) {
+ eloop_cancel_timeout(wps_er_deinit_finish, er, NULL);
+ 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);
+ 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 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 *data = wpabuf_head(reply);
+ char *pos;
+
+ wpa_hexdump_ascii(MSG_MSGDUMP, "WPS ER: Device info",
+ wpabuf_head(reply), wpabuf_len(reply));
+
+ 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");
+ 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);
+ 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 = os_malloc(attr->manufacturer_len + 1);
+ if (sta->manufacturer) {
+ os_memcpy(sta->manufacturer, attr->manufacturer,
+ attr->manufacturer_len);
+ sta->manufacturer[attr->manufacturer_len] = '\0';
+ }
+ }
+
+ if (attr->model_name) {
+ os_free(sta->model_name);
+ sta->model_name = os_malloc(attr->model_name_len + 1);
+ if (sta->model_name) {
+ os_memcpy(sta->model_name, attr->model_name,
+ attr->model_name_len);
+ sta->model_name[attr->model_name_len] = '\0';
+ }
+ }
+
+ if (attr->model_number) {
+ os_free(sta->model_number);
+ sta->model_number = os_malloc(attr->model_number_len + 1);
+ if (sta->model_number) {
+ os_memcpy(sta->model_number, attr->model_number,
+ attr->model_number_len);
+ sta->model_number[attr->model_number_len] = '\0';
+ }
+ }
+
+ if (attr->serial_number) {
+ os_free(sta->serial_number);
+ sta->serial_number = os_malloc(attr->serial_number_len + 1);
+ if (sta->serial_number) {
+ os_memcpy(sta->serial_number, attr->serial_number,
+ attr->serial_number_len);
+ sta->serial_number[attr->serial_number_len] = '\0';
+ }
+ }
+
+ if (attr->dev_name) {
+ os_free(sta->dev_name);
+ sta->dev_name = os_malloc(attr->dev_name_len + 1);
+ if (sta->dev_name) {
+ os_memcpy(sta->dev_name, attr->dev_name,
+ attr->dev_name_len);
+ sta->dev_name[attr->dev_name_len] = '\0';
+ }
+ }
+
+ 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) {
+ 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(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?", 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;
+
+ wpa_printf(MSG_DEBUG, "WPS ER: Finishing deinit");
+
+ 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)
+{
+#ifdef CONFIG_WPS2
+ wpabuf_put_be16(msg, ATTR_UUID_R);
+ wpabuf_put_be16(msg, WPS_UUID_LEN);
+ wpabuf_put_data(msg, uuid_r, WPS_UUID_LEN);
+#endif /* CONFIG_WPS2 */
+ 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;
+#ifdef CONFIG_WPS2
+ u8 bcast[ETH_ALEN];
+#endif /* CONFIG_WPS2 */
+ 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);
+#ifdef CONFIG_WPS2
+ if (count == 0) {
+ os_memset(bcast, 0xff, ETH_ALEN);
+ auth_macs = bcast;
+ count = 1;
+ }
+#endif /* CONFIG_WPS2 */
+
+ 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)
+{
+ 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;
+ }
+
+ ap = wps_er_ap_get(er, NULL, uuid);
+ 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, NULL, 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);
+}
+
+
+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 == NULL)
+ break;
+ msg = os_zalloc(wpabuf_len(reply) + 1);
+ if (msg == 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");
+ return;
+ }
+
+ buf = wps_er_soap_hdr(msg, "PutMessage", "NewInMessage", path, &dst,
+ &len_ptr, &body_ptr);
+ os_free(url);
+ if (buf == NULL)
+ return;
+
+ 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);
+}
+
+
+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 *pin,
+ size_t pin_len)
+{
+ struct wps_er_ap *ap;
+
+ if (er == NULL)
+ return -1;
+
+ ap = wps_er_ap_get(er, NULL, uuid);
+ if (ap == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS ER: AP not found for learn "
+ "request");
+ return -1;
+ }
+ 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 struct wps_credential *cred)
+{
+ struct wps_er_ap *ap;
+
+ if (er == NULL)
+ return -1;
+
+ ap = wps_er_ap_get(er, NULL, uuid);
+ 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 *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);
+ if (ap == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS ER: AP not found for config "
+ "request");
+ return -1;
+ }
+ 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;
+}
diff --git a/src/wps/wps_er.h b/src/wps/wps_er.h
new file mode 100644
index 0000000..5388ed1
--- /dev/null
+++ b/src/wps/wps_er.h
@@ -0,0 +1,117 @@
+/*
+ * Wi-Fi Protected Setup - External Registrar
+ * Copyright (c) 2009, 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.
+ */
+
+#ifndef WPS_ER_H
+#define WPS_ER_H
+
+#include "utils/list.h"
+
+struct wps_er_sta {
+ struct dl_list list;
+ struct wps_er_ap *ap;
+ u8 addr[ETH_ALEN];
+ u16 config_methods;
+ u8 uuid[WPS_UUID_LEN];
+ u8 pri_dev_type[8];
+ u16 dev_passwd_id;
+ int m1_received;
+ char *manufacturer;
+ char *model_name;
+ char *model_number;
+ char *serial_number;
+ char *dev_name;
+ struct wps_data *wps;
+ struct http_client *http;
+ struct wps_credential *cred;
+};
+
+struct wps_er_ap {
+ struct dl_list list;
+ struct wps_er *er;
+ struct dl_list sta; /* list of STAs/Enrollees using this AP */
+ struct in_addr addr;
+ char *location;
+ struct http_client *http;
+ struct wps_data *wps;
+
+ u8 uuid[WPS_UUID_LEN];
+ u8 pri_dev_type[8];
+ u8 wps_state;
+ u8 mac_addr[ETH_ALEN];
+ char *friendly_name;
+ char *manufacturer;
+ char *manufacturer_url;
+ char *model_description;
+ char *model_name;
+ char *model_number;
+ char *model_url;
+ char *serial_number;
+ char *udn;
+ char *upc;
+
+ char *scpd_url;
+ char *control_url;
+ char *event_sub_url;
+
+ int subscribed;
+ u8 sid[WPS_UUID_LEN];
+ unsigned int id;
+
+ struct wps_credential *ap_settings;
+
+ void (*m1_handler)(struct wps_er_ap *ap, struct wpabuf *m1);
+};
+
+struct wps_er_ap_settings {
+ struct dl_list list;
+ u8 uuid[WPS_UUID_LEN];
+ struct wps_credential ap_settings;
+};
+
+struct wps_er {
+ struct wps_context *wps;
+ char ifname[17];
+ u8 mac_addr[ETH_ALEN]; /* mac addr of network i.f. we use */
+ char *ip_addr_text; /* IP address of network i.f. we use */
+ unsigned ip_addr; /* IP address of network i.f. we use (host order) */
+ int multicast_sd;
+ int ssdp_sd;
+ struct dl_list ap;
+ struct dl_list ap_unsubscribing;
+ struct dl_list ap_settings;
+ struct http_server *http_srv;
+ int http_port;
+ unsigned int next_ap_id;
+ unsigned int event_id;
+ int deinitializing;
+ void (*deinit_done_cb)(void *ctx);
+ void *deinit_done_ctx;
+ struct in_addr filter_addr;
+ int skip_set_sel_reg;
+ const u8 *set_sel_reg_uuid_filter;
+};
+
+
+/* wps_er.c */
+void wps_er_ap_add(struct wps_er *er, const u8 *uuid, struct in_addr *addr,
+ const char *location, int max_age);
+void wps_er_ap_remove(struct wps_er *er, struct in_addr *addr);
+int wps_er_ap_cache_settings(struct wps_er *er, struct in_addr *addr);
+
+/* wps_er_ssdp.c */
+int wps_er_ssdp_init(struct wps_er *er);
+void wps_er_ssdp_deinit(struct wps_er *er);
+void wps_er_send_ssdp_msearch(struct wps_er *er);
+
+#endif /* WPS_ER_H */
diff --git a/src/wps/wps_er_ssdp.c b/src/wps/wps_er_ssdp.c
new file mode 100644
index 0000000..83de9ad
--- /dev/null
+++ b/src/wps/wps_er_ssdp.c
@@ -0,0 +1,211 @@
+/*
+ * Wi-Fi Protected Setup - External Registrar (SSDP)
+ * Copyright (c) 2009, 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.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "uuid.h"
+#include "eloop.h"
+#include "wps_i.h"
+#include "wps_upnp.h"
+#include "wps_upnp_i.h"
+#include "wps_er.h"
+
+
+static void wps_er_ssdp_rx(int sd, void *eloop_ctx, void *sock_ctx)
+{
+ struct wps_er *er = eloop_ctx;
+ struct sockaddr_in addr; /* client address */
+ socklen_t addr_len;
+ int nread;
+ char buf[MULTICAST_MAX_READ], *pos, *pos2, *start;
+ int wfa = 0, byebye = 0;
+ int max_age = -1;
+ char *location = NULL;
+ u8 uuid[WPS_UUID_LEN];
+
+ addr_len = sizeof(addr);
+ nread = recvfrom(sd, buf, sizeof(buf) - 1, 0,
+ (struct sockaddr *) &addr, &addr_len);
+ if (nread <= 0)
+ return;
+ buf[nread] = '\0';
+ if (er->filter_addr.s_addr &&
+ er->filter_addr.s_addr != addr.sin_addr.s_addr)
+ return;
+
+ wpa_printf(MSG_DEBUG, "WPS ER: Received SSDP from %s",
+ inet_ntoa(addr.sin_addr));
+ wpa_hexdump_ascii(MSG_MSGDUMP, "WPS ER: Received SSDP contents",
+ (u8 *) buf, nread);
+
+ if (sd == er->multicast_sd) {
+ /* Reply to M-SEARCH */
+ if (os_strncmp(buf, "HTTP/1.1 200 OK", 15) != 0)
+ return; /* unexpected response header */
+ } else {
+ /* Unsolicited message (likely NOTIFY or M-SEARCH) */
+ if (os_strncmp(buf, "NOTIFY ", 7) != 0)
+ return; /* only process notifications */
+ }
+
+ os_memset(uuid, 0, sizeof(uuid));
+
+ for (start = buf; start && *start; start = pos) {
+ pos = os_strchr(start, '\n');
+ if (pos) {
+ if (pos[-1] == '\r')
+ pos[-1] = '\0';
+ *pos++ = '\0';
+ }
+ if (os_strstr(start, "schemas-wifialliance-org:device:"
+ "WFADevice:1"))
+ wfa = 1;
+ if (os_strstr(start, "schemas-wifialliance-org:service:"
+ "WFAWLANConfig:1"))
+ wfa = 1;
+ if (os_strncasecmp(start, "LOCATION:", 9) == 0) {
+ start += 9;
+ while (*start == ' ')
+ start++;
+ location = start;
+ } else if (os_strncasecmp(start, "NTS:", 4) == 0) {
+ if (os_strstr(start, "ssdp:byebye"))
+ byebye = 1;
+ } else if (os_strncasecmp(start, "CACHE-CONTROL:", 14) == 0) {
+ start += 9;
+ while (*start == ' ')
+ start++;
+ pos2 = os_strstr(start, "max-age=");
+ if (pos2 == NULL)
+ continue;
+ pos2 += 8;
+ max_age = atoi(pos2);
+ } else if (os_strncasecmp(start, "USN:", 4) == 0) {
+ start += 4;
+ pos2 = os_strstr(start, "uuid:");
+ if (pos2) {
+ pos2 += 5;
+ while (*pos2 == ' ')
+ pos2++;
+ if (uuid_str2bin(pos2, uuid) < 0) {
+ wpa_printf(MSG_DEBUG, "WPS ER: "
+ "Invalid UUID in USN: %s",
+ pos2);
+ return;
+ }
+ }
+ }
+ }
+
+ if (!wfa)
+ return; /* Not WPS advertisement/reply */
+
+ if (byebye) {
+ wps_er_ap_cache_settings(er, &addr.sin_addr);
+ wps_er_ap_remove(er, &addr.sin_addr);
+ return;
+ }
+
+ if (!location)
+ return; /* Unknown location */
+
+ if (max_age < 1)
+ return; /* No max-age reported */
+
+ wpa_printf(MSG_DEBUG, "WPS ER: AP discovered: %s "
+ "(packet source: %s max-age: %d)",
+ location, inet_ntoa(addr.sin_addr), max_age);
+
+ wps_er_ap_add(er, uuid, &addr.sin_addr, location, max_age);
+}
+
+
+void wps_er_send_ssdp_msearch(struct wps_er *er)
+{
+ struct wpabuf *msg;
+ struct sockaddr_in dest;
+
+ msg = wpabuf_alloc(500);
+ if (msg == NULL)
+ return;
+
+ wpabuf_put_str(msg,
+ "M-SEARCH * HTTP/1.1\r\n"
+ "HOST: 239.255.255.250:1900\r\n"
+ "MAN: \"ssdp:discover\"\r\n"
+ "MX: 3\r\n"
+ "ST: urn:schemas-wifialliance-org:device:WFADevice:1"
+ "\r\n"
+ "\r\n");
+
+ 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);
+
+ if (sendto(er->multicast_sd, wpabuf_head(msg), wpabuf_len(msg), 0,
+ (struct sockaddr *) &dest, sizeof(dest)) < 0)
+ wpa_printf(MSG_DEBUG, "WPS ER: M-SEARCH sendto failed: "
+ "%d (%s)", errno, strerror(errno));
+
+ wpabuf_free(msg);
+}
+
+
+int wps_er_ssdp_init(struct wps_er *er)
+{
+ if (add_ssdp_network(er->ifname)) {
+ wpa_printf(MSG_INFO, "WPS ER: Failed to add routing entry for "
+ "SSDP");
+ return -1;
+ }
+
+ er->multicast_sd = ssdp_open_multicast_sock(er->ip_addr);
+ if (er->multicast_sd < 0) {
+ wpa_printf(MSG_INFO, "WPS ER: Failed to open multicast socket "
+ "for SSDP");
+ return -1;
+ }
+
+ er->ssdp_sd = ssdp_listener_open();
+ if (er->ssdp_sd < 0) {
+ wpa_printf(MSG_INFO, "WPS ER: Failed to open SSDP listener "
+ "socket");
+ return -1;
+ }
+
+ if (eloop_register_sock(er->multicast_sd, EVENT_TYPE_READ,
+ wps_er_ssdp_rx, er, NULL) ||
+ eloop_register_sock(er->ssdp_sd, EVENT_TYPE_READ,
+ wps_er_ssdp_rx, er, NULL))
+ return -1;
+
+ wps_er_send_ssdp_msearch(er);
+
+ return 0;
+}
+
+
+void wps_er_ssdp_deinit(struct wps_er *er)
+{
+ if (er->multicast_sd >= 0) {
+ eloop_unregister_sock(er->multicast_sd, EVENT_TYPE_READ);
+ close(er->multicast_sd);
+ }
+ if (er->ssdp_sd >= 0) {
+ eloop_unregister_sock(er->ssdp_sd, EVENT_TYPE_READ);
+ close(er->ssdp_sd);
+ }
+}
diff --git a/src/wps/wps_i.h b/src/wps/wps_i.h
new file mode 100644
index 0000000..437999b
--- /dev/null
+++ b/src/wps/wps_i.h
@@ -0,0 +1,301 @@
+/*
+ * Wi-Fi Protected Setup - internal definitions
+ * Copyright (c) 2008-2009, 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.
+ */
+
+#ifndef WPS_I_H
+#define WPS_I_H
+
+#include "wps.h"
+
+/**
+ * struct wps_data - WPS registration protocol data
+ *
+ * This data is stored at the EAP-WSC server/peer method and it is kept for a
+ * single registration protocol run.
+ */
+struct wps_data {
+ /**
+ * wps - Pointer to long term WPS context
+ */
+ struct wps_context *wps;
+
+ /**
+ * registrar - Whether this end is a Registrar
+ */
+ int registrar;
+
+ /**
+ * er - Whether the local end is an external registrar
+ */
+ int er;
+
+ enum {
+ /* Enrollee states */
+ SEND_M1, RECV_M2, SEND_M3, RECV_M4, SEND_M5, RECV_M6, SEND_M7,
+ RECV_M8, RECEIVED_M2D, WPS_MSG_DONE, RECV_ACK, WPS_FINISHED,
+ SEND_WSC_NACK,
+
+ /* Registrar states */
+ RECV_M1, SEND_M2, RECV_M3, SEND_M4, RECV_M5, SEND_M6,
+ RECV_M7, SEND_M8, RECV_DONE, SEND_M2D, RECV_M2D_ACK
+ } state;
+
+ u8 uuid_e[WPS_UUID_LEN];
+ u8 uuid_r[WPS_UUID_LEN];
+ u8 mac_addr_e[ETH_ALEN];
+ u8 nonce_e[WPS_NONCE_LEN];
+ u8 nonce_r[WPS_NONCE_LEN];
+ u8 psk1[WPS_PSK_LEN];
+ u8 psk2[WPS_PSK_LEN];
+ u8 snonce[2 * WPS_SECRET_NONCE_LEN];
+ u8 peer_hash1[WPS_HASH_LEN];
+ u8 peer_hash2[WPS_HASH_LEN];
+
+ struct wpabuf *dh_privkey;
+ struct wpabuf *dh_pubkey_e;
+ struct wpabuf *dh_pubkey_r;
+ u8 authkey[WPS_AUTHKEY_LEN];
+ u8 keywrapkey[WPS_KEYWRAPKEY_LEN];
+ u8 emsk[WPS_EMSK_LEN];
+
+ struct wpabuf *last_msg;
+
+ u8 *dev_password;
+ size_t dev_password_len;
+ u16 dev_pw_id;
+ int pbc;
+
+ /**
+ * request_type - Request Type attribute from (Re)AssocReq
+ */
+ u8 request_type;
+
+ /**
+ * encr_type - Available encryption types
+ */
+ u16 encr_type;
+
+ /**
+ * auth_type - Available authentication types
+ */
+ u16 auth_type;
+
+ u8 *new_psk;
+ size_t new_psk_len;
+
+ int wps_pin_revealed;
+ struct wps_credential cred;
+
+ struct wps_device_data peer_dev;
+
+ /**
+ * config_error - Configuration Error value to be used in NACK
+ */
+ u16 config_error;
+ u16 error_indication;
+
+ int ext_reg;
+ int int_reg;
+
+ struct wps_credential *new_ap_settings;
+
+ void *dh_ctx;
+
+ void (*ap_settings_cb)(void *ctx, const struct wps_credential *cred);
+ void *ap_settings_cb_ctx;
+
+ struct wps_credential *use_cred;
+
+ int use_psk_key;
+ u8 p2p_dev_addr[ETH_ALEN]; /* P2P Device Address of the client or
+ * 00:00:00:00:00:00 if not a P2p client */
+};
+
+
+struct wps_parse_attr {
+ /* fixed length fields */
+ const u8 *version; /* 1 octet */
+ const u8 *version2; /* 1 octet */
+ const u8 *msg_type; /* 1 octet */
+ const u8 *enrollee_nonce; /* WPS_NONCE_LEN (16) octets */
+ const u8 *registrar_nonce; /* WPS_NONCE_LEN (16) octets */
+ const u8 *uuid_r; /* WPS_UUID_LEN (16) octets */
+ const u8 *uuid_e; /* WPS_UUID_LEN (16) octets */
+ const u8 *auth_type_flags; /* 2 octets */
+ const u8 *encr_type_flags; /* 2 octets */
+ const u8 *conn_type_flags; /* 1 octet */
+ const u8 *config_methods; /* 2 octets */
+ const u8 *sel_reg_config_methods; /* 2 octets */
+ const u8 *primary_dev_type; /* 8 octets */
+ const u8 *rf_bands; /* 1 octet */
+ const u8 *assoc_state; /* 2 octets */
+ const u8 *config_error; /* 2 octets */
+ const u8 *dev_password_id; /* 2 octets */
+ const u8 *oob_dev_password; /* WPS_OOB_DEVICE_PASSWORD_ATTR_LEN (54)
+ * octets */
+ const u8 *os_version; /* 4 octets */
+ const u8 *wps_state; /* 1 octet */
+ const u8 *authenticator; /* WPS_AUTHENTICATOR_LEN (8) octets */
+ const u8 *r_hash1; /* WPS_HASH_LEN (32) octets */
+ const u8 *r_hash2; /* WPS_HASH_LEN (32) octets */
+ const u8 *e_hash1; /* WPS_HASH_LEN (32) octets */
+ const u8 *e_hash2; /* WPS_HASH_LEN (32) octets */
+ const u8 *r_snonce1; /* WPS_SECRET_NONCE_LEN (16) octets */
+ const u8 *r_snonce2; /* WPS_SECRET_NONCE_LEN (16) octets */
+ const u8 *e_snonce1; /* WPS_SECRET_NONCE_LEN (16) octets */
+ const u8 *e_snonce2; /* WPS_SECRET_NONCE_LEN (16) octets */
+ const u8 *key_wrap_auth; /* WPS_KWA_LEN (8) octets */
+ const u8 *auth_type; /* 2 octets */
+ const u8 *encr_type; /* 2 octets */
+ const u8 *network_idx; /* 1 octet */
+ const u8 *network_key_idx; /* 1 octet */
+ const u8 *mac_addr; /* ETH_ALEN (6) octets */
+ const u8 *key_prov_auto; /* 1 octet (Bool) */
+ const u8 *dot1x_enabled; /* 1 octet (Bool) */
+ 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 */
+ const u8 *settings_delay_time; /* 1 octet */
+ const u8 *network_key_shareable; /* 1 octet (Bool) */
+ const u8 *request_to_enroll; /* 1 octet (Bool) */
+
+ /* variable length fields */
+ const u8 *manufacturer;
+ size_t manufacturer_len;
+ const u8 *model_name;
+ size_t model_name_len;
+ const u8 *model_number;
+ size_t model_number_len;
+ const u8 *serial_number;
+ size_t serial_number_len;
+ const u8 *dev_name;
+ size_t dev_name_len;
+ const u8 *public_key;
+ size_t public_key_len;
+ const u8 *encr_settings;
+ size_t encr_settings_len;
+ const u8 *ssid; /* <= 32 octets */
+ size_t ssid_len;
+ const u8 *network_key; /* <= 64 octets */
+ size_t network_key_len;
+ const u8 *eap_type; /* <= 8 octets */
+ size_t eap_type_len;
+ const u8 *eap_identity; /* <= 64 octets */
+ size_t eap_identity_len;
+ const u8 *authorized_macs; /* <= 30 octets */
+ size_t authorized_macs_len;
+ const u8 *sec_dev_type_list; /* <= 128 octets */
+ size_t sec_dev_type_list_len;
+
+ /* attributes that can occur multiple times */
+#define MAX_CRED_COUNT 10
+ const u8 *cred[MAX_CRED_COUNT];
+ size_t cred_len[MAX_CRED_COUNT];
+ size_t num_cred;
+
+#define MAX_REQ_DEV_TYPE_COUNT 10
+ const u8 *req_dev_type[MAX_REQ_DEV_TYPE_COUNT];
+ size_t num_req_dev_type;
+
+ const u8 *vendor_ext[MAX_WPS_PARSE_VENDOR_EXT];
+ size_t vendor_ext_len[MAX_WPS_PARSE_VENDOR_EXT];
+ size_t num_vendor_ext;
+};
+
+/* wps_common.c */
+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);
+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,
+ size_t encr_len);
+void wps_fail_event(struct wps_context *wps, enum wps_msg_type msg,
+ u16 config_error, u16 error_indication);
+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);
+
+extern struct oob_device_data oob_ufd_device_data;
+extern struct oob_device_data oob_nfc_device_data;
+extern struct oob_nfc_device_data oob_nfc_pn531_device_data;
+
+struct wpabuf * wps_build_wsc_ack(struct wps_data *wps);
+struct wpabuf * wps_build_wsc_nack(struct wps_data *wps);
+
+/* wps_attr_parse.c */
+int wps_parse_msg(const struct wpabuf *msg, struct wps_parse_attr *attr);
+
+/* wps_attr_build.c */
+int wps_build_public_key(struct wps_data *wps, struct wpabuf *msg);
+int wps_build_req_type(struct wpabuf *msg, enum wps_request_type type);
+int wps_build_resp_type(struct wpabuf *msg, enum wps_response_type type);
+int wps_build_config_methods(struct wpabuf *msg, u16 methods);
+int wps_build_uuid_e(struct wpabuf *msg, const u8 *uuid);
+int wps_build_dev_password_id(struct wpabuf *msg, u16 id);
+int wps_build_config_error(struct wpabuf *msg, u16 err);
+int wps_build_authenticator(struct wps_data *wps, struct wpabuf *msg);
+int wps_build_key_wrap_auth(struct wps_data *wps, struct wpabuf *msg);
+int wps_build_encr_settings(struct wps_data *wps, struct wpabuf *msg,
+ struct wpabuf *plain);
+int wps_build_version(struct wpabuf *msg);
+int wps_build_wfa_ext(struct wpabuf *msg, int req_to_enroll,
+ const u8 *auth_macs, size_t auth_macs_count);
+int wps_build_msg_type(struct wpabuf *msg, enum wps_msg_type msg_type);
+int wps_build_enrollee_nonce(struct wps_data *wps, struct wpabuf *msg);
+int wps_build_registrar_nonce(struct wps_data *wps, struct wpabuf *msg);
+int wps_build_auth_type_flags(struct wps_data *wps, struct wpabuf *msg);
+int wps_build_encr_type_flags(struct wps_data *wps, struct wpabuf *msg);
+int wps_build_conn_type_flags(struct wps_data *wps, struct wpabuf *msg);
+int wps_build_assoc_state(struct wps_data *wps, struct wpabuf *msg);
+int wps_build_oob_dev_password(struct wpabuf *msg, struct wps_context *wps);
+struct wpabuf * wps_ie_encapsulate(struct wpabuf *data);
+
+/* wps_attr_process.c */
+int wps_process_authenticator(struct wps_data *wps, const u8 *authenticator,
+ const struct wpabuf *msg);
+int wps_process_key_wrap_auth(struct wps_data *wps, struct wpabuf *msg,
+ const u8 *key_wrap_auth);
+int wps_process_cred(struct wps_parse_attr *attr,
+ struct wps_credential *cred);
+int wps_process_ap_settings(struct wps_parse_attr *attr,
+ struct wps_credential *cred);
+
+/* wps_enrollee.c */
+struct wpabuf * wps_enrollee_get_msg(struct wps_data *wps,
+ enum wsc_op_code *op_code);
+enum wps_process_res wps_enrollee_process_msg(struct wps_data *wps,
+ enum wsc_op_code op_code,
+ const struct wpabuf *msg);
+
+/* wps_registrar.c */
+struct wpabuf * wps_registrar_get_msg(struct wps_data *wps,
+ enum wsc_op_code *op_code);
+enum wps_process_res wps_registrar_process_msg(struct wps_data *wps,
+ enum wsc_op_code op_code,
+ const struct wpabuf *msg);
+int wps_build_cred(struct wps_data *wps, struct wpabuf *msg);
+int wps_device_store(struct wps_registrar *reg,
+ struct wps_device_data *dev, const u8 *uuid);
+void wps_registrar_selected_registrar_changed(struct wps_registrar *reg);
+const u8 * wps_authorized_macs(struct wps_registrar *reg, size_t *count);
+int wps_registrar_pbc_overlap(struct wps_registrar *reg,
+ const u8 *addr, const u8 *uuid_e);
+
+/* ndef.c */
+struct wpabuf * ndef_parse_wifi(struct wpabuf *buf);
+struct wpabuf * ndef_build_wifi(struct wpabuf *buf);
+
+#endif /* WPS_I_H */
diff --git a/src/wps/wps_nfc.c b/src/wps/wps_nfc.c
new file mode 100644
index 0000000..ff12000
--- /dev/null
+++ b/src/wps/wps_nfc.c
@@ -0,0 +1,117 @@
+/*
+ * NFC routines for Wi-Fi Protected Setup
+ * Copyright (c) 2009, Masashi Honma <honma@ictec.co.jp>
+ *
+ * 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.
+ */
+
+#include "includes.h"
+#include "common.h"
+
+#include "wps/wps.h"
+#include "wps_i.h"
+
+
+struct wps_nfc_data {
+ struct oob_nfc_device_data *oob_nfc_dev;
+};
+
+
+static void * init_nfc(struct wps_context *wps,
+ struct oob_device_data *oob_dev, int registrar)
+{
+ struct oob_nfc_device_data *oob_nfc_dev;
+ struct wps_nfc_data *data;
+
+ oob_nfc_dev = wps_get_oob_nfc_device(oob_dev->device_name);
+ if (oob_nfc_dev == NULL) {
+ wpa_printf(MSG_ERROR, "WPS (NFC): Unknown NFC device (%s)",
+ oob_dev->device_name);
+ return NULL;
+ }
+
+ if (oob_nfc_dev->init_func(oob_dev->device_path) < 0)
+ return NULL;
+
+ data = os_zalloc(sizeof(*data));
+ if (data == NULL) {
+ wpa_printf(MSG_ERROR, "WPS (NFC): Failed to allocate "
+ "nfc data area");
+ return NULL;
+ }
+ data->oob_nfc_dev = oob_nfc_dev;
+ return data;
+}
+
+
+static struct wpabuf * read_nfc(void *priv)
+{
+ struct wps_nfc_data *data = priv;
+ struct wpabuf *wifi, *buf;
+ char *raw_data;
+ size_t len;
+
+ raw_data = data->oob_nfc_dev->read_func(&len);
+ if (raw_data == NULL)
+ return NULL;
+
+ wifi = wpabuf_alloc_copy(raw_data, len);
+ os_free(raw_data);
+ if (wifi == NULL) {
+ wpa_printf(MSG_ERROR, "WPS (NFC): Failed to allocate "
+ "nfc read area");
+ return NULL;
+ }
+
+ buf = ndef_parse_wifi(wifi);
+ wpabuf_free(wifi);
+ if (buf == NULL)
+ wpa_printf(MSG_ERROR, "WPS (NFC): Failed to unwrap");
+ return buf;
+}
+
+
+static int write_nfc(void *priv, struct wpabuf *buf)
+{
+ struct wps_nfc_data *data = priv;
+ struct wpabuf *wifi;
+ int ret;
+
+ wifi = ndef_build_wifi(buf);
+ if (wifi == NULL) {
+ wpa_printf(MSG_ERROR, "WPS (NFC): Failed to wrap");
+ return -1;
+ }
+
+ ret = data->oob_nfc_dev->write_func(wpabuf_mhead(wifi),
+ wpabuf_len(wifi));
+ wpabuf_free(wifi);
+ return ret;
+}
+
+
+static void deinit_nfc(void *priv)
+{
+ struct wps_nfc_data *data = priv;
+
+ data->oob_nfc_dev->deinit_func();
+
+ os_free(data);
+}
+
+
+struct oob_device_data oob_nfc_device_data = {
+ .device_name = NULL,
+ .device_path = NULL,
+ .init_func = init_nfc,
+ .read_func = read_nfc,
+ .write_func = write_nfc,
+ .deinit_func = deinit_nfc,
+};
diff --git a/src/wps/wps_nfc_pn531.c b/src/wps/wps_nfc_pn531.c
new file mode 100644
index 0000000..7e05e4d
--- /dev/null
+++ b/src/wps/wps_nfc_pn531.c
@@ -0,0 +1,113 @@
+/*
+ * NFC PN531 routines for Wi-Fi Protected Setup
+ * Copyright (c) 2009, Masashi Honma <honma@ictec.co.jp>
+ *
+ * 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.
+ */
+
+#include "includes.h"
+#include "common.h"
+
+#include "wps/wps.h"
+#include "wps_i.h"
+
+#include "WpsNfcType.h"
+#include "WpsNfc.h"
+
+
+static int init_nfc_pn531(char *path)
+{
+ u32 ret;
+
+ ret = WpsNfcInit();
+ if (ret != WPS_NFCLIB_ERR_SUCCESS) {
+ wpa_printf(MSG_ERROR, "WPS (PN531): Failed to initialize "
+ "NFC Library: 0x%08x", ret);
+ return -1;
+ }
+
+ ret = WpsNfcOpenDevice((int8 *) path);
+ if (ret != WPS_NFCLIB_ERR_SUCCESS) {
+ wpa_printf(MSG_ERROR, "WPS (PN531): Failed to open "
+ "NFC Device(%s): 0x%08x", path, ret);
+ goto fail;
+ }
+
+ ret = WpsNfcTokenDiscovery();
+ if (ret != WPS_NFCLIB_ERR_SUCCESS) {
+ wpa_printf(MSG_ERROR, "WPS (PN531): Failed to discover "
+ "token: 0x%08x", ret);
+ WpsNfcCloseDevice();
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ WpsNfcDeinit();
+ return -1;
+}
+
+
+static void * read_nfc_pn531(size_t *size)
+{
+ uint32 len;
+ u32 ret;
+ int8 *data;
+
+ ret = WpsNfcRawReadToken(&data, &len);
+ if (ret != WPS_NFCLIB_ERR_SUCCESS) {
+ wpa_printf(MSG_ERROR, "WPS (PN531): Failed to read: 0x%08x",
+ ret);
+ return NULL;
+ }
+
+ *size = len;
+ return data;
+}
+
+
+static int write_nfc_pn531(void *data, size_t len)
+{
+ u32 ret;
+
+ ret = WpsNfcRawWriteToken(data, len);
+ if (ret != WPS_NFCLIB_ERR_SUCCESS) {
+ wpa_printf(MSG_ERROR, "WPS (PN531): Failed to write: 0x%08x",
+ ret);
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static void deinit_nfc_pn531(void)
+{
+ u32 ret;
+
+ ret = WpsNfcCloseDevice();
+ if (ret != WPS_NFCLIB_ERR_SUCCESS)
+ wpa_printf(MSG_ERROR, "WPS (PN531): Failed to close "
+ "NFC Device: 0x%08x", ret);
+
+ ret = WpsNfcDeinit();
+ if (ret != WPS_NFCLIB_ERR_SUCCESS)
+ wpa_printf(MSG_ERROR, "WPS (PN531): Failed to deinitialize "
+ "NFC Library: 0x%08x", ret);
+}
+
+
+struct oob_nfc_device_data oob_nfc_pn531_device_data = {
+ .init_func = init_nfc_pn531,
+ .read_func = read_nfc_pn531,
+ .write_func = write_nfc_pn531,
+ .deinit_func = deinit_nfc_pn531,
+};
diff --git a/src/wps/wps_registrar.c b/src/wps/wps_registrar.c
new file mode 100644
index 0000000..65ff40d
--- /dev/null
+++ b/src/wps/wps_registrar.c
@@ -0,0 +1,3273 @@
+/*
+ * Wi-Fi Protected Setup - Registrar
+ * Copyright (c) 2008-2009, 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.
+ */
+
+#include "utils/includes.h"
+
+#include "utils/common.h"
+#include "utils/base64.h"
+#include "utils/eloop.h"
+#include "utils/uuid.h"
+#include "utils/list.h"
+#include "crypto/crypto.h"
+#include "crypto/sha256.h"
+#include "crypto/random.h"
+#include "common/ieee802_11_defs.h"
+#include "wps_i.h"
+#include "wps_dev_attr.h"
+#include "wps_upnp.h"
+#include "wps_upnp_i.h"
+
+#ifndef CONFIG_WPS_STRICT
+#define WPS_WORKAROUNDS
+#endif /* CONFIG_WPS_STRICT */
+
+struct wps_uuid_pin {
+ struct dl_list list;
+ u8 uuid[WPS_UUID_LEN];
+ int wildcard_uuid;
+ u8 *pin;
+ size_t pin_len;
+#define PIN_LOCKED BIT(0)
+#define PIN_EXPIRES BIT(1)
+ int flags;
+ struct os_time expiration;
+ u8 enrollee_addr[ETH_ALEN];
+};
+
+
+static void wps_free_pin(struct wps_uuid_pin *pin)
+{
+ os_free(pin->pin);
+ os_free(pin);
+}
+
+
+static void wps_remove_pin(struct wps_uuid_pin *pin)
+{
+ dl_list_del(&pin->list);
+ wps_free_pin(pin);
+}
+
+
+static void wps_free_pins(struct dl_list *pins)
+{
+ struct wps_uuid_pin *pin, *prev;
+ dl_list_for_each_safe(pin, prev, pins, struct wps_uuid_pin, list)
+ wps_remove_pin(pin);
+}
+
+
+struct wps_pbc_session {
+ struct wps_pbc_session *next;
+ u8 addr[ETH_ALEN];
+ u8 uuid_e[WPS_UUID_LEN];
+ struct os_time timestamp;
+};
+
+
+static void wps_free_pbc_sessions(struct wps_pbc_session *pbc)
+{
+ struct wps_pbc_session *prev;
+
+ while (pbc) {
+ prev = pbc;
+ pbc = pbc->next;
+ os_free(prev);
+ }
+}
+
+
+struct wps_registrar_device {
+ struct wps_registrar_device *next;
+ struct wps_device_data dev;
+ u8 uuid[WPS_UUID_LEN];
+};
+
+
+struct wps_registrar {
+ struct wps_context *wps;
+
+ int pbc;
+ int selected_registrar;
+
+ int (*new_psk_cb)(void *ctx, const u8 *mac_addr, const u8 *psk,
+ size_t psk_len);
+ int (*set_ie_cb)(void *ctx, struct wpabuf *beacon_ie,
+ struct wpabuf *probe_resp_ie);
+ void (*pin_needed_cb)(void *ctx, const u8 *uuid_e,
+ const struct wps_device_data *dev);
+ void (*reg_success_cb)(void *ctx, const u8 *mac_addr,
+ const u8 *uuid_e);
+ void (*set_sel_reg_cb)(void *ctx, int sel_reg, u16 dev_passwd_id,
+ u16 sel_reg_config_methods);
+ void (*enrollee_seen_cb)(void *ctx, const u8 *addr, const u8 *uuid_e,
+ const u8 *pri_dev_type, u16 config_methods,
+ u16 dev_password_id, u8 request_type,
+ const char *dev_name);
+ void *cb_ctx;
+
+ struct dl_list pins;
+ struct wps_pbc_session *pbc_sessions;
+
+ int skip_cred_build;
+ struct wpabuf *extra_cred;
+ int disable_auto_conf;
+ int sel_reg_union;
+ int sel_reg_dev_password_id_override;
+ int sel_reg_config_methods_override;
+ int static_wep_only;
+ int dualband;
+
+ struct wps_registrar_device *devices;
+
+ int force_pbc_overlap;
+
+ u8 authorized_macs[WPS_MAX_AUTHORIZED_MACS][ETH_ALEN];
+ u8 authorized_macs_union[WPS_MAX_AUTHORIZED_MACS][ETH_ALEN];
+
+ u8 p2p_dev_addr[ETH_ALEN];
+};
+
+
+static int wps_set_ie(struct wps_registrar *reg);
+static void wps_registrar_pbc_timeout(void *eloop_ctx, void *timeout_ctx);
+static void wps_registrar_set_selected_timeout(void *eloop_ctx,
+ void *timeout_ctx);
+
+
+static void wps_registrar_add_authorized_mac(struct wps_registrar *reg,
+ const u8 *addr)
+{
+ int i;
+ wpa_printf(MSG_DEBUG, "WPS: Add authorized MAC " MACSTR,
+ MAC2STR(addr));
+ for (i = 0; i < WPS_MAX_AUTHORIZED_MACS; i++)
+ if (os_memcmp(reg->authorized_macs[i], addr, ETH_ALEN) == 0) {
+ wpa_printf(MSG_DEBUG, "WPS: Authorized MAC was "
+ "already in the list");
+ return; /* already in list */
+ }
+ for (i = WPS_MAX_AUTHORIZED_MACS - 1; i > 0; i--)
+ os_memcpy(reg->authorized_macs[i], reg->authorized_macs[i - 1],
+ ETH_ALEN);
+ os_memcpy(reg->authorized_macs[0], addr, ETH_ALEN);
+ wpa_hexdump(MSG_DEBUG, "WPS: Authorized MACs",
+ (u8 *) reg->authorized_macs, sizeof(reg->authorized_macs));
+}
+
+
+static void wps_registrar_remove_authorized_mac(struct wps_registrar *reg,
+ const u8 *addr)
+{
+ int i;
+ wpa_printf(MSG_DEBUG, "WPS: Remove authorized MAC " MACSTR,
+ MAC2STR(addr));
+ for (i = 0; i < WPS_MAX_AUTHORIZED_MACS; i++) {
+ if (os_memcmp(reg->authorized_macs, addr, ETH_ALEN) == 0)
+ break;
+ }
+ if (i == WPS_MAX_AUTHORIZED_MACS) {
+ wpa_printf(MSG_DEBUG, "WPS: Authorized MAC was not in the "
+ "list");
+ return; /* not in the list */
+ }
+ for (; i + 1 < WPS_MAX_AUTHORIZED_MACS; i++)
+ os_memcpy(reg->authorized_macs[i], reg->authorized_macs[i + 1],
+ ETH_ALEN);
+ os_memset(reg->authorized_macs[WPS_MAX_AUTHORIZED_MACS - 1], 0,
+ ETH_ALEN);
+ wpa_hexdump(MSG_DEBUG, "WPS: Authorized MACs",
+ (u8 *) reg->authorized_macs, sizeof(reg->authorized_macs));
+}
+
+
+static void wps_free_devices(struct wps_registrar_device *dev)
+{
+ struct wps_registrar_device *prev;
+
+ while (dev) {
+ prev = dev;
+ dev = dev->next;
+ wps_device_data_free(&prev->dev);
+ os_free(prev);
+ }
+}
+
+
+static struct wps_registrar_device * wps_device_get(struct wps_registrar *reg,
+ const u8 *addr)
+{
+ struct wps_registrar_device *dev;
+
+ for (dev = reg->devices; dev; dev = dev->next) {
+ if (os_memcmp(dev->dev.mac_addr, addr, ETH_ALEN) == 0)
+ return dev;
+ }
+ return NULL;
+}
+
+
+static void wps_device_clone_data(struct wps_device_data *dst,
+ struct wps_device_data *src)
+{
+ os_memcpy(dst->mac_addr, src->mac_addr, ETH_ALEN);
+ os_memcpy(dst->pri_dev_type, src->pri_dev_type, WPS_DEV_TYPE_LEN);
+
+#define WPS_STRDUP(n) \
+ os_free(dst->n); \
+ dst->n = src->n ? os_strdup(src->n) : NULL
+
+ WPS_STRDUP(device_name);
+ WPS_STRDUP(manufacturer);
+ WPS_STRDUP(model_name);
+ WPS_STRDUP(model_number);
+ WPS_STRDUP(serial_number);
+#undef WPS_STRDUP
+}
+
+
+int wps_device_store(struct wps_registrar *reg,
+ struct wps_device_data *dev, const u8 *uuid)
+{
+ struct wps_registrar_device *d;
+
+ d = wps_device_get(reg, dev->mac_addr);
+ if (d == NULL) {
+ d = os_zalloc(sizeof(*d));
+ if (d == NULL)
+ return -1;
+ d->next = reg->devices;
+ reg->devices = d;
+ }
+
+ wps_device_clone_data(&d->dev, dev);
+ os_memcpy(d->uuid, uuid, WPS_UUID_LEN);
+
+ return 0;
+}
+
+
+static void wps_registrar_add_pbc_session(struct wps_registrar *reg,
+ const u8 *addr, const u8 *uuid_e)
+{
+ struct wps_pbc_session *pbc, *prev = NULL;
+ struct os_time now;
+
+ os_get_time(&now);
+
+ pbc = reg->pbc_sessions;
+ while (pbc) {
+ if (os_memcmp(pbc->addr, addr, ETH_ALEN) == 0 &&
+ os_memcmp(pbc->uuid_e, uuid_e, WPS_UUID_LEN) == 0) {
+ if (prev)
+ prev->next = pbc->next;
+ else
+ reg->pbc_sessions = pbc->next;
+ break;
+ }
+ prev = pbc;
+ pbc = pbc->next;
+ }
+
+ if (!pbc) {
+ pbc = os_zalloc(sizeof(*pbc));
+ if (pbc == NULL)
+ return;
+ os_memcpy(pbc->addr, addr, ETH_ALEN);
+ if (uuid_e)
+ os_memcpy(pbc->uuid_e, uuid_e, WPS_UUID_LEN);
+ }
+
+ pbc->next = reg->pbc_sessions;
+ reg->pbc_sessions = pbc;
+ pbc->timestamp = now;
+
+ /* remove entries that have timed out */
+ prev = pbc;
+ pbc = pbc->next;
+
+ while (pbc) {
+ if (now.sec > pbc->timestamp.sec + WPS_PBC_WALK_TIME) {
+ prev->next = NULL;
+ wps_free_pbc_sessions(pbc);
+ break;
+ }
+ prev = pbc;
+ pbc = pbc->next;
+ }
+}
+
+
+static void wps_registrar_remove_pbc_session(struct wps_registrar *reg,
+ const u8 *uuid_e)
+{
+ struct wps_pbc_session *pbc, *prev = NULL, *tmp;
+
+ pbc = reg->pbc_sessions;
+ while (pbc) {
+ if (os_memcmp(pbc->uuid_e, uuid_e, WPS_UUID_LEN) == 0) {
+ if (prev)
+ prev->next = pbc->next;
+ else
+ reg->pbc_sessions = pbc->next;
+ tmp = pbc;
+ pbc = pbc->next;
+ wpa_printf(MSG_DEBUG, "WPS: Removing PBC session for "
+ "addr=" MACSTR, MAC2STR(tmp->addr));
+ wpa_hexdump(MSG_DEBUG, "WPS: Removed UUID-E",
+ tmp->uuid_e, WPS_UUID_LEN);
+ os_free(tmp);
+ continue;
+ }
+ prev = pbc;
+ pbc = pbc->next;
+ }
+}
+
+
+int wps_registrar_pbc_overlap(struct wps_registrar *reg,
+ const u8 *addr, const u8 *uuid_e)
+{
+ int count = 0;
+ struct wps_pbc_session *pbc;
+ struct wps_pbc_session *first = NULL;
+ struct os_time now;
+
+ os_get_time(&now);
+
+ wpa_printf(MSG_DEBUG, "WPS: Checking active PBC sessions for overlap");
+
+ if (uuid_e) {
+ wpa_printf(MSG_DEBUG, "WPS: Add one for the requested UUID");
+ wpa_hexdump(MSG_DEBUG, "WPS: Requested UUID",
+ uuid_e, WPS_UUID_LEN);
+ count++;
+ }
+
+ for (pbc = reg->pbc_sessions; pbc; pbc = pbc->next) {
+ wpa_printf(MSG_DEBUG, "WPS: Consider PBC session with " MACSTR,
+ MAC2STR(pbc->addr));
+ wpa_hexdump(MSG_DEBUG, "WPS: UUID-E",
+ pbc->uuid_e, WPS_UUID_LEN);
+ if (now.sec > pbc->timestamp.sec + WPS_PBC_WALK_TIME) {
+ wpa_printf(MSG_DEBUG, "WPS: PBC walk time has "
+ "expired");
+ break;
+ }
+ if (first &&
+ os_memcmp(pbc->uuid_e, first->uuid_e, WPS_UUID_LEN) == 0) {
+ wpa_printf(MSG_DEBUG, "WPS: Same Enrollee");
+ continue; /* same Enrollee */
+ }
+ if (uuid_e == NULL ||
+ os_memcmp(uuid_e, pbc->uuid_e, WPS_UUID_LEN)) {
+ wpa_printf(MSG_DEBUG, "WPS: New Enrollee");
+ count++;
+ }
+ if (first == NULL)
+ first = pbc;
+ }
+
+ wpa_printf(MSG_DEBUG, "WPS: %u active PBC session(s) found", count);
+
+ return count > 1 ? 1 : 0;
+}
+
+
+static int wps_build_wps_state(struct wps_context *wps, struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * Wi-Fi Protected Setup State (%d)",
+ wps->wps_state);
+ wpabuf_put_be16(msg, ATTR_WPS_STATE);
+ wpabuf_put_be16(msg, 1);
+ wpabuf_put_u8(msg, wps->wps_state);
+ return 0;
+}
+
+
+#ifdef CONFIG_WPS_UPNP
+static void wps_registrar_free_pending_m2(struct wps_context *wps)
+{
+ struct upnp_pending_message *p, *p2, *prev = NULL;
+ p = wps->upnp_msgs;
+ while (p) {
+ if (p->type == WPS_M2 || p->type == WPS_M2D) {
+ if (prev == NULL)
+ wps->upnp_msgs = p->next;
+ else
+ prev->next = p->next;
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Drop pending M2/M2D");
+ p2 = p;
+ p = p->next;
+ wpabuf_free(p2->msg);
+ os_free(p2);
+ continue;
+ }
+ prev = p;
+ p = p->next;
+ }
+}
+#endif /* CONFIG_WPS_UPNP */
+
+
+static int wps_build_ap_setup_locked(struct wps_context *wps,
+ struct wpabuf *msg)
+{
+ if (wps->ap_setup_locked && wps->ap_setup_locked != 2) {
+ wpa_printf(MSG_DEBUG, "WPS: * AP Setup Locked");
+ wpabuf_put_be16(msg, ATTR_AP_SETUP_LOCKED);
+ wpabuf_put_be16(msg, 1);
+ wpabuf_put_u8(msg, 1);
+ }
+ return 0;
+}
+
+
+static int wps_build_selected_registrar(struct wps_registrar *reg,
+ struct wpabuf *msg)
+{
+ if (!reg->sel_reg_union)
+ return 0;
+ wpa_printf(MSG_DEBUG, "WPS: * Selected Registrar");
+ wpabuf_put_be16(msg, ATTR_SELECTED_REGISTRAR);
+ wpabuf_put_be16(msg, 1);
+ wpabuf_put_u8(msg, 1);
+ return 0;
+}
+
+
+static int wps_build_sel_reg_dev_password_id(struct wps_registrar *reg,
+ struct wpabuf *msg)
+{
+ u16 id = reg->pbc ? DEV_PW_PUSHBUTTON : DEV_PW_DEFAULT;
+ if (!reg->sel_reg_union)
+ return 0;
+ if (reg->sel_reg_dev_password_id_override >= 0)
+ id = reg->sel_reg_dev_password_id_override;
+ wpa_printf(MSG_DEBUG, "WPS: * Device Password ID (%d)", id);
+ wpabuf_put_be16(msg, ATTR_DEV_PASSWORD_ID);
+ wpabuf_put_be16(msg, 2);
+ wpabuf_put_be16(msg, id);
+ return 0;
+}
+
+
+static int wps_build_sel_pbc_reg_uuid_e(struct wps_registrar *reg,
+ struct wpabuf *msg)
+{
+ u16 id = reg->pbc ? DEV_PW_PUSHBUTTON : DEV_PW_DEFAULT;
+ if (!reg->sel_reg_union)
+ return 0;
+ if (reg->sel_reg_dev_password_id_override >= 0)
+ id = reg->sel_reg_dev_password_id_override;
+ if (id != DEV_PW_PUSHBUTTON || !reg->dualband)
+ return 0;
+ return wps_build_uuid_e(msg, reg->wps->uuid);
+}
+
+
+static void wps_set_pushbutton(u16 *methods, u16 conf_methods)
+{
+ *methods |= WPS_CONFIG_PUSHBUTTON;
+#ifdef CONFIG_WPS2
+ if (conf_methods & WPS_CONFIG_VIRT_PUSHBUTTON)
+ *methods |= WPS_CONFIG_VIRT_PUSHBUTTON;
+ if (conf_methods & WPS_CONFIG_PHY_PUSHBUTTON)
+ *methods |= WPS_CONFIG_PHY_PUSHBUTTON;
+ if ((*methods & WPS_CONFIG_VIRT_PUSHBUTTON) !=
+ WPS_CONFIG_VIRT_PUSHBUTTON ||
+ (*methods & WPS_CONFIG_PHY_PUSHBUTTON) !=
+ WPS_CONFIG_PHY_PUSHBUTTON) {
+ /*
+ * Required to include virtual/physical flag, but we were not
+ * configured with push button type, so have to default to one
+ * of them.
+ */
+ *methods |= WPS_CONFIG_PHY_PUSHBUTTON;
+ }
+#endif /* CONFIG_WPS2 */
+}
+
+
+static int wps_build_sel_reg_config_methods(struct wps_registrar *reg,
+ struct wpabuf *msg)
+{
+ u16 methods;
+ if (!reg->sel_reg_union)
+ return 0;
+ methods = reg->wps->config_methods;
+ methods &= ~WPS_CONFIG_PUSHBUTTON;
+#ifdef CONFIG_WPS2
+ methods &= ~(WPS_CONFIG_VIRT_PUSHBUTTON |
+ WPS_CONFIG_PHY_PUSHBUTTON);
+#endif /* CONFIG_WPS2 */
+ if (reg->pbc)
+ wps_set_pushbutton(&methods, reg->wps->config_methods);
+ if (reg->sel_reg_config_methods_override >= 0)
+ methods = reg->sel_reg_config_methods_override;
+ wpa_printf(MSG_DEBUG, "WPS: * Selected Registrar Config Methods (%x)",
+ methods);
+ wpabuf_put_be16(msg, ATTR_SELECTED_REGISTRAR_CONFIG_METHODS);
+ wpabuf_put_be16(msg, 2);
+ wpabuf_put_be16(msg, methods);
+ return 0;
+}
+
+
+static int wps_build_probe_config_methods(struct wps_registrar *reg,
+ struct wpabuf *msg)
+{
+ u16 methods;
+ /*
+ * These are the methods that the AP supports as an Enrollee for adding
+ * external Registrars.
+ */
+ methods = reg->wps->config_methods & ~WPS_CONFIG_PUSHBUTTON;
+#ifdef CONFIG_WPS2
+ methods &= ~(WPS_CONFIG_VIRT_PUSHBUTTON |
+ WPS_CONFIG_PHY_PUSHBUTTON);
+#endif /* CONFIG_WPS2 */
+ wpa_printf(MSG_DEBUG, "WPS: * Config Methods (%x)", methods);
+ wpabuf_put_be16(msg, ATTR_CONFIG_METHODS);
+ wpabuf_put_be16(msg, 2);
+ wpabuf_put_be16(msg, methods);
+ return 0;
+}
+
+
+static int wps_build_config_methods_r(struct wps_registrar *reg,
+ struct wpabuf *msg)
+{
+ u16 methods;
+ methods = reg->wps->config_methods & ~WPS_CONFIG_PUSHBUTTON;
+#ifdef CONFIG_WPS2
+ methods &= ~(WPS_CONFIG_VIRT_PUSHBUTTON |
+ WPS_CONFIG_PHY_PUSHBUTTON);
+#endif /* CONFIG_WPS2 */
+ if (reg->pbc)
+ wps_set_pushbutton(&methods, reg->wps->config_methods);
+ return wps_build_config_methods(msg, methods);
+}
+
+
+const u8 * wps_authorized_macs(struct wps_registrar *reg, size_t *count)
+{
+ *count = 0;
+
+#ifdef CONFIG_WPS2
+ while (*count < WPS_MAX_AUTHORIZED_MACS) {
+ if (is_zero_ether_addr(reg->authorized_macs_union[*count]))
+ break;
+ (*count)++;
+ }
+#endif /* CONFIG_WPS2 */
+
+ return (const u8 *) reg->authorized_macs_union;
+}
+
+
+/**
+ * wps_registrar_init - Initialize WPS Registrar data
+ * @wps: Pointer to longterm WPS context
+ * @cfg: Registrar configuration
+ * Returns: Pointer to allocated Registrar data or %NULL on failure
+ *
+ * This function is used to initialize WPS Registrar functionality. It can be
+ * used for a single Registrar run (e.g., when run in a supplicant) or multiple
+ * runs (e.g., when run as an internal Registrar in an AP). Caller is
+ * responsible for freeing the returned data with wps_registrar_deinit() when
+ * Registrar functionality is not needed anymore.
+ */
+struct wps_registrar *
+wps_registrar_init(struct wps_context *wps,
+ const struct wps_registrar_config *cfg)
+{
+ struct wps_registrar *reg = os_zalloc(sizeof(*reg));
+ if (reg == NULL)
+ return NULL;
+
+ dl_list_init(®->pins);
+ reg->wps = wps;
+ reg->new_psk_cb = cfg->new_psk_cb;
+ reg->set_ie_cb = cfg->set_ie_cb;
+ reg->pin_needed_cb = cfg->pin_needed_cb;
+ reg->reg_success_cb = cfg->reg_success_cb;
+ reg->set_sel_reg_cb = cfg->set_sel_reg_cb;
+ reg->enrollee_seen_cb = cfg->enrollee_seen_cb;
+ reg->cb_ctx = cfg->cb_ctx;
+ reg->skip_cred_build = cfg->skip_cred_build;
+ if (cfg->extra_cred) {
+ reg->extra_cred = wpabuf_alloc_copy(cfg->extra_cred,
+ cfg->extra_cred_len);
+ if (reg->extra_cred == NULL) {
+ os_free(reg);
+ return NULL;
+ }
+ }
+ 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;
+ reg->dualband = cfg->dualband;
+
+ if (wps_set_ie(reg)) {
+ wps_registrar_deinit(reg);
+ return NULL;
+ }
+
+ return reg;
+}
+
+
+/**
+ * wps_registrar_deinit - Deinitialize WPS Registrar data
+ * @reg: Registrar data from wps_registrar_init()
+ */
+void wps_registrar_deinit(struct wps_registrar *reg)
+{
+ if (reg == NULL)
+ return;
+ eloop_cancel_timeout(wps_registrar_pbc_timeout, reg, NULL);
+ eloop_cancel_timeout(wps_registrar_set_selected_timeout, reg, NULL);
+ wps_free_pins(®->pins);
+ wps_free_pbc_sessions(reg->pbc_sessions);
+ wpabuf_free(reg->extra_cred);
+ wps_free_devices(reg->devices);
+ os_free(reg);
+}
+
+
+/**
+ * wps_registrar_add_pin - Configure a new PIN for Registrar
+ * @reg: Registrar data from wps_registrar_init()
+ * @addr: Enrollee MAC address or %NULL if not known
+ * @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 *addr,
+ const u8 *uuid, const u8 *pin, size_t pin_len,
+ int timeout)
+{
+ struct wps_uuid_pin *p;
+
+ p = os_zalloc(sizeof(*p));
+ if (p == NULL)
+ return -1;
+ if (addr)
+ os_memcpy(p->enrollee_addr, addr, ETH_ALEN);
+ if (uuid == NULL)
+ p->wildcard_uuid = 1;
+ else
+ os_memcpy(p->uuid, uuid, WPS_UUID_LEN);
+ p->pin = os_malloc(pin_len);
+ if (p->pin == NULL) {
+ os_free(p);
+ return -1;
+ }
+ 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;
+ }
+
+ dl_list_add(®->pins, &p->list);
+
+ 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;
+ if (addr)
+ wps_registrar_add_authorized_mac(reg, addr);
+ else
+ wps_registrar_add_authorized_mac(
+ reg, (u8 *) "\xff\xff\xff\xff\xff\xff");
+ wps_registrar_selected_registrar_changed(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_remove_pin(struct wps_registrar *reg,
+ struct wps_uuid_pin *pin)
+{
+ u8 *addr;
+ u8 bcast[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+
+ if (is_zero_ether_addr(pin->enrollee_addr))
+ addr = bcast;
+ else
+ addr = pin->enrollee_addr;
+ wps_registrar_remove_authorized_mac(reg, addr);
+ wps_remove_pin(pin);
+ wps_registrar_selected_registrar_changed(reg);
+}
+
+
+static void wps_registrar_expire_pins(struct wps_registrar *reg)
+{
+ struct wps_uuid_pin *pin, *prev;
+ struct os_time now;
+
+ os_get_time(&now);
+ dl_list_for_each_safe(pin, prev, ®->pins, struct wps_uuid_pin, list)
+ {
+ if ((pin->flags & PIN_EXPIRES) &&
+ os_time_before(&pin->expiration, &now)) {
+ wpa_hexdump(MSG_DEBUG, "WPS: Expired PIN for UUID",
+ pin->uuid, WPS_UUID_LEN);
+ wps_registrar_remove_pin(reg, pin);
+ }
+ }
+}
+
+
+/**
+ * wps_registrar_invalidate_wildcard_pin - Invalidate a wildcard PIN
+ * @reg: Registrar data from wps_registrar_init()
+ * Returns: 0 on success, -1 if not wildcard PIN is enabled
+ */
+static int wps_registrar_invalidate_wildcard_pin(struct wps_registrar *reg)
+{
+ struct wps_uuid_pin *pin, *prev;
+
+ dl_list_for_each_safe(pin, prev, ®->pins, struct wps_uuid_pin, list)
+ {
+ if (pin->wildcard_uuid) {
+ wpa_hexdump(MSG_DEBUG, "WPS: Invalidated PIN for UUID",
+ pin->uuid, WPS_UUID_LEN);
+ wps_registrar_remove_pin(reg, pin);
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+
+/**
+ * wps_registrar_invalidate_pin - Invalidate a PIN for a specific UUID-E
+ * @reg: Registrar data from wps_registrar_init()
+ * @uuid: UUID-E
+ * Returns: 0 on success, -1 on failure (e.g., PIN not found)
+ */
+int wps_registrar_invalidate_pin(struct wps_registrar *reg, const u8 *uuid)
+{
+ struct wps_uuid_pin *pin, *prev;
+
+ dl_list_for_each_safe(pin, prev, ®->pins, struct wps_uuid_pin, list)
+ {
+ if (os_memcmp(pin->uuid, uuid, WPS_UUID_LEN) == 0) {
+ wpa_hexdump(MSG_DEBUG, "WPS: Invalidated PIN for UUID",
+ pin->uuid, WPS_UUID_LEN);
+ wps_registrar_remove_pin(reg, pin);
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+
+static const u8 * wps_registrar_get_pin(struct wps_registrar *reg,
+ const u8 *uuid, size_t *pin_len)
+{
+ struct wps_uuid_pin *pin, *found = NULL;
+
+ wps_registrar_expire_pins(reg);
+
+ dl_list_for_each(pin, ®->pins, struct wps_uuid_pin, list) {
+ if (!pin->wildcard_uuid &&
+ os_memcmp(pin->uuid, uuid, WPS_UUID_LEN) == 0) {
+ found = pin;
+ break;
+ }
+ }
+
+ if (!found) {
+ /* Check for wildcard UUIDs since none of the UUID-specific
+ * PINs matched */
+ dl_list_for_each(pin, ®->pins, struct wps_uuid_pin, list) {
+ if (pin->wildcard_uuid == 1) {
+ wpa_printf(MSG_DEBUG, "WPS: Found a wildcard "
+ "PIN. Assigned it for this UUID-E");
+ pin->wildcard_uuid = 2;
+ os_memcpy(pin->uuid, uuid, WPS_UUID_LEN);
+ found = pin;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ return NULL;
+
+ /*
+ * Lock the PIN to avoid attacks based on concurrent re-use of the PIN
+ * that could otherwise avoid PIN invalidations.
+ */
+ if (found->flags & PIN_LOCKED) {
+ wpa_printf(MSG_DEBUG, "WPS: Selected PIN locked - do not "
+ "allow concurrent re-use");
+ return NULL;
+ }
+ *pin_len = found->pin_len;
+ found->flags |= PIN_LOCKED;
+ return found->pin;
+}
+
+
+/**
+ * wps_registrar_unlock_pin - Unlock a PIN for a specific UUID-E
+ * @reg: Registrar data from wps_registrar_init()
+ * @uuid: UUID-E
+ * Returns: 0 on success, -1 on failure
+ *
+ * PINs are locked to enforce only one concurrent use. This function unlocks a
+ * PIN to allow it to be used again. If the specified PIN was configured using
+ * a wildcard UUID, it will be removed instead of allowing multiple uses.
+ */
+int wps_registrar_unlock_pin(struct wps_registrar *reg, const u8 *uuid)
+{
+ struct wps_uuid_pin *pin;
+
+ dl_list_for_each(pin, ®->pins, struct wps_uuid_pin, list) {
+ if (os_memcmp(pin->uuid, uuid, WPS_UUID_LEN) == 0) {
+ if (pin->wildcard_uuid == 2) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalidating used "
+ "wildcard PIN");
+ return wps_registrar_invalidate_pin(reg, uuid);
+ }
+ pin->flags &= ~PIN_LOCKED;
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+
+static void wps_registrar_stop_pbc(struct wps_registrar *reg)
+{
+ reg->selected_registrar = 0;
+ reg->pbc = 0;
+ os_memset(reg->p2p_dev_addr, 0, ETH_ALEN);
+ wps_registrar_remove_authorized_mac(reg,
+ (u8 *) "\xff\xff\xff\xff\xff\xff");
+ wps_registrar_selected_registrar_changed(reg);
+}
+
+
+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);
+}
+
+
+/**
+ * wps_registrar_button_pushed - Notify Registrar that AP button was pushed
+ * @reg: Registrar data from wps_registrar_init()
+ * @p2p_dev_addr: Limit allowed PBC devices to the specified P2P device, %NULL
+ * indicates no such filtering
+ * Returns: 0 on success, -1 on failure, -2 on session overlap
+ *
+ * This function is called on an AP when a push button is pushed to activate
+ * PBC mode. The PBC mode will be stopped after walk time (2 minutes) timeout
+ * or when a PBC registration is completed. If more than one Enrollee in active
+ * PBC mode has been detected during the monitor time (previous 2 minutes), the
+ * PBC mode is not actived and -2 is returned to indicate session overlap. This
+ * is skipped if a specific Enrollee is selected.
+ */
+int wps_registrar_button_pushed(struct wps_registrar *reg,
+ const u8 *p2p_dev_addr)
+{
+ if (p2p_dev_addr == NULL &&
+ 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 -2;
+ }
+ wpa_printf(MSG_DEBUG, "WPS: Button pushed - PBC mode started");
+ reg->force_pbc_overlap = 0;
+ reg->selected_registrar = 1;
+ reg->pbc = 1;
+ if (p2p_dev_addr)
+ os_memcpy(reg->p2p_dev_addr, p2p_dev_addr, ETH_ALEN);
+ else
+ os_memset(reg->p2p_dev_addr, 0, ETH_ALEN);
+ wps_registrar_add_authorized_mac(reg,
+ (u8 *) "\xff\xff\xff\xff\xff\xff");
+ wps_registrar_selected_registrar_changed(reg);
+
+ eloop_cancel_timeout(wps_registrar_set_selected_timeout, reg, NULL);
+ eloop_cancel_timeout(wps_registrar_pbc_timeout, reg, NULL);
+ eloop_register_timeout(WPS_PBC_WALK_TIME, 0, wps_registrar_pbc_timeout,
+ reg, NULL);
+ return 0;
+}
+
+
+static void wps_registrar_pbc_completed(struct wps_registrar *reg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: PBC completed - stopping PBC mode");
+ eloop_cancel_timeout(wps_registrar_pbc_timeout, reg, NULL);
+ 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_registrar_selected_registrar_changed(reg);
+}
+
+
+int wps_registrar_wps_cancel(struct wps_registrar *reg)
+{
+ if (reg->pbc) {
+ wpa_printf(MSG_DEBUG, "WPS: PBC is set - cancelling it");
+ wps_registrar_pbc_timeout(reg, NULL);
+ return 1;
+ } else if (reg->selected_registrar) {
+ /* PIN Method */
+ wpa_printf(MSG_DEBUG, "WPS: PIN is set - cancelling it");
+ wps_registrar_pin_completed(reg);
+ wps_registrar_invalidate_wildcard_pin(reg);
+ return 1;
+ }
+ return 0;
+}
+
+
+/**
+ * wps_registrar_probe_req_rx - Notify Registrar of Probe Request
+ * @reg: Registrar data from wps_registrar_init()
+ * @addr: MAC address of the Probe Request sender
+ * @wps_data: WPS IE contents
+ *
+ * This function is called on an AP when a Probe Request with WPS IE is
+ * received. This is used to track PBC mode use and to detect possible overlap
+ * situation with other WPS APs.
+ */
+void wps_registrar_probe_req_rx(struct wps_registrar *reg, const u8 *addr,
+ const struct wpabuf *wps_data,
+ int p2p_wildcard)
+{
+ struct wps_parse_attr attr;
+
+ wpa_hexdump_buf(MSG_MSGDUMP,
+ "WPS: Probe Request with WPS data received",
+ wps_data);
+
+ if (wps_parse_msg(wps_data, &attr) < 0)
+ return;
+
+ if (attr.config_methods == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Config Methods attribute in "
+ "Probe Request");
+ return;
+ }
+
+ if (attr.dev_password_id == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Device Password Id attribute "
+ "in Probe Request");
+ return;
+ }
+
+ if (reg->enrollee_seen_cb && attr.uuid_e &&
+ attr.primary_dev_type && attr.request_type && !p2p_wildcard) {
+ char *dev_name = NULL;
+ if (attr.dev_name) {
+ dev_name = os_zalloc(attr.dev_name_len + 1);
+ if (dev_name) {
+ os_memcpy(dev_name, attr.dev_name,
+ attr.dev_name_len);
+ }
+ }
+ reg->enrollee_seen_cb(reg->cb_ctx, addr, attr.uuid_e,
+ attr.primary_dev_type,
+ WPA_GET_BE16(attr.config_methods),
+ WPA_GET_BE16(attr.dev_password_id),
+ *attr.request_type, dev_name);
+ os_free(dev_name);
+ }
+
+ if (WPA_GET_BE16(attr.dev_password_id) != DEV_PW_PUSHBUTTON)
+ return; /* Not PBC */
+
+ 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;
+ }
+ wpa_hexdump(MSG_DEBUG, "WPS: UUID-E from Probe Request", attr.uuid_e,
+ WPS_UUID_LEN);
+
+ 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);
+ }
+}
+
+
+static int wps_cb_new_psk(struct wps_registrar *reg, const u8 *mac_addr,
+ const u8 *psk, size_t psk_len)
+{
+ if (reg->new_psk_cb == NULL)
+ return 0;
+
+ return reg->new_psk_cb(reg->cb_ctx, mac_addr, psk, psk_len);
+}
+
+
+static void wps_cb_pin_needed(struct wps_registrar *reg, const u8 *uuid_e,
+ const struct wps_device_data *dev)
+{
+ if (reg->pin_needed_cb == NULL)
+ return;
+
+ reg->pin_needed_cb(reg->cb_ctx, uuid_e, dev);
+}
+
+
+static void wps_cb_reg_success(struct wps_registrar *reg, const u8 *mac_addr,
+ const u8 *uuid_e)
+{
+ if (reg->reg_success_cb == NULL)
+ return;
+
+ reg->reg_success_cb(reg->cb_ctx, mac_addr, uuid_e);
+}
+
+
+static int wps_cb_set_ie(struct wps_registrar *reg, struct wpabuf *beacon_ie,
+ struct wpabuf *probe_resp_ie)
+{
+ return reg->set_ie_cb(reg->cb_ctx, beacon_ie, probe_resp_ie);
+}
+
+
+static void wps_cb_set_sel_reg(struct wps_registrar *reg)
+{
+ u16 methods = 0;
+ if (reg->set_sel_reg_cb == NULL)
+ return;
+
+ if (reg->selected_registrar) {
+ methods = reg->wps->config_methods & ~WPS_CONFIG_PUSHBUTTON;
+#ifdef CONFIG_WPS2
+ methods &= ~(WPS_CONFIG_VIRT_PUSHBUTTON |
+ WPS_CONFIG_PHY_PUSHBUTTON);
+#endif /* CONFIG_WPS2 */
+ if (reg->pbc)
+ wps_set_pushbutton(&methods, reg->wps->config_methods);
+ }
+
+ wpa_printf(MSG_DEBUG, "WPS: wps_cb_set_sel_reg: sel_reg=%d "
+ "config_methods=0x%x pbc=%d methods=0x%x",
+ reg->selected_registrar, reg->wps->config_methods,
+ reg->pbc, methods);
+
+ reg->set_sel_reg_cb(reg->cb_ctx, reg->selected_registrar,
+ reg->pbc ? DEV_PW_PUSHBUTTON : DEV_PW_DEFAULT,
+ methods);
+}
+
+
+static int wps_set_ie(struct wps_registrar *reg)
+{
+ struct wpabuf *beacon;
+ struct wpabuf *probe;
+ const u8 *auth_macs;
+ size_t count;
+ size_t vendor_len = 0;
+ int i;
+
+ if (reg->set_ie_cb == NULL)
+ return 0;
+
+ for (i = 0; i < MAX_WPS_VENDOR_EXTENSIONS; i++) {
+ if (reg->wps->dev.vendor_ext[i]) {
+ vendor_len += 2 + 2;
+ vendor_len += wpabuf_len(reg->wps->dev.vendor_ext[i]);
+ }
+ }
+
+ beacon = wpabuf_alloc(400 + vendor_len);
+ if (beacon == NULL)
+ return -1;
+ probe = wpabuf_alloc(500 + vendor_len);
+ if (probe == NULL) {
+ wpabuf_free(beacon);
+ return -1;
+ }
+
+ auth_macs = wps_authorized_macs(reg, &count);
+
+ wpa_printf(MSG_DEBUG, "WPS: Build Beacon IEs");
+
+ if (wps_build_version(beacon) ||
+ wps_build_wps_state(reg->wps, beacon) ||
+ wps_build_ap_setup_locked(reg->wps, beacon) ||
+ wps_build_selected_registrar(reg, beacon) ||
+ wps_build_sel_reg_dev_password_id(reg, beacon) ||
+ wps_build_sel_reg_config_methods(reg, beacon) ||
+ wps_build_sel_pbc_reg_uuid_e(reg, beacon) ||
+ (reg->dualband && wps_build_rf_bands(®->wps->dev, beacon)) ||
+ wps_build_wfa_ext(beacon, 0, auth_macs, count) ||
+ wps_build_vendor_ext(®->wps->dev, beacon)) {
+ wpabuf_free(beacon);
+ wpabuf_free(probe);
+ return -1;
+ }
+
+#ifdef CONFIG_P2P
+ if (wps_build_dev_name(®->wps->dev, beacon) ||
+ wps_build_primary_dev_type(®->wps->dev, beacon)) {
+ wpabuf_free(beacon);
+ wpabuf_free(probe);
+ return -1;
+ }
+#endif /* CONFIG_P2P */
+
+ wpa_printf(MSG_DEBUG, "WPS: Build Probe Response IEs");
+
+ if (wps_build_version(probe) ||
+ wps_build_wps_state(reg->wps, probe) ||
+ wps_build_ap_setup_locked(reg->wps, probe) ||
+ wps_build_selected_registrar(reg, probe) ||
+ wps_build_sel_reg_dev_password_id(reg, probe) ||
+ wps_build_sel_reg_config_methods(reg, probe) ||
+ wps_build_resp_type(probe, reg->wps->ap ? WPS_RESP_AP :
+ WPS_RESP_REGISTRAR) ||
+ wps_build_uuid_e(probe, reg->wps->uuid) ||
+ wps_build_device_attrs(®->wps->dev, probe) ||
+ wps_build_probe_config_methods(reg, probe) ||
+ wps_build_rf_bands(®->wps->dev, probe) ||
+ wps_build_wfa_ext(probe, 0, auth_macs, count) ||
+ wps_build_vendor_ext(®->wps->dev, probe)) {
+ wpabuf_free(beacon);
+ wpabuf_free(probe);
+ return -1;
+ }
+
+ beacon = wps_ie_encapsulate(beacon);
+ probe = wps_ie_encapsulate(probe);
+
+ if (!beacon || !probe) {
+ wpabuf_free(beacon);
+ wpabuf_free(probe);
+ 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));
+ }
+
+ return wps_cb_set_ie(reg, beacon, probe);
+}
+
+
+static int wps_get_dev_password(struct wps_data *wps)
+{
+ const u8 *pin;
+ size_t pin_len = 0;
+
+ os_free(wps->dev_password);
+ wps->dev_password = NULL;
+
+ if (wps->pbc) {
+ wpa_printf(MSG_DEBUG, "WPS: Use default PIN for PBC");
+ pin = (const u8 *) "00000000";
+ pin_len = 8;
+ } else {
+ pin = wps_registrar_get_pin(wps->wps->registrar, wps->uuid_e,
+ &pin_len);
+ }
+ if (pin == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Device Password available for "
+ "the Enrollee");
+ wps_cb_pin_needed(wps->wps->registrar, wps->uuid_e,
+ &wps->peer_dev);
+ return -1;
+ }
+
+ wps->dev_password = os_malloc(pin_len);
+ if (wps->dev_password == NULL)
+ return -1;
+ os_memcpy(wps->dev_password, pin, pin_len);
+ wps->dev_password_len = pin_len;
+
+ return 0;
+}
+
+
+static int wps_build_uuid_r(struct wps_data *wps, struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * UUID-R");
+ wpabuf_put_be16(msg, ATTR_UUID_R);
+ wpabuf_put_be16(msg, WPS_UUID_LEN);
+ wpabuf_put_data(msg, wps->uuid_r, WPS_UUID_LEN);
+ return 0;
+}
+
+
+static int wps_build_r_hash(struct wps_data *wps, struct wpabuf *msg)
+{
+ u8 *hash;
+ const u8 *addr[4];
+ size_t len[4];
+
+ if (random_get_bytes(wps->snonce, 2 * WPS_SECRET_NONCE_LEN) < 0)
+ return -1;
+ wpa_hexdump(MSG_DEBUG, "WPS: R-S1", wps->snonce, WPS_SECRET_NONCE_LEN);
+ wpa_hexdump(MSG_DEBUG, "WPS: R-S2",
+ wps->snonce + WPS_SECRET_NONCE_LEN, WPS_SECRET_NONCE_LEN);
+
+ if (wps->dh_pubkey_e == NULL || wps->dh_pubkey_r == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: DH public keys not available for "
+ "R-Hash derivation");
+ return -1;
+ }
+
+ wpa_printf(MSG_DEBUG, "WPS: * R-Hash1");
+ wpabuf_put_be16(msg, ATTR_R_HASH1);
+ wpabuf_put_be16(msg, SHA256_MAC_LEN);
+ hash = wpabuf_put(msg, SHA256_MAC_LEN);
+ /* R-Hash1 = HMAC_AuthKey(R-S1 || PSK1 || PK_E || PK_R) */
+ addr[0] = wps->snonce;
+ len[0] = WPS_SECRET_NONCE_LEN;
+ addr[1] = wps->psk1;
+ len[1] = WPS_PSK_LEN;
+ addr[2] = wpabuf_head(wps->dh_pubkey_e);
+ len[2] = wpabuf_len(wps->dh_pubkey_e);
+ addr[3] = wpabuf_head(wps->dh_pubkey_r);
+ len[3] = wpabuf_len(wps->dh_pubkey_r);
+ hmac_sha256_vector(wps->authkey, WPS_AUTHKEY_LEN, 4, addr, len, hash);
+ wpa_hexdump(MSG_DEBUG, "WPS: R-Hash1", hash, SHA256_MAC_LEN);
+
+ wpa_printf(MSG_DEBUG, "WPS: * R-Hash2");
+ wpabuf_put_be16(msg, ATTR_R_HASH2);
+ wpabuf_put_be16(msg, SHA256_MAC_LEN);
+ hash = wpabuf_put(msg, SHA256_MAC_LEN);
+ /* R-Hash2 = HMAC_AuthKey(R-S2 || PSK2 || PK_E || PK_R) */
+ addr[0] = wps->snonce + WPS_SECRET_NONCE_LEN;
+ addr[1] = wps->psk2;
+ hmac_sha256_vector(wps->authkey, WPS_AUTHKEY_LEN, 4, addr, len, hash);
+ wpa_hexdump(MSG_DEBUG, "WPS: R-Hash2", hash, SHA256_MAC_LEN);
+
+ return 0;
+}
+
+
+static int wps_build_r_snonce1(struct wps_data *wps, struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * R-SNonce1");
+ wpabuf_put_be16(msg, ATTR_R_SNONCE1);
+ wpabuf_put_be16(msg, WPS_SECRET_NONCE_LEN);
+ wpabuf_put_data(msg, wps->snonce, WPS_SECRET_NONCE_LEN);
+ return 0;
+}
+
+
+static int wps_build_r_snonce2(struct wps_data *wps, struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * R-SNonce2");
+ wpabuf_put_be16(msg, ATTR_R_SNONCE2);
+ wpabuf_put_be16(msg, WPS_SECRET_NONCE_LEN);
+ wpabuf_put_data(msg, wps->snonce + WPS_SECRET_NONCE_LEN,
+ WPS_SECRET_NONCE_LEN);
+ return 0;
+}
+
+
+static int wps_build_cred_network_idx(struct wpabuf *msg,
+ const struct wps_credential *cred)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * Network Index (1)");
+ wpabuf_put_be16(msg, ATTR_NETWORK_INDEX);
+ wpabuf_put_be16(msg, 1);
+ wpabuf_put_u8(msg, 1);
+ return 0;
+}
+
+
+static int wps_build_cred_ssid(struct wpabuf *msg,
+ const struct wps_credential *cred)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * SSID");
+ wpa_hexdump_ascii(MSG_DEBUG, "WPS: SSID for Credential",
+ cred->ssid, cred->ssid_len);
+ wpabuf_put_be16(msg, ATTR_SSID);
+ wpabuf_put_be16(msg, cred->ssid_len);
+ wpabuf_put_data(msg, cred->ssid, cred->ssid_len);
+ return 0;
+}
+
+
+static int wps_build_cred_auth_type(struct wpabuf *msg,
+ const struct wps_credential *cred)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * Authentication Type (0x%x)",
+ cred->auth_type);
+ wpabuf_put_be16(msg, ATTR_AUTH_TYPE);
+ wpabuf_put_be16(msg, 2);
+ wpabuf_put_be16(msg, cred->auth_type);
+ return 0;
+}
+
+
+static int wps_build_cred_encr_type(struct wpabuf *msg,
+ const struct wps_credential *cred)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * Encryption Type (0x%x)",
+ cred->encr_type);
+ wpabuf_put_be16(msg, ATTR_ENCR_TYPE);
+ wpabuf_put_be16(msg, 2);
+ wpabuf_put_be16(msg, cred->encr_type);
+ return 0;
+}
+
+
+static int wps_build_cred_network_key(struct wpabuf *msg,
+ const struct wps_credential *cred)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * Network Key (len=%d)",
+ (int) cred->key_len);
+ wpa_hexdump_key(MSG_DEBUG, "WPS: Network Key",
+ cred->key, cred->key_len);
+ wpabuf_put_be16(msg, ATTR_NETWORK_KEY);
+ wpabuf_put_be16(msg, cred->key_len);
+ wpabuf_put_data(msg, cred->key, cred->key_len);
+ return 0;
+}
+
+
+static int wps_build_cred_mac_addr(struct wpabuf *msg,
+ const struct wps_credential *cred)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * MAC Address (" MACSTR ")",
+ MAC2STR(cred->mac_addr));
+ wpabuf_put_be16(msg, ATTR_MAC_ADDR);
+ wpabuf_put_be16(msg, ETH_ALEN);
+ wpabuf_put_data(msg, cred->mac_addr, ETH_ALEN);
+ return 0;
+}
+
+
+static int wps_build_credential(struct wpabuf *msg,
+ const struct wps_credential *cred)
+{
+ if (wps_build_cred_network_idx(msg, cred) ||
+ wps_build_cred_ssid(msg, cred) ||
+ wps_build_cred_auth_type(msg, cred) ||
+ wps_build_cred_encr_type(msg, cred) ||
+ wps_build_cred_network_key(msg, cred) ||
+ wps_build_cred_mac_addr(msg, cred))
+ return -1;
+ return 0;
+}
+
+
+int wps_build_cred(struct wps_data *wps, struct wpabuf *msg)
+{
+ struct wpabuf *cred;
+
+ if (wps->wps->registrar->skip_cred_build)
+ goto skip_cred_build;
+
+ wpa_printf(MSG_DEBUG, "WPS: * Credential");
+ if (wps->use_cred) {
+ os_memcpy(&wps->cred, wps->use_cred, sizeof(wps->cred));
+ goto use_provided;
+ }
+ os_memset(&wps->cred, 0, sizeof(wps->cred));
+
+ os_memcpy(wps->cred.ssid, wps->wps->ssid, wps->wps->ssid_len);
+ wps->cred.ssid_len = wps->wps->ssid_len;
+
+ /* Select the best authentication and encryption type */
+ if (wps->auth_type & WPS_AUTH_WPA2PSK)
+ wps->auth_type = WPS_AUTH_WPA2PSK;
+ else if (wps->auth_type & WPS_AUTH_WPAPSK)
+ wps->auth_type = WPS_AUTH_WPAPSK;
+ else if (wps->auth_type & WPS_AUTH_OPEN)
+ wps->auth_type = WPS_AUTH_OPEN;
+ else if (wps->auth_type & WPS_AUTH_SHARED)
+ wps->auth_type = WPS_AUTH_SHARED;
+ else {
+ wpa_printf(MSG_DEBUG, "WPS: Unsupported auth_type 0x%x",
+ wps->auth_type);
+ return -1;
+ }
+ wps->cred.auth_type = wps->auth_type;
+
+ if (wps->auth_type == WPS_AUTH_WPA2PSK ||
+ wps->auth_type == WPS_AUTH_WPAPSK) {
+ if (wps->encr_type & WPS_ENCR_AES)
+ wps->encr_type = WPS_ENCR_AES;
+ else if (wps->encr_type & WPS_ENCR_TKIP)
+ wps->encr_type = WPS_ENCR_TKIP;
+ else {
+ wpa_printf(MSG_DEBUG, "WPS: No suitable encryption "
+ "type for WPA/WPA2");
+ return -1;
+ }
+ } else {
+ if (wps->encr_type & WPS_ENCR_WEP)
+ wps->encr_type = WPS_ENCR_WEP;
+ else if (wps->encr_type & WPS_ENCR_NONE)
+ wps->encr_type = WPS_ENCR_NONE;
+ else {
+ wpa_printf(MSG_DEBUG, "WPS: No suitable encryption "
+ "type for non-WPA/WPA2 mode");
+ return -1;
+ }
+ }
+ wps->cred.encr_type = wps->encr_type;
+ /*
+ * 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) {
+ u8 r[16];
+ /* Generate a random passphrase */
+ if (random_get_bytes(r, sizeof(r)) < 0)
+ return -1;
+ os_free(wps->new_psk);
+ wps->new_psk = base64_encode(r, sizeof(r), &wps->new_psk_len);
+ if (wps->new_psk == NULL)
+ return -1;
+ wps->new_psk_len--; /* remove newline */
+ while (wps->new_psk_len &&
+ wps->new_psk[wps->new_psk_len - 1] == '=')
+ wps->new_psk_len--;
+ wpa_hexdump_ascii_key(MSG_DEBUG, "WPS: Generated passphrase",
+ wps->new_psk, wps->new_psk_len);
+ os_memcpy(wps->cred.key, wps->new_psk, wps->new_psk_len);
+ wps->cred.key_len = wps->new_psk_len;
+ } else if (wps->use_psk_key && wps->wps->psk_set) {
+ char hex[65];
+ wpa_printf(MSG_DEBUG, "WPS: Use PSK format for Network Key");
+ wpa_snprintf_hex(hex, sizeof(hex), wps->wps->psk, 32);
+ os_memcpy(wps->cred.key, hex, 32 * 2);
+ wps->cred.key_len = 32 * 2;
+ } else if (wps->wps->network_key) {
+ os_memcpy(wps->cred.key, wps->wps->network_key,
+ wps->wps->network_key_len);
+ wps->cred.key_len = wps->wps->network_key_len;
+ } else if (wps->auth_type & (WPS_AUTH_WPAPSK | WPS_AUTH_WPA2PSK)) {
+ char hex[65];
+ /* Generate a random per-device PSK */
+ os_free(wps->new_psk);
+ wps->new_psk_len = 32;
+ wps->new_psk = os_malloc(wps->new_psk_len);
+ if (wps->new_psk == NULL)
+ return -1;
+ if (random_get_bytes(wps->new_psk, wps->new_psk_len) < 0) {
+ os_free(wps->new_psk);
+ wps->new_psk = NULL;
+ return -1;
+ }
+ wpa_hexdump_key(MSG_DEBUG, "WPS: Generated per-device PSK",
+ wps->new_psk, wps->new_psk_len);
+ wpa_snprintf_hex(hex, sizeof(hex), wps->new_psk,
+ wps->new_psk_len);
+ os_memcpy(wps->cred.key, hex, wps->new_psk_len * 2);
+ wps->cred.key_len = wps->new_psk_len * 2;
+ }
+
+use_provided:
+#ifdef CONFIG_WPS_TESTING
+ if (wps_testing_dummy_cred)
+ cred = wpabuf_alloc(200);
+ else
+ cred = NULL;
+ if (cred) {
+ struct wps_credential dummy;
+ wpa_printf(MSG_DEBUG, "WPS: Add dummy credential");
+ os_memset(&dummy, 0, sizeof(dummy));
+ os_memcpy(dummy.ssid, "dummy", 5);
+ dummy.ssid_len = 5;
+ dummy.auth_type = WPS_AUTH_WPA2PSK;
+ dummy.encr_type = WPS_ENCR_AES;
+ os_memcpy(dummy.key, "dummy psk", 9);
+ dummy.key_len = 9;
+ os_memcpy(dummy.mac_addr, wps->mac_addr_e, ETH_ALEN);
+ wps_build_credential(cred, &dummy);
+ wpa_hexdump_buf(MSG_DEBUG, "WPS: Dummy Credential", cred);
+
+ wpabuf_put_be16(msg, ATTR_CRED);
+ wpabuf_put_be16(msg, wpabuf_len(cred));
+ wpabuf_put_buf(msg, cred);
+
+ wpabuf_free(cred);
+ }
+#endif /* CONFIG_WPS_TESTING */
+
+ cred = wpabuf_alloc(200);
+ if (cred == NULL)
+ return -1;
+
+ if (wps_build_credential(cred, &wps->cred)) {
+ wpabuf_free(cred);
+ return -1;
+ }
+
+ wpabuf_put_be16(msg, ATTR_CRED);
+ wpabuf_put_be16(msg, wpabuf_len(cred));
+ wpabuf_put_buf(msg, cred);
+ wpabuf_free(cred);
+
+skip_cred_build:
+ if (wps->wps->registrar->extra_cred) {
+ wpa_printf(MSG_DEBUG, "WPS: * Credential (pre-configured)");
+ wpabuf_put_buf(msg, wps->wps->registrar->extra_cred);
+ }
+
+ return 0;
+}
+
+
+static int wps_build_ap_settings(struct wps_data *wps, struct wpabuf *msg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: * AP Settings");
+
+ if (wps_build_credential(msg, &wps->cred))
+ return -1;
+
+ return 0;
+}
+
+
+static struct wpabuf * wps_build_m2(struct wps_data *wps)
+{
+ struct wpabuf *msg;
+
+ if (random_get_bytes(wps->nonce_r, WPS_NONCE_LEN) < 0)
+ return NULL;
+ wpa_hexdump(MSG_DEBUG, "WPS: Registrar Nonce",
+ wps->nonce_r, WPS_NONCE_LEN);
+ wpa_hexdump(MSG_DEBUG, "WPS: UUID-R", wps->uuid_r, WPS_UUID_LEN);
+
+ wpa_printf(MSG_DEBUG, "WPS: Building Message M2");
+ msg = wpabuf_alloc(1000);
+ if (msg == NULL)
+ return NULL;
+
+ if (wps_build_version(msg) ||
+ wps_build_msg_type(msg, WPS_M2) ||
+ wps_build_enrollee_nonce(wps, msg) ||
+ wps_build_registrar_nonce(wps, msg) ||
+ wps_build_uuid_r(wps, msg) ||
+ wps_build_public_key(wps, msg) ||
+ wps_derive_keys(wps) ||
+ wps_build_auth_type_flags(wps, msg) ||
+ wps_build_encr_type_flags(wps, msg) ||
+ wps_build_conn_type_flags(wps, msg) ||
+ wps_build_config_methods_r(wps->wps->registrar, msg) ||
+ wps_build_device_attrs(&wps->wps->dev, msg) ||
+ wps_build_rf_bands(&wps->wps->dev, msg) ||
+ wps_build_assoc_state(wps, msg) ||
+ wps_build_config_error(msg, WPS_CFG_NO_ERROR) ||
+ wps_build_dev_password_id(msg, wps->dev_pw_id) ||
+ wps_build_os_version(&wps->wps->dev, msg) ||
+ wps_build_wfa_ext(msg, 0, NULL, 0) ||
+ wps_build_authenticator(wps, msg)) {
+ wpabuf_free(msg);
+ return NULL;
+ }
+
+ wps->int_reg = 1;
+ wps->state = RECV_M3;
+ return msg;
+}
+
+
+static struct wpabuf * wps_build_m2d(struct wps_data *wps)
+{
+ struct wpabuf *msg;
+ 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 &&
+ err == WPS_CFG_NO_ERROR)
+ err = WPS_CFG_SETUP_LOCKED;
+
+ if (wps_build_version(msg) ||
+ wps_build_msg_type(msg, WPS_M2D) ||
+ wps_build_enrollee_nonce(wps, msg) ||
+ wps_build_registrar_nonce(wps, msg) ||
+ wps_build_uuid_r(wps, msg) ||
+ wps_build_auth_type_flags(wps, msg) ||
+ wps_build_encr_type_flags(wps, msg) ||
+ wps_build_conn_type_flags(wps, msg) ||
+ wps_build_config_methods_r(wps->wps->registrar, msg) ||
+ wps_build_device_attrs(&wps->wps->dev, msg) ||
+ wps_build_rf_bands(&wps->wps->dev, msg) ||
+ wps_build_assoc_state(wps, msg) ||
+ wps_build_config_error(msg, err) ||
+ wps_build_os_version(&wps->wps->dev, msg) ||
+ wps_build_wfa_ext(msg, 0, NULL, 0)) {
+ wpabuf_free(msg);
+ return NULL;
+ }
+
+ wps->state = RECV_M2D_ACK;
+ return msg;
+}
+
+
+static struct wpabuf * wps_build_m4(struct wps_data *wps)
+{
+ struct wpabuf *msg, *plain;
+
+ wpa_printf(MSG_DEBUG, "WPS: Building Message M4");
+
+ wps_derive_psk(wps, wps->dev_password, wps->dev_password_len);
+
+ plain = wpabuf_alloc(200);
+ if (plain == NULL)
+ return NULL;
+
+ msg = wpabuf_alloc(1000);
+ if (msg == NULL) {
+ wpabuf_free(plain);
+ return NULL;
+ }
+
+ if (wps_build_version(msg) ||
+ wps_build_msg_type(msg, WPS_M4) ||
+ wps_build_enrollee_nonce(wps, msg) ||
+ wps_build_r_hash(wps, msg) ||
+ wps_build_r_snonce1(wps, plain) ||
+ wps_build_key_wrap_auth(wps, plain) ||
+ wps_build_encr_settings(wps, msg, plain) ||
+ wps_build_wfa_ext(msg, 0, NULL, 0) ||
+ wps_build_authenticator(wps, msg)) {
+ wpabuf_free(plain);
+ wpabuf_free(msg);
+ return NULL;
+ }
+ wpabuf_free(plain);
+
+ wps->state = RECV_M5;
+ return msg;
+}
+
+
+static struct wpabuf * wps_build_m6(struct wps_data *wps)
+{
+ struct wpabuf *msg, *plain;
+
+ wpa_printf(MSG_DEBUG, "WPS: Building Message M6");
+
+ plain = wpabuf_alloc(200);
+ if (plain == NULL)
+ return NULL;
+
+ msg = wpabuf_alloc(1000);
+ if (msg == NULL) {
+ wpabuf_free(plain);
+ return NULL;
+ }
+
+ if (wps_build_version(msg) ||
+ wps_build_msg_type(msg, WPS_M6) ||
+ wps_build_enrollee_nonce(wps, msg) ||
+ wps_build_r_snonce2(wps, plain) ||
+ wps_build_key_wrap_auth(wps, plain) ||
+ wps_build_encr_settings(wps, msg, plain) ||
+ wps_build_wfa_ext(msg, 0, NULL, 0) ||
+ wps_build_authenticator(wps, msg)) {
+ wpabuf_free(plain);
+ wpabuf_free(msg);
+ return NULL;
+ }
+ wpabuf_free(plain);
+
+ wps->wps_pin_revealed = 1;
+ wps->state = RECV_M7;
+ return msg;
+}
+
+
+static struct wpabuf * wps_build_m8(struct wps_data *wps)
+{
+ struct wpabuf *msg, *plain;
+
+ wpa_printf(MSG_DEBUG, "WPS: Building Message M8");
+
+ plain = wpabuf_alloc(500);
+ if (plain == NULL)
+ return NULL;
+
+ msg = wpabuf_alloc(1000);
+ if (msg == NULL) {
+ wpabuf_free(plain);
+ return NULL;
+ }
+
+ if (wps_build_version(msg) ||
+ wps_build_msg_type(msg, WPS_M8) ||
+ wps_build_enrollee_nonce(wps, msg) ||
+ ((wps->wps->ap || wps->er) && wps_build_cred(wps, plain)) ||
+ (!wps->wps->ap && !wps->er && wps_build_ap_settings(wps, plain)) ||
+ wps_build_key_wrap_auth(wps, plain) ||
+ wps_build_encr_settings(wps, msg, plain) ||
+ wps_build_wfa_ext(msg, 0, NULL, 0) ||
+ wps_build_authenticator(wps, msg)) {
+ wpabuf_free(plain);
+ wpabuf_free(msg);
+ return NULL;
+ }
+ wpabuf_free(plain);
+
+ wps->state = RECV_DONE;
+ return msg;
+}
+
+
+struct wpabuf * wps_registrar_get_msg(struct wps_data *wps,
+ enum wsc_op_code *op_code)
+{
+ struct wpabuf *msg;
+
+#ifdef CONFIG_WPS_UPNP
+ if (!wps->int_reg && wps->wps->wps_upnp) {
+ struct upnp_pending_message *p, *prev = NULL;
+ if (wps->ext_reg > 1)
+ wps_registrar_free_pending_m2(wps->wps);
+ p = wps->wps->upnp_msgs;
+ /* TODO: check pending message MAC address */
+ while (p && p->next) {
+ prev = p;
+ p = p->next;
+ }
+ if (p) {
+ wpa_printf(MSG_DEBUG, "WPS: Use pending message from "
+ "UPnP");
+ if (prev)
+ prev->next = NULL;
+ 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);
+ if (wps->ext_reg == 0)
+ wps->ext_reg = 1;
+ return msg;
+ }
+ }
+ if (wps->ext_reg) {
+ wpa_printf(MSG_DEBUG, "WPS: Using external Registrar, but no "
+ "pending message available");
+ return NULL;
+ }
+#endif /* CONFIG_WPS_UPNP */
+
+ switch (wps->state) {
+ case SEND_M2:
+ if (wps_get_dev_password(wps) < 0)
+ msg = wps_build_m2d(wps);
+ else
+ msg = wps_build_m2(wps);
+ *op_code = WSC_MSG;
+ break;
+ case SEND_M2D:
+ msg = wps_build_m2d(wps);
+ *op_code = WSC_MSG;
+ break;
+ case SEND_M4:
+ msg = wps_build_m4(wps);
+ *op_code = WSC_MSG;
+ break;
+ case SEND_M6:
+ msg = wps_build_m6(wps);
+ *op_code = WSC_MSG;
+ break;
+ case SEND_M8:
+ msg = wps_build_m8(wps);
+ *op_code = WSC_MSG;
+ break;
+ case RECV_DONE:
+ msg = wps_build_wsc_ack(wps);
+ *op_code = WSC_ACK;
+ break;
+ case SEND_WSC_NACK:
+ msg = wps_build_wsc_nack(wps);
+ *op_code = WSC_NACK;
+ break;
+ default:
+ wpa_printf(MSG_DEBUG, "WPS: Unsupported state %d for building "
+ "a message", wps->state);
+ msg = NULL;
+ break;
+ }
+
+ if (*op_code == WSC_MSG && msg) {
+ /* Save a copy of the last message for Authenticator derivation
+ */
+ wpabuf_free(wps->last_msg);
+ wps->last_msg = wpabuf_dup(msg);
+ }
+
+ return msg;
+}
+
+
+static int wps_process_enrollee_nonce(struct wps_data *wps, const u8 *e_nonce)
+{
+ if (e_nonce == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Enrollee Nonce received");
+ return -1;
+ }
+
+ os_memcpy(wps->nonce_e, e_nonce, WPS_NONCE_LEN);
+ wpa_hexdump(MSG_DEBUG, "WPS: Enrollee Nonce",
+ wps->nonce_e, WPS_NONCE_LEN);
+
+ return 0;
+}
+
+
+static int wps_process_registrar_nonce(struct wps_data *wps, const u8 *r_nonce)
+{
+ if (r_nonce == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Registrar Nonce received");
+ return -1;
+ }
+
+ if (os_memcmp(wps->nonce_r, r_nonce, WPS_NONCE_LEN) != 0) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Registrar Nonce received");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static int wps_process_uuid_e(struct wps_data *wps, const u8 *uuid_e)
+{
+ if (uuid_e == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No UUID-E received");
+ return -1;
+ }
+
+ os_memcpy(wps->uuid_e, uuid_e, WPS_UUID_LEN);
+ wpa_hexdump(MSG_DEBUG, "WPS: UUID-E", wps->uuid_e, WPS_UUID_LEN);
+
+ return 0;
+}
+
+
+static int wps_process_dev_password_id(struct wps_data *wps, const u8 *pw_id)
+{
+ if (pw_id == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Device Password ID received");
+ return -1;
+ }
+
+ wps->dev_pw_id = WPA_GET_BE16(pw_id);
+ wpa_printf(MSG_DEBUG, "WPS: Device Password ID %d", wps->dev_pw_id);
+
+ return 0;
+}
+
+
+static int wps_process_e_hash1(struct wps_data *wps, const u8 *e_hash1)
+{
+ if (e_hash1 == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No E-Hash1 received");
+ return -1;
+ }
+
+ os_memcpy(wps->peer_hash1, e_hash1, WPS_HASH_LEN);
+ wpa_hexdump(MSG_DEBUG, "WPS: E-Hash1", wps->peer_hash1, WPS_HASH_LEN);
+
+ return 0;
+}
+
+
+static int wps_process_e_hash2(struct wps_data *wps, const u8 *e_hash2)
+{
+ if (e_hash2 == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No E-Hash2 received");
+ return -1;
+ }
+
+ os_memcpy(wps->peer_hash2, e_hash2, WPS_HASH_LEN);
+ wpa_hexdump(MSG_DEBUG, "WPS: E-Hash2", wps->peer_hash2, WPS_HASH_LEN);
+
+ return 0;
+}
+
+
+static int wps_process_e_snonce1(struct wps_data *wps, const u8 *e_snonce1)
+{
+ u8 hash[SHA256_MAC_LEN];
+ const u8 *addr[4];
+ size_t len[4];
+
+ if (e_snonce1 == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No E-SNonce1 received");
+ return -1;
+ }
+
+ wpa_hexdump_key(MSG_DEBUG, "WPS: E-SNonce1", e_snonce1,
+ WPS_SECRET_NONCE_LEN);
+
+ /* E-Hash1 = HMAC_AuthKey(E-S1 || PSK1 || PK_E || PK_R) */
+ addr[0] = e_snonce1;
+ len[0] = WPS_SECRET_NONCE_LEN;
+ addr[1] = wps->psk1;
+ len[1] = WPS_PSK_LEN;
+ addr[2] = wpabuf_head(wps->dh_pubkey_e);
+ len[2] = wpabuf_len(wps->dh_pubkey_e);
+ addr[3] = wpabuf_head(wps->dh_pubkey_r);
+ len[3] = wpabuf_len(wps->dh_pubkey_r);
+ hmac_sha256_vector(wps->authkey, WPS_AUTHKEY_LEN, 4, addr, len, hash);
+
+ if (os_memcmp(wps->peer_hash1, hash, WPS_HASH_LEN) != 0) {
+ wpa_printf(MSG_DEBUG, "WPS: E-Hash1 derived from E-S1 does "
+ "not match with the pre-committed value");
+ wps->config_error = WPS_CFG_DEV_PASSWORD_AUTH_FAILURE;
+ wps_pwd_auth_fail_event(wps->wps, 0, 1);
+ return -1;
+ }
+
+ wpa_printf(MSG_DEBUG, "WPS: Enrollee proved knowledge of the first "
+ "half of the device password");
+
+ return 0;
+}
+
+
+static int wps_process_e_snonce2(struct wps_data *wps, const u8 *e_snonce2)
+{
+ u8 hash[SHA256_MAC_LEN];
+ const u8 *addr[4];
+ size_t len[4];
+
+ if (e_snonce2 == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No E-SNonce2 received");
+ return -1;
+ }
+
+ wpa_hexdump_key(MSG_DEBUG, "WPS: E-SNonce2", e_snonce2,
+ WPS_SECRET_NONCE_LEN);
+
+ /* E-Hash2 = HMAC_AuthKey(E-S2 || PSK2 || PK_E || PK_R) */
+ addr[0] = e_snonce2;
+ len[0] = WPS_SECRET_NONCE_LEN;
+ addr[1] = wps->psk2;
+ len[1] = WPS_PSK_LEN;
+ addr[2] = wpabuf_head(wps->dh_pubkey_e);
+ len[2] = wpabuf_len(wps->dh_pubkey_e);
+ addr[3] = wpabuf_head(wps->dh_pubkey_r);
+ len[3] = wpabuf_len(wps->dh_pubkey_r);
+ hmac_sha256_vector(wps->authkey, WPS_AUTHKEY_LEN, 4, addr, len, hash);
+
+ if (os_memcmp(wps->peer_hash2, hash, WPS_HASH_LEN) != 0) {
+ wpa_printf(MSG_DEBUG, "WPS: E-Hash2 derived from E-S2 does "
+ "not match with the pre-committed value");
+ wps_registrar_invalidate_pin(wps->wps->registrar, wps->uuid_e);
+ wps->config_error = WPS_CFG_DEV_PASSWORD_AUTH_FAILURE;
+ wps_pwd_auth_fail_event(wps->wps, 0, 2);
+ return -1;
+ }
+
+ wpa_printf(MSG_DEBUG, "WPS: Enrollee proved knowledge of the second "
+ "half of the device password");
+ wps->wps_pin_revealed = 0;
+ wps_registrar_unlock_pin(wps->wps->registrar, wps->uuid_e);
+
+ return 0;
+}
+
+
+static int wps_process_mac_addr(struct wps_data *wps, const u8 *mac_addr)
+{
+ if (mac_addr == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No MAC Address received");
+ return -1;
+ }
+
+ wpa_printf(MSG_DEBUG, "WPS: Enrollee MAC Address " MACSTR,
+ MAC2STR(mac_addr));
+ os_memcpy(wps->mac_addr_e, mac_addr, ETH_ALEN);
+ os_memcpy(wps->peer_dev.mac_addr, mac_addr, ETH_ALEN);
+
+ return 0;
+}
+
+
+static int wps_process_pubkey(struct wps_data *wps, const u8 *pk,
+ size_t pk_len)
+{
+ if (pk == NULL || pk_len == 0) {
+ wpa_printf(MSG_DEBUG, "WPS: No Public Key received");
+ return -1;
+ }
+
+#ifdef CONFIG_WPS_OOB
+ if (wps->wps->oob_conf.pubkey_hash != NULL) {
+ const u8 *addr[1];
+ u8 hash[WPS_HASH_LEN];
+
+ addr[0] = pk;
+ sha256_vector(1, addr, &pk_len, hash);
+ if (os_memcmp(hash,
+ wpabuf_head(wps->wps->oob_conf.pubkey_hash),
+ WPS_OOB_PUBKEY_HASH_LEN) != 0) {
+ wpa_printf(MSG_ERROR, "WPS: Public Key hash error");
+ return -1;
+ }
+ }
+#endif /* CONFIG_WPS_OOB */
+
+ wpabuf_free(wps->dh_pubkey_e);
+ wps->dh_pubkey_e = wpabuf_alloc_copy(pk, pk_len);
+ if (wps->dh_pubkey_e == NULL)
+ return -1;
+
+ return 0;
+}
+
+
+static int wps_process_auth_type_flags(struct wps_data *wps, const u8 *auth)
+{
+ u16 auth_types;
+
+ if (auth == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Authentication Type flags "
+ "received");
+ return -1;
+ }
+
+ auth_types = WPA_GET_BE16(auth);
+
+ wpa_printf(MSG_DEBUG, "WPS: Enrollee Authentication Type flags 0x%x",
+ auth_types);
+ wps->auth_type = wps->wps->auth_types & auth_types;
+ if (wps->auth_type == 0) {
+ 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;
+}
+
+
+static int wps_process_encr_type_flags(struct wps_data *wps, const u8 *encr)
+{
+ u16 encr_types;
+
+ if (encr == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Encryption Type flags "
+ "received");
+ return -1;
+ }
+
+ encr_types = WPA_GET_BE16(encr);
+
+ wpa_printf(MSG_DEBUG, "WPS: Enrollee Encryption Type flags 0x%x",
+ encr_types);
+ 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 (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;
+}
+
+
+static int wps_process_conn_type_flags(struct wps_data *wps, const u8 *conn)
+{
+ if (conn == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Connection Type flags "
+ "received");
+ return -1;
+ }
+
+ wpa_printf(MSG_DEBUG, "WPS: Enrollee Connection Type flags 0x%x",
+ *conn);
+
+ return 0;
+}
+
+
+static int wps_process_config_methods(struct wps_data *wps, const u8 *methods)
+{
+ u16 m;
+
+ if (methods == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Config Methods received");
+ return -1;
+ }
+
+ m = WPA_GET_BE16(methods);
+
+ wpa_printf(MSG_DEBUG, "WPS: Enrollee Config Methods 0x%x"
+ "%s%s%s%s%s%s%s%s%s", m,
+ m & WPS_CONFIG_USBA ? " [USBA]" : "",
+ m & WPS_CONFIG_ETHERNET ? " [Ethernet]" : "",
+ m & WPS_CONFIG_LABEL ? " [Label]" : "",
+ m & WPS_CONFIG_DISPLAY ? " [Display]" : "",
+ m & WPS_CONFIG_EXT_NFC_TOKEN ? " [Ext NFC Token]" : "",
+ m & WPS_CONFIG_INT_NFC_TOKEN ? " [Int NFC Token]" : "",
+ m & WPS_CONFIG_NFC_INTERFACE ? " [NFC]" : "",
+ m & WPS_CONFIG_PUSHBUTTON ? " [PBC]" : "",
+ m & WPS_CONFIG_KEYPAD ? " [Keypad]" : "");
+
+ if (!(m & WPS_CONFIG_DISPLAY) && !wps->use_psk_key) {
+ /*
+ * The Enrollee does not have a display so it is unlikely to be
+ * able to show the passphrase to a user and as such, could
+ * benefit from receiving PSK to reduce key derivation time.
+ */
+ wpa_printf(MSG_DEBUG, "WPS: Prefer PSK format key due to "
+ "Enrollee not supporting display");
+ wps->use_psk_key = 1;
+ }
+
+ return 0;
+}
+
+
+static int wps_process_wps_state(struct wps_data *wps, const u8 *state)
+{
+ if (state == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Wi-Fi Protected Setup State "
+ "received");
+ return -1;
+ }
+
+ wpa_printf(MSG_DEBUG, "WPS: Enrollee Wi-Fi Protected Setup State %d",
+ *state);
+
+ return 0;
+}
+
+
+static int wps_process_assoc_state(struct wps_data *wps, const u8 *assoc)
+{
+ u16 a;
+
+ if (assoc == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Association State received");
+ return -1;
+ }
+
+ a = WPA_GET_BE16(assoc);
+ wpa_printf(MSG_DEBUG, "WPS: Enrollee Association State %d", a);
+
+ return 0;
+}
+
+
+static int wps_process_config_error(struct wps_data *wps, const u8 *err)
+{
+ u16 e;
+
+ if (err == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Configuration Error received");
+ return -1;
+ }
+
+ e = WPA_GET_BE16(err);
+ wpa_printf(MSG_DEBUG, "WPS: Enrollee Configuration Error %d", e);
+
+ return 0;
+}
+
+
+static int wps_registrar_p2p_dev_addr_match(struct wps_data *wps)
+{
+#ifdef CONFIG_P2P
+ struct wps_registrar *reg = wps->wps->registrar;
+
+ if (is_zero_ether_addr(reg->p2p_dev_addr))
+ return 1; /* no filtering in use */
+
+ if (os_memcmp(reg->p2p_dev_addr, wps->p2p_dev_addr, ETH_ALEN) != 0) {
+ wpa_printf(MSG_DEBUG, "WPS: No match on P2P Device Address "
+ "filtering for PBC: expected " MACSTR " was "
+ MACSTR " - indicate PBC session overlap",
+ MAC2STR(reg->p2p_dev_addr),
+ MAC2STR(wps->p2p_dev_addr));
+ return 0;
+ }
+#endif /* CONFIG_P2P */
+ return 1;
+}
+
+
+static int wps_registrar_skip_overlap(struct wps_data *wps)
+{
+#ifdef CONFIG_P2P
+ struct wps_registrar *reg = wps->wps->registrar;
+
+ if (is_zero_ether_addr(reg->p2p_dev_addr))
+ return 0; /* no specific Enrollee selected */
+
+ if (os_memcmp(reg->p2p_dev_addr, wps->p2p_dev_addr, ETH_ALEN) == 0) {
+ wpa_printf(MSG_DEBUG, "WPS: Skip PBC overlap due to selected "
+ "Enrollee match");
+ return 1;
+ }
+#endif /* CONFIG_P2P */
+ return 0;
+}
+
+
+static enum wps_process_res wps_process_m1(struct wps_data *wps,
+ struct wps_parse_attr *attr)
+{
+ wpa_printf(MSG_DEBUG, "WPS: Received M1");
+
+ if (wps->state != RECV_M1) {
+ wpa_printf(MSG_DEBUG, "WPS: Unexpected state (%d) for "
+ "receiving M1", wps->state);
+ return WPS_FAILURE;
+ }
+
+ if (wps_process_uuid_e(wps, attr->uuid_e) ||
+ wps_process_mac_addr(wps, attr->mac_addr) ||
+ wps_process_enrollee_nonce(wps, attr->enrollee_nonce) ||
+ wps_process_pubkey(wps, attr->public_key, attr->public_key_len) ||
+ wps_process_auth_type_flags(wps, attr->auth_type_flags) ||
+ wps_process_encr_type_flags(wps, attr->encr_type_flags) ||
+ wps_process_conn_type_flags(wps, attr->conn_type_flags) ||
+ wps_process_config_methods(wps, attr->config_methods) ||
+ wps_process_wps_state(wps, attr->wps_state) ||
+ wps_process_device_attrs(&wps->peer_dev, attr) ||
+ wps_process_rf_bands(&wps->peer_dev, attr->rf_bands) ||
+ wps_process_assoc_state(wps, attr->assoc_state) ||
+ wps_process_dev_password_id(wps, attr->dev_password_id) ||
+ wps_process_config_error(wps, attr->config_error) ||
+ wps_process_os_version(&wps->peer_dev, attr->os_version))
+ return WPS_FAILURE;
+
+ if (wps->dev_pw_id < 0x10 &&
+ wps->dev_pw_id != DEV_PW_DEFAULT &&
+ wps->dev_pw_id != DEV_PW_USER_SPECIFIED &&
+ wps->dev_pw_id != DEV_PW_MACHINE_SPECIFIED &&
+ wps->dev_pw_id != DEV_PW_REGISTRAR_SPECIFIED &&
+ (wps->dev_pw_id != DEV_PW_PUSHBUTTON ||
+ !wps->wps->registrar->pbc)) {
+ wpa_printf(MSG_DEBUG, "WPS: Unsupported Device Password ID %d",
+ wps->dev_pw_id);
+ wps->state = SEND_M2D;
+ return WPS_CONTINUE;
+ }
+
+#ifdef CONFIG_WPS_OOB
+ if (wps->dev_pw_id >= 0x10 &&
+ wps->dev_pw_id != wps->wps->oob_dev_pw_id) {
+ wpa_printf(MSG_DEBUG, "WPS: OOB Device Password ID "
+ "%d mismatch", wps->dev_pw_id);
+ wps->state = SEND_M2D;
+ return WPS_CONTINUE;
+ }
+#endif /* CONFIG_WPS_OOB */
+
+ if (wps->dev_pw_id == DEV_PW_PUSHBUTTON) {
+ if ((wps->wps->registrar->force_pbc_overlap ||
+ wps_registrar_pbc_overlap(wps->wps->registrar,
+ wps->mac_addr_e, wps->uuid_e) ||
+ !wps_registrar_p2p_dev_addr_match(wps)) &&
+ !wps_registrar_skip_overlap(wps)) {
+ 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_fail_event(wps->wps, WPS_M1,
+ WPS_CFG_MULTIPLE_PBC_DETECTED,
+ WPS_EI_NO_ERROR);
+ wps->wps->registrar->force_pbc_overlap = 1;
+ return WPS_CONTINUE;
+ }
+ wps_registrar_add_pbc_session(wps->wps->registrar,
+ wps->mac_addr_e, wps->uuid_e);
+ wps->pbc = 1;
+ }
+
+#ifdef WPS_WORKAROUNDS
+ /*
+ * It looks like Mac OS X 10.6.3 and 10.6.4 do not like Network Key in
+ * passphrase format. To avoid interop issues, force PSK format to be
+ * used.
+ */
+ if (!wps->use_psk_key &&
+ wps->peer_dev.manufacturer &&
+ os_strncmp(wps->peer_dev.manufacturer, "Apple ", 6) == 0 &&
+ wps->peer_dev.model_name &&
+ os_strcmp(wps->peer_dev.model_name, "AirPort") == 0) {
+ wpa_printf(MSG_DEBUG, "WPS: Workaround - Force Network Key in "
+ "PSK format");
+ wps->use_psk_key = 1;
+ }
+#endif /* WPS_WORKAROUNDS */
+
+ wps->state = SEND_M2;
+ return WPS_CONTINUE;
+}
+
+
+static enum wps_process_res wps_process_m3(struct wps_data *wps,
+ const struct wpabuf *msg,
+ struct wps_parse_attr *attr)
+{
+ wpa_printf(MSG_DEBUG, "WPS: Received M3");
+
+ if (wps->state != RECV_M3) {
+ wpa_printf(MSG_DEBUG, "WPS: Unexpected state (%d) for "
+ "receiving M3", wps->state);
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ if (wps->pbc && wps->wps->registrar->force_pbc_overlap &&
+ !wps_registrar_skip_overlap(wps)) {
+ 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) ||
+ wps_process_e_hash2(wps, attr->e_hash2)) {
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ wps->state = SEND_M4;
+ return WPS_CONTINUE;
+}
+
+
+static enum wps_process_res wps_process_m5(struct wps_data *wps,
+ const struct wpabuf *msg,
+ struct wps_parse_attr *attr)
+{
+ struct wpabuf *decrypted;
+ struct wps_parse_attr eattr;
+
+ wpa_printf(MSG_DEBUG, "WPS: Received M5");
+
+ if (wps->state != RECV_M5) {
+ wpa_printf(MSG_DEBUG, "WPS: Unexpected state (%d) for "
+ "receiving M5", wps->state);
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ if (wps->pbc && wps->wps->registrar->force_pbc_overlap &&
+ !wps_registrar_skip_overlap(wps)) {
+ 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;
+ return WPS_CONTINUE;
+ }
+
+ decrypted = wps_decrypt_encr_settings(wps, attr->encr_settings,
+ attr->encr_settings_len);
+ if (decrypted == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: Failed to decrypted Encrypted "
+ "Settings attribute");
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ if (wps_validate_m5_encr(decrypted, attr->version2 != NULL) < 0) {
+ wpabuf_free(decrypted);
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ wpa_printf(MSG_DEBUG, "WPS: Processing decrypted Encrypted Settings "
+ "attribute");
+ if (wps_parse_msg(decrypted, &eattr) < 0 ||
+ wps_process_key_wrap_auth(wps, decrypted, eattr.key_wrap_auth) ||
+ wps_process_e_snonce1(wps, eattr.e_snonce1)) {
+ wpabuf_free(decrypted);
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+ wpabuf_free(decrypted);
+
+ wps->state = SEND_M6;
+ return WPS_CONTINUE;
+}
+
+
+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 void wps_cred_update(struct wps_credential *dst,
+ struct wps_credential *src)
+{
+ os_memcpy(dst->ssid, src->ssid, sizeof(dst->ssid));
+ dst->ssid_len = src->ssid_len;
+ dst->auth_type = src->auth_type;
+ dst->encr_type = src->encr_type;
+ dst->key_idx = src->key_idx;
+ os_memcpy(dst->key, src->key, sizeof(dst->key));
+ dst->key_len = src->key_len;
+}
+
+
+static int wps_process_ap_settings_r(struct wps_data *wps,
+ struct wps_parse_attr *attr)
+{
+ if (wps->wps->ap || wps->er)
+ return 0;
+
+ /* AP Settings Attributes in M7 when Enrollee is an AP */
+ if (wps_process_ap_settings(attr, &wps->cred) < 0)
+ return -1;
+
+ wpa_printf(MSG_INFO, "WPS: Received old AP configuration from AP");
+
+ if (wps->new_ap_settings) {
+ wpa_printf(MSG_INFO, "WPS: Update AP configuration based on "
+ "new settings");
+ wps_cred_update(&wps->cred, wps->new_ap_settings);
+ return 0;
+ } else {
+ /*
+ * Use the AP PIN only to receive the current AP settings, not
+ * to reconfigure the AP.
+ */
+
+ /*
+ * Clear selected registrar here since we do not get to
+ * WSC_Done in this protocol run.
+ */
+ wps_registrar_pin_completed(wps->wps->registrar);
+
+ if (wps->ap_settings_cb) {
+ wps->ap_settings_cb(wps->ap_settings_cb_ctx,
+ &wps->cred);
+ return 1;
+ }
+ wps_sta_cred_cb(wps);
+ return 1;
+ }
+}
+
+
+static enum wps_process_res wps_process_m7(struct wps_data *wps,
+ const struct wpabuf *msg,
+ struct wps_parse_attr *attr)
+{
+ struct wpabuf *decrypted;
+ struct wps_parse_attr eattr;
+
+ wpa_printf(MSG_DEBUG, "WPS: Received M7");
+
+ if (wps->state != RECV_M7) {
+ wpa_printf(MSG_DEBUG, "WPS: Unexpected state (%d) for "
+ "receiving M7", wps->state);
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ if (wps->pbc && wps->wps->registrar->force_pbc_overlap &&
+ !wps_registrar_skip_overlap(wps)) {
+ 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;
+ return WPS_CONTINUE;
+ }
+
+ decrypted = wps_decrypt_encr_settings(wps, attr->encr_settings,
+ attr->encr_settings_len);
+ if (decrypted == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: Failed to decrypt Encrypted "
+ "Settings attribute");
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ if (wps_validate_m7_encr(decrypted, wps->wps->ap || wps->er,
+ attr->version2 != NULL) < 0) {
+ wpabuf_free(decrypted);
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ wpa_printf(MSG_DEBUG, "WPS: Processing decrypted Encrypted Settings "
+ "attribute");
+ if (wps_parse_msg(decrypted, &eattr) < 0 ||
+ wps_process_key_wrap_auth(wps, decrypted, eattr.key_wrap_auth) ||
+ wps_process_e_snonce2(wps, eattr.e_snonce2) ||
+ wps_process_ap_settings_r(wps, &eattr)) {
+ wpabuf_free(decrypted);
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ wpabuf_free(decrypted);
+
+ wps->state = SEND_M8;
+ return WPS_CONTINUE;
+}
+
+
+static enum wps_process_res wps_process_wsc_msg(struct wps_data *wps,
+ const struct wpabuf *msg)
+{
+ struct wps_parse_attr attr;
+ enum wps_process_res ret = WPS_CONTINUE;
+
+ wpa_printf(MSG_DEBUG, "WPS: Received WSC_MSG");
+
+ if (wps_parse_msg(msg, &attr) < 0)
+ return WPS_FAILURE;
+
+ if (attr.msg_type == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Message Type attribute");
+ wps->state = SEND_WSC_NACK;
+ return WPS_CONTINUE;
+ }
+
+ if (*attr.msg_type != WPS_M1 &&
+ (attr.registrar_nonce == NULL ||
+ os_memcmp(wps->nonce_r, attr.registrar_nonce,
+ WPS_NONCE_LEN != 0))) {
+ wpa_printf(MSG_DEBUG, "WPS: Mismatch in registrar nonce");
+ return WPS_FAILURE;
+ }
+
+ switch (*attr.msg_type) {
+ case WPS_M1:
+ if (wps_validate_m1(msg) < 0)
+ return WPS_FAILURE;
+#ifdef CONFIG_WPS_UPNP
+ if (wps->wps->wps_upnp && attr.mac_addr) {
+ /* Remove old pending messages when starting new run */
+ wps_free_pending_msgs(wps->wps->upnp_msgs);
+ wps->wps->upnp_msgs = NULL;
+
+ upnp_wps_device_send_wlan_event(
+ wps->wps->wps_upnp, attr.mac_addr,
+ UPNP_WPS_WLANEVENT_TYPE_EAP, msg);
+ }
+#endif /* CONFIG_WPS_UPNP */
+ ret = wps_process_m1(wps, &attr);
+ break;
+ case WPS_M3:
+ if (wps_validate_m3(msg) < 0)
+ return WPS_FAILURE;
+ ret = wps_process_m3(wps, msg, &attr);
+ if (ret == WPS_FAILURE || wps->state == SEND_WSC_NACK)
+ wps_fail_event(wps->wps, WPS_M3, wps->config_error,
+ wps->error_indication);
+ break;
+ case WPS_M5:
+ if (wps_validate_m5(msg) < 0)
+ return WPS_FAILURE;
+ ret = wps_process_m5(wps, msg, &attr);
+ if (ret == WPS_FAILURE || wps->state == SEND_WSC_NACK)
+ wps_fail_event(wps->wps, WPS_M5, wps->config_error,
+ wps->error_indication);
+ break;
+ case WPS_M7:
+ if (wps_validate_m7(msg) < 0)
+ return WPS_FAILURE;
+ ret = wps_process_m7(wps, msg, &attr);
+ if (ret == WPS_FAILURE || wps->state == SEND_WSC_NACK)
+ wps_fail_event(wps->wps, WPS_M7, wps->config_error,
+ wps->error_indication);
+ break;
+ default:
+ wpa_printf(MSG_DEBUG, "WPS: Unsupported Message Type %d",
+ *attr.msg_type);
+ return WPS_FAILURE;
+ }
+
+ if (ret == WPS_CONTINUE) {
+ /* Save a copy of the last message for Authenticator derivation
+ */
+ wpabuf_free(wps->last_msg);
+ wps->last_msg = wpabuf_dup(msg);
+ }
+
+ return ret;
+}
+
+
+static enum wps_process_res wps_process_wsc_ack(struct wps_data *wps,
+ const struct wpabuf *msg)
+{
+ struct wps_parse_attr attr;
+
+ wpa_printf(MSG_DEBUG, "WPS: Received WSC_ACK");
+
+ if (wps_parse_msg(msg, &attr) < 0)
+ return WPS_FAILURE;
+
+ if (attr.msg_type == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Message Type attribute");
+ return WPS_FAILURE;
+ }
+
+ if (*attr.msg_type != WPS_WSC_ACK) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Message Type %d",
+ *attr.msg_type);
+ return WPS_FAILURE;
+ }
+
+#ifdef CONFIG_WPS_UPNP
+ if (wps->wps->wps_upnp && wps->ext_reg && wps->state == RECV_M2D_ACK &&
+ upnp_wps_subscribers(wps->wps->wps_upnp)) {
+ if (wps->wps->upnp_msgs)
+ return WPS_CONTINUE;
+ wpa_printf(MSG_DEBUG, "WPS: Wait for response from an "
+ "external Registrar");
+ return WPS_PENDING;
+ }
+#endif /* CONFIG_WPS_UPNP */
+
+ if (attr.registrar_nonce == NULL ||
+ os_memcmp(wps->nonce_r, attr.registrar_nonce, WPS_NONCE_LEN != 0))
+ {
+ wpa_printf(MSG_DEBUG, "WPS: Mismatch in registrar nonce");
+ return WPS_FAILURE;
+ }
+
+ if (attr.enrollee_nonce == NULL ||
+ os_memcmp(wps->nonce_e, attr.enrollee_nonce, WPS_NONCE_LEN != 0)) {
+ wpa_printf(MSG_DEBUG, "WPS: Mismatch in enrollee nonce");
+ return WPS_FAILURE;
+ }
+
+ if (wps->state == RECV_M2D_ACK) {
+#ifdef CONFIG_WPS_UPNP
+ if (wps->wps->wps_upnp &&
+ upnp_wps_subscribers(wps->wps->wps_upnp)) {
+ if (wps->wps->upnp_msgs)
+ return WPS_CONTINUE;
+ if (wps->ext_reg == 0)
+ wps->ext_reg = 1;
+ wpa_printf(MSG_DEBUG, "WPS: Wait for response from an "
+ "external Registrar");
+ return WPS_PENDING;
+ }
+#endif /* CONFIG_WPS_UPNP */
+
+ wpa_printf(MSG_DEBUG, "WPS: No more registrars available - "
+ "terminate negotiation");
+ }
+
+ return WPS_FAILURE;
+}
+
+
+static enum wps_process_res wps_process_wsc_nack(struct wps_data *wps,
+ const struct wpabuf *msg)
+{
+ struct wps_parse_attr attr;
+ int old_state;
+ u16 config_error;
+
+ wpa_printf(MSG_DEBUG, "WPS: Received WSC_NACK");
+
+ old_state = wps->state;
+ wps->state = SEND_WSC_NACK;
+
+ if (wps_parse_msg(msg, &attr) < 0)
+ return WPS_FAILURE;
+
+ if (attr.msg_type == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Message Type attribute");
+ return WPS_FAILURE;
+ }
+
+ if (*attr.msg_type != WPS_WSC_NACK) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Message Type %d",
+ *attr.msg_type);
+ return WPS_FAILURE;
+ }
+
+#ifdef CONFIG_WPS_UPNP
+ if (wps->wps->wps_upnp && wps->ext_reg) {
+ wpa_printf(MSG_DEBUG, "WPS: Negotiation using external "
+ "Registrar terminated by the Enrollee");
+ return WPS_FAILURE;
+ }
+#endif /* CONFIG_WPS_UPNP */
+
+ if (attr.registrar_nonce == NULL ||
+ os_memcmp(wps->nonce_r, attr.registrar_nonce, WPS_NONCE_LEN != 0))
+ {
+ wpa_printf(MSG_DEBUG, "WPS: Mismatch in registrar nonce");
+ return WPS_FAILURE;
+ }
+
+ if (attr.enrollee_nonce == NULL ||
+ os_memcmp(wps->nonce_e, attr.enrollee_nonce, WPS_NONCE_LEN != 0)) {
+ wpa_printf(MSG_DEBUG, "WPS: Mismatch in enrollee nonce");
+ return WPS_FAILURE;
+ }
+
+ if (attr.config_error == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Configuration Error attribute "
+ "in WSC_NACK");
+ return WPS_FAILURE;
+ }
+
+ config_error = WPA_GET_BE16(attr.config_error);
+ wpa_printf(MSG_DEBUG, "WPS: Enrollee terminated negotiation with "
+ "Configuration Error %d", config_error);
+
+ switch (old_state) {
+ case RECV_M3:
+ wps_fail_event(wps->wps, WPS_M2, config_error,
+ wps->error_indication);
+ break;
+ case RECV_M5:
+ wps_fail_event(wps->wps, WPS_M4, config_error,
+ wps->error_indication);
+ break;
+ case RECV_M7:
+ wps_fail_event(wps->wps, WPS_M6, config_error,
+ wps->error_indication);
+ break;
+ case RECV_DONE:
+ wps_fail_event(wps->wps, WPS_M8, config_error,
+ wps->error_indication);
+ break;
+ default:
+ break;
+ }
+
+ return WPS_FAILURE;
+}
+
+
+static enum wps_process_res wps_process_wsc_done(struct wps_data *wps,
+ const struct wpabuf *msg)
+{
+ struct wps_parse_attr attr;
+
+ wpa_printf(MSG_DEBUG, "WPS: Received WSC_Done");
+
+ if (wps->state != RECV_DONE &&
+ (!wps->wps->wps_upnp || !wps->ext_reg)) {
+ wpa_printf(MSG_DEBUG, "WPS: Unexpected state (%d) for "
+ "receiving WSC_Done", wps->state);
+ return WPS_FAILURE;
+ }
+
+ if (wps_parse_msg(msg, &attr) < 0)
+ return WPS_FAILURE;
+
+ if (attr.msg_type == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS: No Message Type attribute");
+ return WPS_FAILURE;
+ }
+
+ if (*attr.msg_type != WPS_WSC_DONE) {
+ wpa_printf(MSG_DEBUG, "WPS: Invalid Message Type %d",
+ *attr.msg_type);
+ return WPS_FAILURE;
+ }
+
+#ifdef CONFIG_WPS_UPNP
+ if (wps->wps->wps_upnp && wps->ext_reg) {
+ wpa_printf(MSG_DEBUG, "WPS: Negotiation using external "
+ "Registrar completed successfully");
+ wps_device_store(wps->wps->registrar, &wps->peer_dev,
+ wps->uuid_e);
+ return WPS_DONE;
+ }
+#endif /* CONFIG_WPS_UPNP */
+
+ if (attr.registrar_nonce == NULL ||
+ os_memcmp(wps->nonce_r, attr.registrar_nonce, WPS_NONCE_LEN != 0))
+ {
+ wpa_printf(MSG_DEBUG, "WPS: Mismatch in registrar nonce");
+ return WPS_FAILURE;
+ }
+
+ if (attr.enrollee_nonce == NULL ||
+ os_memcmp(wps->nonce_e, attr.enrollee_nonce, WPS_NONCE_LEN != 0)) {
+ wpa_printf(MSG_DEBUG, "WPS: Mismatch in enrollee nonce");
+ return WPS_FAILURE;
+ }
+
+ wpa_printf(MSG_DEBUG, "WPS: Negotiation completed successfully");
+ wps_device_store(wps->wps->registrar, &wps->peer_dev,
+ wps->uuid_e);
+
+ if (wps->wps->wps_state == WPS_STATE_NOT_CONFIGURED && wps->new_psk &&
+ wps->wps->ap && !wps->wps->registrar->disable_auto_conf) {
+ struct wps_credential cred;
+
+ wpa_printf(MSG_DEBUG, "WPS: Moving to Configured state based "
+ "on first Enrollee connection");
+
+ os_memset(&cred, 0, sizeof(cred));
+ os_memcpy(cred.ssid, wps->wps->ssid, wps->wps->ssid_len);
+ cred.ssid_len = wps->wps->ssid_len;
+ cred.auth_type = WPS_AUTH_WPAPSK | WPS_AUTH_WPA2PSK;
+ cred.encr_type = WPS_ENCR_TKIP | WPS_ENCR_AES;
+ os_memcpy(cred.key, wps->new_psk, wps->new_psk_len);
+ cred.key_len = wps->new_psk_len;
+
+ wps->wps->wps_state = WPS_STATE_CONFIGURED;
+ wpa_hexdump_ascii_key(MSG_DEBUG,
+ "WPS: Generated random passphrase",
+ wps->new_psk, wps->new_psk_len);
+ if (wps->wps->cred_cb)
+ wps->wps->cred_cb(wps->wps->cb_ctx, &cred);
+
+ os_free(wps->new_psk);
+ wps->new_psk = NULL;
+ }
+
+ if (!wps->wps->ap && !wps->er)
+ wps_sta_cred_cb(wps);
+
+ if (wps->new_psk) {
+ if (wps_cb_new_psk(wps->wps->registrar, wps->mac_addr_e,
+ wps->new_psk, wps->new_psk_len)) {
+ wpa_printf(MSG_DEBUG, "WPS: Failed to configure the "
+ "new PSK");
+ }
+ os_free(wps->new_psk);
+ wps->new_psk = NULL;
+ }
+
+ wps_cb_reg_success(wps->wps->registrar, wps->mac_addr_e, wps->uuid_e);
+
+ if (wps->pbc) {
+ wps_registrar_remove_pbc_session(wps->wps->registrar,
+ wps->uuid_e);
+ wps_registrar_pbc_completed(wps->wps->registrar);
+ } else {
+ wps_registrar_pin_completed(wps->wps->registrar);
+ }
+ /* TODO: maintain AuthorizedMACs somewhere separately for each ER and
+ * merge them into APs own list.. */
+
+ wps_success_event(wps->wps);
+
+ return WPS_DONE;
+}
+
+
+enum wps_process_res wps_registrar_process_msg(struct wps_data *wps,
+ enum wsc_op_code op_code,
+ const struct wpabuf *msg)
+{
+ enum wps_process_res ret;
+
+ wpa_printf(MSG_DEBUG, "WPS: Processing received message (len=%lu "
+ "op_code=%d)",
+ (unsigned long) wpabuf_len(msg), op_code);
+
+#ifdef CONFIG_WPS_UPNP
+ if (wps->wps->wps_upnp && op_code == WSC_MSG && wps->ext_reg == 1) {
+ struct wps_parse_attr attr;
+ if (wps_parse_msg(msg, &attr) == 0 && attr.msg_type &&
+ *attr.msg_type == WPS_M3)
+ wps->ext_reg = 2; /* past M2/M2D phase */
+ }
+ if (wps->ext_reg > 1)
+ 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_NACK))
+ {
+ struct wps_parse_attr attr;
+ int type;
+ if (wps_parse_msg(msg, &attr) < 0 || attr.msg_type == NULL)
+ type = -1;
+ else
+ type = *attr.msg_type;
+ wpa_printf(MSG_DEBUG, "WPS: Sending received message (type %d)"
+ " to external Registrar for processing", type);
+ upnp_wps_device_send_wlan_event(wps->wps->wps_upnp,
+ wps->mac_addr_e,
+ UPNP_WPS_WLANEVENT_TYPE_EAP,
+ msg);
+ if (op_code == WSC_MSG)
+ return WPS_PENDING;
+ } else if (wps->wps->wps_upnp && wps->ext_reg && op_code == WSC_MSG) {
+ wpa_printf(MSG_DEBUG, "WPS: Skip internal processing - using "
+ "external Registrar");
+ return WPS_CONTINUE;
+ }
+#endif /* CONFIG_WPS_UPNP */
+
+ switch (op_code) {
+ case WSC_MSG:
+ return wps_process_wsc_msg(wps, msg);
+ case WSC_ACK:
+ if (wps_validate_wsc_ack(msg) < 0)
+ return WPS_FAILURE;
+ return wps_process_wsc_ack(wps, msg);
+ case WSC_NACK:
+ if (wps_validate_wsc_nack(msg) < 0)
+ return WPS_FAILURE;
+ return wps_process_wsc_nack(wps, msg);
+ case WSC_Done:
+ if (wps_validate_wsc_done(msg) < 0)
+ return WPS_FAILURE;
+ ret = wps_process_wsc_done(wps, msg);
+ if (ret == WPS_FAILURE) {
+ wps->state = SEND_WSC_NACK;
+ wps_fail_event(wps->wps, WPS_WSC_DONE,
+ wps->config_error,
+ wps->error_indication);
+ }
+ return ret;
+ default:
+ wpa_printf(MSG_DEBUG, "WPS: Unsupported op_code %d", op_code);
+ return WPS_FAILURE;
+ }
+}
+
+
+int wps_registrar_update_ie(struct wps_registrar *reg)
+{
+ return wps_set_ie(reg);
+}
+
+
+static void wps_registrar_set_selected_timeout(void *eloop_ctx,
+ void *timeout_ctx)
+{
+ struct wps_registrar *reg = eloop_ctx;
+
+ wpa_printf(MSG_DEBUG, "WPS: Selected Registrar timeout - "
+ "unselect internal Registrar");
+ reg->selected_registrar = 0;
+ reg->pbc = 0;
+ wps_registrar_selected_registrar_changed(reg);
+}
+
+
+#ifdef CONFIG_WPS_UPNP
+static void wps_registrar_sel_reg_add(struct wps_registrar *reg,
+ struct subscription *s)
+{
+ int i, j;
+ wpa_printf(MSG_DEBUG, "WPS: External Registrar selected (dev_pw_id=%d "
+ "config_methods=0x%x)",
+ s->dev_password_id, s->config_methods);
+ reg->sel_reg_union = 1;
+ if (reg->sel_reg_dev_password_id_override != DEV_PW_PUSHBUTTON)
+ reg->sel_reg_dev_password_id_override = s->dev_password_id;
+ if (reg->sel_reg_config_methods_override == -1)
+ reg->sel_reg_config_methods_override = 0;
+ reg->sel_reg_config_methods_override |= s->config_methods;
+ for (i = 0; i < WPS_MAX_AUTHORIZED_MACS; i++)
+ if (is_zero_ether_addr(reg->authorized_macs_union[i]))
+ break;
+ for (j = 0; i < WPS_MAX_AUTHORIZED_MACS && j < WPS_MAX_AUTHORIZED_MACS;
+ j++) {
+ if (is_zero_ether_addr(s->authorized_macs[j]))
+ break;
+ wpa_printf(MSG_DEBUG, "WPS: Add authorized MAC into union: "
+ MACSTR, MAC2STR(s->authorized_macs[j]));
+ os_memcpy(reg->authorized_macs_union[i],
+ s->authorized_macs[j], ETH_ALEN);
+ i++;
+ }
+ wpa_hexdump(MSG_DEBUG, "WPS: Authorized MACs union",
+ (u8 *) reg->authorized_macs_union,
+ sizeof(reg->authorized_macs_union));
+}
+#endif /* CONFIG_WPS_UPNP */
+
+
+static void wps_registrar_sel_reg_union(struct wps_registrar *reg)
+{
+#ifdef CONFIG_WPS_UPNP
+ struct subscription *s;
+
+ if (reg->wps->wps_upnp == NULL)
+ return;
+
+ dl_list_for_each(s, ®->wps->wps_upnp->subscriptions,
+ struct subscription, list) {
+ struct subscr_addr *sa;
+ sa = dl_list_first(&s->addr_list, struct subscr_addr, list);
+ if (sa) {
+ wpa_printf(MSG_DEBUG, "WPS: External Registrar %s:%d",
+ inet_ntoa(sa->saddr.sin_addr),
+ ntohs(sa->saddr.sin_port));
+ }
+ if (s->selected_registrar)
+ wps_registrar_sel_reg_add(reg, s);
+ else
+ wpa_printf(MSG_DEBUG, "WPS: External Registrar not "
+ "selected");
+ }
+#endif /* CONFIG_WPS_UPNP */
+}
+
+
+/**
+ * wps_registrar_selected_registrar_changed - SetSelectedRegistrar change
+ * @reg: Registrar data from wps_registrar_init()
+ *
+ * This function is called when selected registrar state changes, e.g., when an
+ * AP receives a SetSelectedRegistrar UPnP message.
+ */
+void wps_registrar_selected_registrar_changed(struct wps_registrar *reg)
+{
+ wpa_printf(MSG_DEBUG, "WPS: Selected registrar information changed");
+
+ reg->sel_reg_union = reg->selected_registrar;
+ reg->sel_reg_dev_password_id_override = -1;
+ reg->sel_reg_config_methods_override = -1;
+ os_memcpy(reg->authorized_macs_union, reg->authorized_macs,
+ WPS_MAX_AUTHORIZED_MACS * ETH_ALEN);
+ wpa_hexdump(MSG_DEBUG, "WPS: Authorized MACs union (start with own)",
+ (u8 *) reg->authorized_macs_union,
+ sizeof(reg->authorized_macs_union));
+ if (reg->selected_registrar) {
+ u16 methods;
+
+ methods = reg->wps->config_methods & ~WPS_CONFIG_PUSHBUTTON;
+#ifdef CONFIG_WPS2
+ methods &= ~(WPS_CONFIG_VIRT_PUSHBUTTON |
+ WPS_CONFIG_PHY_PUSHBUTTON);
+#endif /* CONFIG_WPS2 */
+ if (reg->pbc) {
+ reg->sel_reg_dev_password_id_override =
+ DEV_PW_PUSHBUTTON;
+ wps_set_pushbutton(&methods, reg->wps->config_methods);
+ }
+ wpa_printf(MSG_DEBUG, "WPS: Internal Registrar selected "
+ "(pbc=%d)", reg->pbc);
+ reg->sel_reg_config_methods_override = methods;
+ } else
+ wpa_printf(MSG_DEBUG, "WPS: Internal Registrar not selected");
+
+ wps_registrar_sel_reg_union(reg);
+
+ wps_set_ie(reg);
+ wps_cb_set_sel_reg(reg);
+}
+
+
+int wps_registrar_get_info(struct wps_registrar *reg, const u8 *addr,
+ char *buf, size_t buflen)
+{
+ struct wps_registrar_device *d;
+ int len = 0, ret;
+ char uuid[40];
+ char devtype[WPS_DEV_TYPE_BUFSIZE];
+
+ d = wps_device_get(reg, addr);
+ if (d == NULL)
+ return 0;
+ if (uuid_bin2str(d->uuid, uuid, sizeof(uuid)))
+ return 0;
+
+ ret = os_snprintf(buf + len, buflen - len,
+ "wpsUuid=%s\n"
+ "wpsPrimaryDeviceType=%s\n"
+ "wpsDeviceName=%s\n"
+ "wpsManufacturer=%s\n"
+ "wpsModelName=%s\n"
+ "wpsModelNumber=%s\n"
+ "wpsSerialNumber=%s\n",
+ uuid,
+ wps_dev_type_bin2str(d->dev.pri_dev_type, devtype,
+ sizeof(devtype)),
+ d->dev.device_name ? d->dev.device_name : "",
+ d->dev.manufacturer ? d->dev.manufacturer : "",
+ d->dev.model_name ? d->dev.model_name : "",
+ d->dev.model_number ? d->dev.model_number : "",
+ d->dev.serial_number ? d->dev.serial_number : "");
+ if (ret < 0 || (size_t) ret >= buflen - len)
+ return len;
+ len += ret;
+
+ return len;
+}
+
+
+int wps_registrar_config_ap(struct wps_registrar *reg,
+ struct wps_credential *cred)
+{
+#ifdef CONFIG_WPS2
+ printf("encr_type=0x%x\n", cred->encr_type);
+ if (!(cred->encr_type & (WPS_ENCR_NONE | WPS_ENCR_TKIP |
+ WPS_ENCR_AES))) {
+ if (cred->encr_type & WPS_ENCR_WEP) {
+ wpa_printf(MSG_INFO, "WPS: Reject new AP settings "
+ "due to WEP configuration");
+ return -1;
+ }
+
+ wpa_printf(MSG_INFO, "WPS: Reject new AP settings due to "
+ "invalid encr_type 0x%x", cred->encr_type);
+ return -1;
+ }
+
+ if ((cred->encr_type & (WPS_ENCR_TKIP | WPS_ENCR_AES)) ==
+ WPS_ENCR_TKIP) {
+ wpa_printf(MSG_DEBUG, "WPS: Upgrade encr_type TKIP -> "
+ "TKIP+AES");
+ cred->encr_type |= WPS_ENCR_AES;
+ }
+
+ if ((cred->auth_type & (WPS_AUTH_WPAPSK | WPS_AUTH_WPA2PSK)) ==
+ WPS_AUTH_WPAPSK) {
+ wpa_printf(MSG_DEBUG, "WPS: Upgrade auth_type WPAPSK -> "
+ "WPAPSK+WPA2PSK");
+ cred->auth_type |= WPS_AUTH_WPA2PSK;
+ }
+#endif /* CONFIG_WPS2 */
+
+ if (reg->wps->cred_cb)
+ return reg->wps->cred_cb(reg->wps->cb_ctx, cred);
+
+ return -1;
+}
diff --git a/src/wps/wps_ufd.c b/src/wps/wps_ufd.c
new file mode 100644
index 0000000..1a911e1
--- /dev/null
+++ b/src/wps/wps_ufd.c
@@ -0,0 +1,235 @@
+/*
+ * UFD routines for Wi-Fi Protected Setup
+ * Copyright (c) 2009, Masashi Honma <honma@ictec.co.jp>
+ *
+ * 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.
+ */
+
+#include "includes.h"
+#include "common.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <dirent.h>
+
+#include "wps/wps.h"
+#include "wps/wps_i.h"
+
+#ifdef CONFIG_NATIVE_WINDOWS
+#define UFD_DIR1 "%s\\SMRTNTKY"
+#define UFD_DIR2 UFD_DIR1 "\\WFAWSC"
+#define UFD_FILE UFD_DIR2 "\\%s"
+#else /* CONFIG_NATIVE_WINDOWS */
+#define UFD_DIR1 "%s/SMRTNTKY"
+#define UFD_DIR2 UFD_DIR1 "/WFAWSC"
+#define UFD_FILE UFD_DIR2 "/%s"
+#endif /* CONFIG_NATIVE_WINDOWS */
+
+
+struct wps_ufd_data {
+ int ufd_fd;
+};
+
+
+static int dev_pwd_e_file_filter(const struct dirent *entry)
+{
+ unsigned int prefix;
+ char ext[5];
+
+ if (sscanf(entry->d_name, "%8x.%4s", &prefix, ext) != 2)
+ return 0;
+ if (prefix == 0)
+ return 0;
+ if (os_strcasecmp(ext, "WFA") != 0)
+ return 0;
+
+ return 1;
+}
+
+
+static int wps_get_dev_pwd_e_file_name(char *path, char *file_name)
+{
+ struct dirent **namelist;
+ int i, file_num;
+
+ file_num = scandir(path, &namelist, &dev_pwd_e_file_filter,
+ alphasort);
+ if (file_num < 0) {
+ wpa_printf(MSG_ERROR, "WPS: OOB file not found: %d (%s)",
+ errno, strerror(errno));
+ return -1;
+ }
+ if (file_num == 0) {
+ wpa_printf(MSG_ERROR, "WPS: OOB file not found");
+ os_free(namelist);
+ return -1;
+ }
+ os_strlcpy(file_name, namelist[0]->d_name, 13);
+ for (i = 0; i < file_num; i++)
+ os_free(namelist[i]);
+ os_free(namelist);
+ return 0;
+}
+
+
+static int get_file_name(struct wps_context *wps, int registrar,
+ const char *path, char *file_name)
+{
+ switch (wps->oob_conf.oob_method) {
+ case OOB_METHOD_CRED:
+ os_snprintf(file_name, 13, "00000000.WSC");
+ break;
+ case OOB_METHOD_DEV_PWD_E:
+ if (registrar) {
+ char temp[128];
+ os_snprintf(temp, sizeof(temp), UFD_DIR2, path);
+ if (wps_get_dev_pwd_e_file_name(temp, file_name) < 0)
+ return -1;
+ } else {
+ u8 *mac_addr = wps->dev.mac_addr;
+
+ os_snprintf(file_name, 13, "%02X%02X%02X%02X.WFA",
+ mac_addr[2], mac_addr[3], mac_addr[4],
+ mac_addr[5]);
+ }
+ break;
+ case OOB_METHOD_DEV_PWD_R:
+ os_snprintf(file_name, 13, "00000000.WFA");
+ break;
+ default:
+ wpa_printf(MSG_ERROR, "WPS: Invalid USBA OOB method");
+ return -1;
+ }
+ return 0;
+}
+
+
+static int ufd_mkdir(const char *path)
+{
+ if (mkdir(path, S_IRWXU) < 0 && errno != EEXIST) {
+ wpa_printf(MSG_ERROR, "WPS (UFD): Failed to create directory "
+ "'%s': %d (%s)", path, errno, strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+
+static void * init_ufd(struct wps_context *wps,
+ struct oob_device_data *oob_dev, int registrar)
+{
+ int write_f;
+ char temp[128];
+ char *path = oob_dev->device_path;
+ char filename[13];
+ struct wps_ufd_data *data;
+ int ufd_fd;
+
+ if (path == NULL)
+ return NULL;
+
+ write_f = wps->oob_conf.oob_method == OOB_METHOD_DEV_PWD_E ?
+ !registrar : registrar;
+
+ if (get_file_name(wps, registrar, path, filename) < 0) {
+ wpa_printf(MSG_ERROR, "WPS (UFD): Failed to get file name");
+ return NULL;
+ }
+
+ if (write_f) {
+ os_snprintf(temp, sizeof(temp), UFD_DIR1, path);
+ if (ufd_mkdir(temp))
+ return NULL;
+ os_snprintf(temp, sizeof(temp), UFD_DIR2, path);
+ if (ufd_mkdir(temp))
+ return NULL;
+ }
+
+ os_snprintf(temp, sizeof(temp), UFD_FILE, path, filename);
+ if (write_f)
+ ufd_fd = open(temp, O_WRONLY | O_CREAT | O_TRUNC,
+ S_IRUSR | S_IWUSR);
+ else
+ ufd_fd = open(temp, O_RDONLY);
+ if (ufd_fd < 0) {
+ wpa_printf(MSG_ERROR, "WPS (UFD): Failed to open %s: %s",
+ temp, strerror(errno));
+ return NULL;
+ }
+
+ data = os_zalloc(sizeof(*data));
+ if (data == NULL)
+ return NULL;
+ data->ufd_fd = ufd_fd;
+ return data;
+}
+
+
+static struct wpabuf * read_ufd(void *priv)
+{
+ struct wps_ufd_data *data = priv;
+ struct wpabuf *buf;
+ struct stat s;
+ size_t file_size;
+
+ if (fstat(data->ufd_fd, &s) < 0) {
+ wpa_printf(MSG_ERROR, "WPS (UFD): Failed to get file size");
+ return NULL;
+ }
+
+ file_size = s.st_size;
+ buf = wpabuf_alloc(file_size);
+ if (buf == NULL) {
+ wpa_printf(MSG_ERROR, "WPS (UFD): Failed to alloc read "
+ "buffer");
+ return NULL;
+ }
+
+ if (read(data->ufd_fd, wpabuf_mhead(buf), file_size) !=
+ (int) file_size) {
+ wpabuf_free(buf);
+ wpa_printf(MSG_ERROR, "WPS (UFD): Failed to read");
+ return NULL;
+ }
+ wpabuf_put(buf, file_size);
+ return buf;
+}
+
+
+static int write_ufd(void *priv, struct wpabuf *buf)
+{
+ struct wps_ufd_data *data = priv;
+
+ if (write(data->ufd_fd, wpabuf_mhead(buf), wpabuf_len(buf)) !=
+ (int) wpabuf_len(buf)) {
+ wpa_printf(MSG_ERROR, "WPS (UFD): Failed to write");
+ return -1;
+ }
+ return 0;
+}
+
+
+static void deinit_ufd(void *priv)
+{
+ struct wps_ufd_data *data = priv;
+ close(data->ufd_fd);
+ os_free(data);
+}
+
+
+struct oob_device_data oob_ufd_device_data = {
+ .device_name = NULL,
+ .device_path = NULL,
+ .init_func = init_ufd,
+ .read_func = read_ufd,
+ .write_func = write_ufd,
+ .deinit_func = deinit_ufd,
+};
diff --git a/src/wps/wps_upnp.c b/src/wps/wps_upnp.c
new file mode 100644
index 0000000..763dcaf
--- /dev/null
+++ b/src/wps/wps_upnp.c
@@ -0,0 +1,1210 @@
+/*
+ * UPnP WPS Device
+ * Copyright (c) 2000-2003 Intel Corporation
+ * Copyright (c) 2006-2007 Sony Corporation
+ * Copyright (c) 2008-2009 Atheros Communications
+ * Copyright (c) 2009-2010, Jouni Malinen <j@w1.fi>
+ *
+ * See below for more details on licensing and code history.
+ */
+
+/*
+ * This has been greatly stripped down from the original file
+ * (upnp_wps_device.c) by Ted Merrill, Atheros Communications
+ * in order to eliminate use of the bulky libupnp library etc.
+ *
+ * History:
+ * upnp_wps_device.c is/was a shim layer between wps_opt_upnp.c and
+ * the libupnp library.
+ * The layering (by Sony) was well done; only a very minor modification
+ * to API of upnp_wps_device.c was required.
+ * libupnp was found to be undesirable because:
+ * -- It consumed too much code and data space
+ * -- It uses multiple threads, making debugging more difficult
+ * and possibly reducing reliability.
+ * -- It uses static variables and only supports one instance.
+ * The shim and libupnp are here replaced by special code written
+ * specifically for the needs of hostapd.
+ * Various shortcuts can and are taken to keep the code size small.
+ * Generally, execution time is not as crucial.
+ *
+ * BUGS:
+ * -- UPnP requires that we be able to resolve domain names.
+ * While uncommon, if we have to do it then it will stall the entire
+ * hostapd program, which is bad.
+ * This is because we use the standard linux getaddrinfo() function
+ * which is syncronous.
+ * An asyncronous solution would be to use the free "ares" library.
+ * -- Does not have a robust output buffering scheme. Uses a single
+ * fixed size output buffer per TCP/HTTP connection, with possible (although
+ * unlikely) possibility of overflow and likely excessive use of RAM.
+ * A better solution would be to write the HTTP output as a buffered stream,
+ * using chunking: (handle header specially, then) generate data with
+ * a printf-like function into a buffer, catching buffer full condition,
+ * then send it out surrounded by http chunking.
+ * -- There is some code that could be separated out into the common
+ * library to be shared with wpa_supplicant.
+ * -- 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.
+ * -- The http error code generation is pretty bogus, hopefully noone cares.
+ *
+ * Author: Ted Merrill, Atheros Communications, based upon earlier work
+ * as explained above and below.
+ *
+ * Copyright:
+ * Copyright 2008 Atheros Communications.
+ *
+ * The original header (of upnp_wps_device.c) reads:
+ *
+ * Copyright (c) 2006-2007 Sony Corporation. All Rights Reserved.
+ *
+ * File Name: upnp_wps_device.c
+ * Description: EAP-WPS UPnP device source
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Sony Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Portions from Intel libupnp files, e.g. genlib/net/http/httpreadwrite.c
+ * typical header:
+ *
+ * Copyright (c) 2000-2003 Intel Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * * Neither name of Intel Corporation nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/*
+ * Overview of WPS over UPnP:
+ *
+ * UPnP is a protocol that allows devices to discover each other and control
+ * each other. In UPnP terminology, a device is either a "device" (a server
+ * that provides information about itself and allows itself to be controlled)
+ * or a "control point" (a client that controls "devices") or possibly both.
+ * This file implements a UPnP "device".
+ *
+ * For us, we use mostly basic UPnP discovery, but the control part of interest
+ * is WPS carried via UPnP messages. There is quite a bit of basic UPnP
+ * discovery to do before we can get to WPS, however.
+ *
+ * UPnP discovery begins with "devices" send out multicast UDP packets to a
+ * certain fixed multicast IP address and port, and "control points" sending
+ * out other such UDP packets.
+ *
+ * The packets sent by devices are NOTIFY packets (not to be confused with TCP
+ * NOTIFY packets that are used later) and those sent by control points are
+ * M-SEARCH packets. These packets contain a simple HTTP style header. The
+ * packets are sent redundantly to get around packet loss. Devices respond to
+ * M-SEARCH packets with HTTP-like UDP packets containing HTTP/1.1 200 OK
+ * messages, which give similar information as the UDP NOTIFY packets.
+ *
+ * The above UDP packets advertise the (arbitrary) TCP ports that the
+ * respective parties will listen to. The control point can then do a HTTP
+ * SUBSCRIBE (something like an HTTP PUT) after which the device can do a
+ * separate HTTP NOTIFY (also like an HTTP PUT) to do event messaging.
+ *
+ * The control point will also do HTTP GET of the "device file" listed in the
+ * original UDP information from the device (see UPNP_WPS_DEVICE_XML_FILE
+ * data), and based on this will do additional GETs... HTTP POSTs are done to
+ * cause an action.
+ *
+ * Beyond some basic information in HTTP headers, additional information is in
+ * the HTTP bodies, in a format set by the SOAP and XML standards, a markup
+ * language related to HTML used for web pages. This language is intended to
+ * provide the ultimate in self-documentation by providing a universal
+ * namespace based on pseudo-URLs called URIs. Note that although a URI looks
+ * like a URL (a web address), they are never accessed as such but are used
+ * only as identifiers.
+ *
+ * The POST of a GetDeviceInfo gets information similar to what might be
+ * obtained from a probe request or response on Wi-Fi. WPS messages M1-M8
+ * are passed via a POST of a PutMessage; the M1-M8 WPS messages are converted
+ * to a bin64 ascii representation for encapsulation. When proxying messages,
+ * WLANEvent and PutWLANResponse are used.
+ *
+ * This of course glosses over a lot of details.
+ */
+
+#include "includes.h"
+
+#include <assert.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <sys/ioctl.h>
+
+#include "common.h"
+#include "uuid.h"
+#include "base64.h"
+#include "wps.h"
+#include "wps_i.h"
+#include "wps_upnp.h"
+#include "wps_upnp_i.h"
+
+
+/*
+ * UPnP allows a client ("control point") to send a server like us ("device")
+ * a domain name for registration, and we are supposed to resolve it. This is
+ * bad because, using the standard Linux library, we will stall the entire
+ * hostapd waiting for resolution.
+ *
+ * The "correct" solution would be to use an event driven library for domain
+ * name resolution such as "ares". However, this would increase code size
+ * further. Since it is unlikely that we'll actually see such domain names, we
+ * can just refuse to accept them.
+ */
+#define NO_DOMAIN_NAME_RESOLUTION 1 /* 1 to allow only dotted ip addresses */
+
+
+/*
+ * UPnP does not scale well. If we were in a room with thousands of control
+ * points then potentially we could be expected to handle subscriptions for
+ * each of them, which would exhaust our memory. So we must set a limit. In
+ * practice we are unlikely to see more than one or two.
+ */
+#define MAX_SUBSCRIPTIONS 4 /* how many subscribing clients we handle */
+#define MAX_ADDR_PER_SUBSCRIPTION 8
+
+/* Maximum number of Probe Request events per second */
+#define MAX_EVENTS_PER_SEC 5
+
+
+static struct upnp_wps_device_sm *shared_upnp_device = NULL;
+
+
+/* Write the current date/time per RFC */
+void format_date(struct wpabuf *buf)
+{
+ const char *weekday_str = "Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat";
+ const char *month_str = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0"
+ "Jul\0Aug\0Sep\0Oct\0Nov\0Dec";
+ struct tm *date;
+ time_t t;
+
+ t = time(NULL);
+ date = gmtime(&t);
+ wpabuf_printf(buf, "%s, %02d %s %d %02d:%02d:%02d GMT",
+ &weekday_str[date->tm_wday * 4], date->tm_mday,
+ &month_str[date->tm_mon * 4], date->tm_year + 1900,
+ date->tm_hour, date->tm_min, date->tm_sec);
+}
+
+
+/***************************************************************************
+ * UUIDs (unique identifiers)
+ *
+ * These are supposed to be unique in all the world.
+ * Sometimes permanent ones are used, sometimes temporary ones
+ * based on random numbers... there are different rules for valid content
+ * of different types.
+ * Each uuid is 16 bytes long.
+ **************************************************************************/
+
+/* uuid_make -- construct a random UUID
+ * The UPnP documents don't seem to offer any guidelines as to which method to
+ * use for constructing UUIDs for subscriptions. Presumably any method from
+ * rfc4122 is good enough; I've chosen random number method.
+ */
+static void uuid_make(u8 uuid[UUID_LEN])
+{
+ os_get_random(uuid, UUID_LEN);
+
+ /* Replace certain bits as specified in rfc4122 or X.667 */
+ uuid[6] &= 0x0f; uuid[6] |= (4 << 4); /* version 4 == random gen */
+ uuid[8] &= 0x3f; uuid[8] |= 0x80;
+}
+
+
+/*
+ * Subscriber address handling.
+ * Since a subscriber may have an arbitrary number of addresses, we have to
+ * add a bunch of code to handle them.
+ *
+ * Addresses are passed in text, and MAY be domain names instead of the (usual
+ * and expected) dotted IP addresses. Resolving domain names consumes a lot of
+ * resources. Worse, we are currently using the standard Linux getaddrinfo()
+ * which will block the entire program until complete or timeout! The proper
+ * solution would be to use the "ares" library or similar with more state
+ * machine steps etc. or just disable domain name resolution by setting
+ * NO_DOMAIN_NAME_RESOLUTION to 1 at top of this file.
+ */
+
+/* subscr_addr_delete -- delete single unlinked subscriber address
+ * (be sure to unlink first if need be)
+ */
+void subscr_addr_delete(struct subscr_addr *a)
+{
+ /*
+ * Note: do NOT free domain_and_port or path because they point to
+ * memory within the allocation of "a".
+ */
+ os_free(a);
+}
+
+
+/* subscr_addr_free_all -- unlink and delete list of subscriber addresses. */
+static void subscr_addr_free_all(struct subscription *s)
+{
+ struct subscr_addr *a, *tmp;
+ dl_list_for_each_safe(a, tmp, &s->addr_list, struct subscr_addr, list)
+ {
+ dl_list_del(&a->list);
+ subscr_addr_delete(a);
+ }
+}
+
+
+/* subscr_addr_add_url -- add address(es) for one url to subscription */
+static void subscr_addr_add_url(struct subscription *s, const char *url,
+ size_t url_len)
+{
+ int alloc_len;
+ char *scratch_mem = NULL;
+ char *mem;
+ char *domain_and_port;
+ char *delim;
+ char *path;
+ char *domain;
+ int port = 80; /* port to send to (default is port 80) */
+ struct addrinfo hints;
+ struct addrinfo *result = NULL;
+ struct addrinfo *rp;
+ int rerr;
+
+ /* url MUST begin with http: */
+ if (url_len < 7 || os_strncasecmp(url, "http://", 7))
+ goto fail;
+ url += 7;
+ url_len -= 7;
+
+ /* allocate memory for the extra stuff we need */
+ alloc_len = 2 * (url_len + 1);
+ scratch_mem = os_zalloc(alloc_len);
+ if (scratch_mem == NULL)
+ goto fail;
+ mem = scratch_mem;
+ os_strncpy(mem, url, url_len);
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Adding URL '%s'", mem);
+ domain_and_port = mem;
+ mem += 1 + os_strlen(mem);
+ delim = os_strchr(domain_and_port, '/');
+ if (delim) {
+ *delim++ = 0; /* null terminate domain and port */
+ path = delim;
+ } else {
+ path = domain_and_port + os_strlen(domain_and_port);
+ }
+ domain = mem;
+ strcpy(domain, domain_and_port);
+ delim = os_strchr(domain, ':');
+ if (delim) {
+ *delim++ = 0; /* null terminate domain */
+ if (isdigit(*delim))
+ port = atol(delim);
+ }
+
+ /*
+ * getaddrinfo does the right thing with dotted decimal notations, or
+ * will resolve domain names. Resolving domain names will unfortunately
+ * hang the entire program until it is resolved or it times out
+ * internal to getaddrinfo; fortunately we think that the use of actual
+ * domain names (vs. dotted decimal notations) should be uncommon.
+ */
+ os_memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_INET; /* IPv4 */
+ hints.ai_socktype = SOCK_STREAM;
+#if NO_DOMAIN_NAME_RESOLUTION
+ /* Suppress domain name resolutions that would halt
+ * the program for periods of time
+ */
+ hints.ai_flags = AI_NUMERICHOST;
+#else
+ /* Allow domain name resolution. */
+ hints.ai_flags = 0;
+#endif
+ hints.ai_protocol = 0; /* Any protocol? */
+ rerr = getaddrinfo(domain, NULL /* fill in port ourselves */,
+ &hints, &result);
+ if (rerr) {
+ wpa_printf(MSG_INFO, "WPS UPnP: Resolve error %d (%s) on: %s",
+ rerr, gai_strerror(rerr), domain);
+ goto fail;
+ }
+ for (rp = result; rp; rp = rp->ai_next) {
+ struct subscr_addr *a;
+
+ /* Limit no. of address to avoid denial of service attack */
+ if (dl_list_len(&s->addr_list) >= MAX_ADDR_PER_SUBSCRIPTION) {
+ wpa_printf(MSG_INFO, "WPS UPnP: subscr_addr_add_url: "
+ "Ignoring excessive addresses");
+ break;
+ }
+
+ a = os_zalloc(sizeof(*a) + alloc_len);
+ if (a == NULL)
+ continue;
+ mem = (void *) (a + 1);
+ a->domain_and_port = mem;
+ strcpy(mem, domain_and_port);
+ mem += 1 + strlen(mem);
+ a->path = mem;
+ if (path[0] != '/')
+ *mem++ = '/';
+ strcpy(mem, path);
+ mem += 1 + os_strlen(mem);
+ os_memcpy(&a->saddr, rp->ai_addr, sizeof(a->saddr));
+ a->saddr.sin_port = htons(port);
+
+ dl_list_add(&s->addr_list, &a->list);
+ }
+
+fail:
+ if (result)
+ freeaddrinfo(result);
+ os_free(scratch_mem);
+}
+
+
+/* subscr_addr_list_create -- create list from urls in string.
+ * Each url is enclosed by angle brackets.
+ */
+static void subscr_addr_list_create(struct subscription *s,
+ const char *url_list)
+{
+ const char *end;
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Parsing URL list '%s'", url_list);
+ for (;;) {
+ while (*url_list == ' ' || *url_list == '\t')
+ url_list++;
+ if (*url_list != '<')
+ break;
+ url_list++;
+ end = os_strchr(url_list, '>');
+ if (end == NULL)
+ break;
+ subscr_addr_add_url(s, url_list, end - url_list);
+ url_list = end + 1;
+ }
+}
+
+
+int send_wpabuf(int fd, struct wpabuf *buf)
+{
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Send %lu byte message",
+ (unsigned long) wpabuf_len(buf));
+ errno = 0;
+ if (write(fd, wpabuf_head(buf), wpabuf_len(buf)) !=
+ (int) wpabuf_len(buf)) {
+ wpa_printf(MSG_ERROR, "WPS UPnP: Failed to send buffer: "
+ "errno=%d (%s)",
+ errno, strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static void wpabuf_put_property(struct wpabuf *buf, const char *name,
+ const char *value)
+{
+ wpabuf_put_str(buf, "<e:property>");
+ wpabuf_printf(buf, "<%s>", name);
+ if (value)
+ wpabuf_put_str(buf, value);
+ wpabuf_printf(buf, "</%s>", name);
+ wpabuf_put_str(buf, "</e:property>\n");
+}
+
+
+/**
+ * upnp_wps_device_send_event - Queue event messages for subscribers
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ *
+ * This function queues the last WLANEvent to be sent for all currently
+ * subscribed UPnP control points. sm->wlanevent must have been set with the
+ * encoded data before calling this function.
+ */
+static void upnp_wps_device_send_event(struct upnp_wps_device_sm *sm)
+{
+ /* Enqueue event message for all subscribers */
+ struct wpabuf *buf; /* holds event message */
+ int buf_size = 0;
+ struct subscription *s, *tmp;
+ /* Actually, utf-8 is the default, but it doesn't hurt to specify it */
+ const char *format_head =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">\n";
+ const char *format_tail = "</e:propertyset>\n";
+ struct os_time now;
+
+ if (dl_list_empty(&sm->subscriptions)) {
+ /* optimize */
+ return;
+ }
+
+ if (os_get_time(&now) == 0) {
+ if (now.sec != sm->last_event_sec) {
+ sm->last_event_sec = now.sec;
+ sm->num_events_in_sec = 1;
+ } else {
+ sm->num_events_in_sec++;
+ /*
+ * In theory, this should apply to all WLANEvent
+ * notifications, but EAP messages are of much higher
+ * priority and Probe Request notifications should not
+ * be allowed to drop EAP messages, so only throttle
+ * Probe Request notifications.
+ */
+ if (sm->num_events_in_sec > MAX_EVENTS_PER_SEC &&
+ sm->wlanevent_type ==
+ UPNP_WPS_WLANEVENT_TYPE_PROBE) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Throttle "
+ "event notifications (%u seen "
+ "during one second)",
+ sm->num_events_in_sec);
+ return;
+ }
+ }
+ }
+
+ /* Determine buffer size needed first */
+ buf_size += os_strlen(format_head);
+ buf_size += 50 + 2 * os_strlen("WLANEvent");
+ if (sm->wlanevent)
+ buf_size += os_strlen(sm->wlanevent);
+ buf_size += os_strlen(format_tail);
+
+ buf = wpabuf_alloc(buf_size);
+ if (buf == NULL)
+ return;
+ wpabuf_put_str(buf, format_head);
+ wpabuf_put_property(buf, "WLANEvent", sm->wlanevent);
+ wpabuf_put_str(buf, format_tail);
+
+ wpa_printf(MSG_MSGDUMP, "WPS UPnP: WLANEvent message:\n%s",
+ (char *) wpabuf_head(buf));
+
+ dl_list_for_each_safe(s, tmp, &sm->subscriptions, struct subscription,
+ list) {
+ event_add(s, buf,
+ sm->wlanevent_type == UPNP_WPS_WLANEVENT_TYPE_PROBE);
+ }
+
+ wpabuf_free(buf);
+}
+
+
+/*
+ * Event subscription (subscriber machines register with us to receive event
+ * messages).
+ * This is the result of an incoming HTTP over TCP SUBSCRIBE request.
+ */
+
+/* subscription_destroy -- destroy an unlinked subscription
+ * Be sure to unlink first if necessary.
+ */
+void subscription_destroy(struct subscription *s)
+{
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Destroy subscription %p", s);
+ subscr_addr_free_all(s);
+ event_delete_all(s);
+ upnp_er_remove_notification(s);
+ os_free(s);
+}
+
+
+/* subscription_list_age -- remove expired subscriptions */
+static void subscription_list_age(struct upnp_wps_device_sm *sm, time_t now)
+{
+ struct subscription *s, *tmp;
+ dl_list_for_each_safe(s, tmp, &sm->subscriptions,
+ struct subscription, list) {
+ if (s->timeout_time > now)
+ break;
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Removing aged subscription");
+ dl_list_del(&s->list);
+ subscription_destroy(s);
+ }
+}
+
+
+/* subscription_find -- return existing subscription matching uuid, if any
+ * returns NULL if not found
+ */
+struct subscription * subscription_find(struct upnp_wps_device_sm *sm,
+ const u8 uuid[UUID_LEN])
+{
+ struct subscription *s;
+ dl_list_for_each(s, &sm->subscriptions, struct subscription, list) {
+ if (os_memcmp(s->uuid, uuid, UUID_LEN) == 0)
+ return s; /* Found match */
+ }
+ return NULL;
+}
+
+
+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");
+ if (wps_build_version(msg) ||
+ wps_build_msg_type(msg, WPS_WSC_ACK)) {
+ wpabuf_free(msg);
+ return NULL;
+ }
+ /* 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);
+ wps_build_wfa_ext(msg, 0, NULL, 0);
+ return msg;
+}
+
+
+/* subscription_first_event -- send format/queue event that is automatically
+ * sent on a new subscription.
+ */
+static int subscription_first_event(struct subscription *s)
+{
+ /*
+ * Actually, utf-8 is the default, but it doesn't hurt to specify it.
+ *
+ * APStatus is apparently a bit set,
+ * 0x1 = configuration change (but is always set?)
+ * 0x10 = ap is locked
+ *
+ * Per UPnP spec, we send out the last value of each variable, even
+ * for WLANEvent, whatever it was.
+ */
+ char *wlan_event;
+ struct wpabuf *buf;
+ int ap_status = 1; /* TODO: add 0x10 if access point is locked */
+ const char *head =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">\n";
+ const char *tail = "</e:propertyset>\n";
+ char txt[10];
+ int ret;
+
+ 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 "
+ "initial event message");
+ wlan_event = "";
+ }
+ buf = wpabuf_alloc(500 + os_strlen(wlan_event));
+ if (buf == NULL)
+ return -1;
+
+ wpabuf_put_str(buf, head);
+ wpabuf_put_property(buf, "STAStatus", "1");
+ os_snprintf(txt, sizeof(txt), "%d", ap_status);
+ wpabuf_put_property(buf, "APStatus", txt);
+ if (*wlan_event)
+ wpabuf_put_property(buf, "WLANEvent", wlan_event);
+ wpabuf_put_str(buf, tail);
+
+ ret = event_add(s, buf, 0);
+ if (ret) {
+ wpabuf_free(buf);
+ return ret;
+ }
+ wpabuf_free(buf);
+
+ return 0;
+}
+
+
+/**
+ * subscription_start - Remember a UPnP control point to send events to.
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * @callback_urls: Callback URLs
+ * Returns: %NULL on error, or pointer to new subscription structure.
+ */
+struct subscription * subscription_start(struct upnp_wps_device_sm *sm,
+ const char *callback_urls)
+{
+ struct subscription *s;
+ time_t now = time(NULL);
+ time_t expire = now + UPNP_SUBSCRIBE_SEC;
+
+ /* Get rid of expired subscriptions so we have room */
+ subscription_list_age(sm, now);
+
+ /* If too many subscriptions, remove oldest */
+ if (dl_list_len(&sm->subscriptions) >= MAX_SUBSCRIPTIONS) {
+ s = dl_list_first(&sm->subscriptions, struct subscription,
+ list);
+ wpa_printf(MSG_INFO, "WPS UPnP: Too many subscriptions, "
+ "trashing oldest");
+ dl_list_del(&s->list);
+ subscription_destroy(s);
+ }
+
+ s = os_zalloc(sizeof(*s));
+ if (s == NULL)
+ return NULL;
+ dl_list_init(&s->addr_list);
+ dl_list_init(&s->event_queue);
+
+ s->sm = sm;
+ s->timeout_time = expire;
+ uuid_make(s->uuid);
+ subscr_addr_list_create(s, callback_urls);
+ if (dl_list_empty(&s->addr_list)) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: No valid callback URLs in "
+ "'%s' - drop subscription", callback_urls);
+ subscription_destroy(s);
+ return NULL;
+ }
+
+ /* Add to end of list, since it has the highest expiration time */
+ dl_list_add_tail(&sm->subscriptions, &s->list);
+ /* Queue up immediate event message (our last event)
+ * as required by UPnP spec.
+ */
+ if (subscription_first_event(s)) {
+ wpa_printf(MSG_INFO, "WPS UPnP: Dropping subscriber due to "
+ "event backlog");
+ dl_list_del(&s->list);
+ subscription_destroy(s);
+ return NULL;
+ }
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Subscription %p started with %s",
+ s, callback_urls);
+ /* Schedule sending this */
+ event_send_all_later(sm);
+ return s;
+}
+
+
+/* subscription_renew -- find subscription and reset timeout */
+struct subscription * subscription_renew(struct upnp_wps_device_sm *sm,
+ const u8 uuid[UUID_LEN])
+{
+ time_t now = time(NULL);
+ time_t expire = now + UPNP_SUBSCRIBE_SEC;
+ struct subscription *s = subscription_find(sm, uuid);
+ if (s == NULL)
+ return NULL;
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Subscription renewed");
+ dl_list_del(&s->list);
+ s->timeout_time = expire;
+ /* add back to end of list, since it now has highest expiry */
+ dl_list_add_tail(&sm->subscriptions, &s->list);
+ return s;
+}
+
+
+/**
+ * upnp_wps_device_send_wlan_event - Event notification
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * @from_mac_addr: Source (Enrollee) MAC address for the event
+ * @ev_type: Event type
+ * @msg: Event data
+ * Returns: 0 on success, -1 on failure
+ *
+ * Tell external Registrars (UPnP control points) that something happened. In
+ * particular, events include WPS messages from clients that are proxied to
+ * external Registrars.
+ */
+int upnp_wps_device_send_wlan_event(struct upnp_wps_device_sm *sm,
+ const u8 from_mac_addr[ETH_ALEN],
+ enum upnp_wps_wlanevent_type ev_type,
+ const struct wpabuf *msg)
+{
+ int ret = -1;
+ char type[2];
+ const u8 *mac = from_mac_addr;
+ char mac_text[18];
+ u8 *raw = NULL;
+ size_t raw_len;
+ char *val;
+ size_t val_len;
+ int pos = 0;
+
+ if (!sm)
+ goto fail;
+
+ os_snprintf(type, sizeof(type), "%1u", ev_type);
+
+ raw_len = 1 + 17 + (msg ? wpabuf_len(msg) : 0);
+ raw = os_zalloc(raw_len);
+ if (!raw)
+ goto fail;
+
+ *(raw + pos) = (u8) ev_type;
+ pos += 1;
+ os_snprintf(mac_text, sizeof(mac_text), MACSTR, MAC2STR(mac));
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Proxying WLANEvent from %s",
+ mac_text);
+ os_memcpy(raw + pos, mac_text, 17);
+ pos += 17;
+ if (msg) {
+ os_memcpy(raw + pos, wpabuf_head(msg), wpabuf_len(msg));
+ pos += wpabuf_len(msg);
+ }
+ raw_len = pos;
+
+ val = (char *) base64_encode(raw, raw_len, &val_len);
+ if (val == NULL)
+ goto fail;
+
+ os_free(sm->wlanevent);
+ sm->wlanevent = val;
+ sm->wlanevent_type = ev_type;
+ upnp_wps_device_send_event(sm);
+
+ ret = 0;
+
+fail:
+ os_free(raw);
+
+ return ret;
+}
+
+
+#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+#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
+ * @ip_addr: Buffer for returning IP address in network byte order
+ * @ip_addr_text: Buffer for returning a pointer to allocated IP address text
+ * @mac: Buffer for returning MAC address
+ * Returns: 0 on success, -1 on failure
+ */
+int get_netif_info(const char *net_if, unsigned *ip_addr, char **ip_addr_text,
+ u8 mac[ETH_ALEN])
+{
+ struct ifreq req;
+ int sock = -1;
+ struct sockaddr_in *addr;
+ struct in_addr in_addr;
+
+ *ip_addr_text = os_zalloc(16);
+ if (*ip_addr_text == NULL)
+ goto fail;
+
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0)
+ goto fail;
+
+ os_strlcpy(req.ifr_name, net_if, sizeof(req.ifr_name));
+ if (ioctl(sock, SIOCGIFADDR, &req) < 0) {
+ wpa_printf(MSG_ERROR, "WPS UPnP: SIOCGIFADDR failed: %d (%s)",
+ errno, strerror(errno));
+ goto fail;
+ }
+ addr = (void *) &req.ifr_addr;
+ *ip_addr = addr->sin_addr.s_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: "
+ "%d (%s)", errno, strerror(errno));
+ goto fail;
+ }
+ os_memcpy(mac, req.ifr_addr.sa_data, 6);
+#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+ 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
+
+ close(sock);
+ return 0;
+
+fail:
+ if (sock >= 0)
+ close(sock);
+ os_free(*ip_addr_text);
+ *ip_addr_text = NULL;
+ return -1;
+}
+
+
+static void upnp_wps_free_msearchreply(struct dl_list *head)
+{
+ struct advertisement_state_machine *a, *tmp;
+ dl_list_for_each_safe(a, tmp, head, struct advertisement_state_machine,
+ list)
+ msearchreply_state_machine_stop(a);
+}
+
+
+static void upnp_wps_free_subscriptions(struct dl_list *head,
+ struct wps_registrar *reg)
+{
+ struct subscription *s, *tmp;
+ dl_list_for_each_safe(s, tmp, head, struct subscription, list) {
+ if (reg && s->reg != reg)
+ continue;
+ dl_list_del(&s->list);
+ subscription_destroy(s);
+ }
+}
+
+
+/**
+ * upnp_wps_device_stop - Stop WPS UPnP operations on an interface
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ */
+static void upnp_wps_device_stop(struct upnp_wps_device_sm *sm)
+{
+ if (!sm || !sm->started)
+ return;
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Stop device");
+ web_listener_stop(sm);
+ upnp_wps_free_msearchreply(&sm->msearch_replies);
+ upnp_wps_free_subscriptions(&sm->subscriptions, NULL);
+
+ advertisement_state_machine_stop(sm, 1);
+
+ event_send_stop_all(sm);
+ os_free(sm->wlanevent);
+ sm->wlanevent = NULL;
+ os_free(sm->ip_addr_text);
+ sm->ip_addr_text = NULL;
+ if (sm->multicast_sd >= 0)
+ close(sm->multicast_sd);
+ sm->multicast_sd = -1;
+ ssdp_listener_stop(sm);
+
+ sm->started = 0;
+}
+
+
+/**
+ * upnp_wps_device_start - Start WPS UPnP operations on an interface
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * @net_if: Selected network interface name
+ * Returns: 0 on success, -1 on failure
+ */
+static int upnp_wps_device_start(struct upnp_wps_device_sm *sm, char *net_if)
+{
+ if (!sm || !net_if)
+ return -1;
+
+ if (sm->started)
+ upnp_wps_device_stop(sm);
+
+ sm->multicast_sd = -1;
+ sm->ssdp_sd = -1;
+ sm->started = 1;
+ sm->advertise_count = 0;
+
+ /* Fix up linux multicast handling */
+ if (add_ssdp_network(net_if))
+ goto fail;
+
+ /* Determine which IP and mac address we're using */
+ if (get_netif_info(net_if, &sm->ip_addr, &sm->ip_addr_text,
+ sm->mac_addr)) {
+ wpa_printf(MSG_INFO, "WPS UPnP: Could not get IP/MAC address "
+ "for %s. Does it have IP address?", net_if);
+ goto fail;
+ }
+
+ /* Listen for incoming TCP connections so that others
+ * can fetch our "xml files" from us.
+ */
+ if (web_listener_start(sm))
+ goto fail;
+
+ /* Set up for receiving discovery (UDP) packets */
+ if (ssdp_listener_start(sm))
+ goto fail;
+
+ /* Set up for sending multicast */
+ if (ssdp_open_multicast(sm) < 0)
+ goto fail;
+
+ /*
+ * Broadcast NOTIFY messages to let the world know we exist.
+ * This is done via a state machine since the messages should not be
+ * all sent out at once.
+ */
+ if (advertisement_state_machine_start(sm))
+ goto fail;
+
+ return 0;
+
+fail:
+ upnp_wps_device_stop(sm);
+ return -1;
+}
+
+
+static struct upnp_wps_device_interface *
+upnp_wps_get_iface(struct upnp_wps_device_sm *sm, void *priv)
+{
+ struct upnp_wps_device_interface *iface;
+ dl_list_for_each(iface, &sm->interfaces,
+ struct upnp_wps_device_interface, list) {
+ if (iface->priv == priv)
+ return iface;
+ }
+ return NULL;
+}
+
+
+/**
+ * upnp_wps_device_deinit - Deinitialize WPS UPnP
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * @priv: External context data that was used in upnp_wps_device_init() call
+ */
+void upnp_wps_device_deinit(struct upnp_wps_device_sm *sm, void *priv)
+{
+ struct upnp_wps_device_interface *iface;
+
+ if (!sm)
+ return;
+
+ iface = upnp_wps_get_iface(sm, priv);
+ if (iface == NULL) {
+ wpa_printf(MSG_ERROR, "WPS UPnP: Could not find the interface "
+ "instance to deinit");
+ return;
+ }
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Deinit interface instance %p", iface);
+ if (dl_list_len(&sm->interfaces) == 1) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Deinitializing last instance "
+ "- free global device instance");
+ upnp_wps_device_stop(sm);
+ } else
+ upnp_wps_free_subscriptions(&sm->subscriptions,
+ iface->wps->registrar);
+ dl_list_del(&iface->list);
+
+ if (iface->peer.wps)
+ wps_deinit(iface->peer.wps);
+ os_free(iface->ctx->ap_pin);
+ os_free(iface->ctx);
+ os_free(iface);
+
+ if (dl_list_empty(&sm->interfaces)) {
+ os_free(sm->root_dir);
+ os_free(sm->desc_url);
+ os_free(sm);
+ shared_upnp_device = NULL;
+ }
+}
+
+
+/**
+ * upnp_wps_device_init - Initialize WPS UPnP
+ * @ctx: callback table; we must eventually free it
+ * @wps: Pointer to longterm WPS context
+ * @priv: External context data that will be used in callbacks
+ * @net_if: Selected network interface name
+ * Returns: WPS UPnP state or %NULL on failure
+ */
+struct upnp_wps_device_sm *
+upnp_wps_device_init(struct upnp_wps_device_ctx *ctx, struct wps_context *wps,
+ void *priv, char *net_if)
+{
+ struct upnp_wps_device_sm *sm;
+ struct upnp_wps_device_interface *iface;
+ int start = 0;
+
+ iface = os_zalloc(sizeof(*iface));
+ if (iface == NULL) {
+ os_free(ctx->ap_pin);
+ os_free(ctx);
+ return NULL;
+ }
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Init interface instance %p", iface);
+
+ iface->ctx = ctx;
+ iface->wps = wps;
+ iface->priv = priv;
+
+ if (shared_upnp_device) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Share existing device "
+ "context");
+ sm = shared_upnp_device;
+ } else {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Initialize device context");
+ sm = os_zalloc(sizeof(*sm));
+ if (!sm) {
+ wpa_printf(MSG_ERROR, "WPS UPnP: upnp_wps_device_init "
+ "failed");
+ os_free(iface);
+ os_free(ctx->ap_pin);
+ os_free(ctx);
+ return NULL;
+ }
+ shared_upnp_device = sm;
+
+ dl_list_init(&sm->msearch_replies);
+ dl_list_init(&sm->subscriptions);
+ dl_list_init(&sm->interfaces);
+ start = 1;
+ }
+
+ dl_list_add(&sm->interfaces, &iface->list);
+
+ if (start && upnp_wps_device_start(sm, net_if)) {
+ upnp_wps_device_deinit(sm, priv);
+ return NULL;
+ }
+
+
+ return sm;
+}
+
+
+/**
+ * upnp_wps_subscribers - Check whether there are any event subscribers
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * Returns: 0 if no subscribers, 1 if subscribers
+ */
+int upnp_wps_subscribers(struct upnp_wps_device_sm *sm)
+{
+ return !dl_list_empty(&sm->subscriptions);
+}
+
+
+int upnp_wps_set_ap_pin(struct upnp_wps_device_sm *sm, const char *ap_pin)
+{
+ struct upnp_wps_device_interface *iface;
+ if (sm == NULL)
+ return 0;
+
+ dl_list_for_each(iface, &sm->interfaces,
+ struct upnp_wps_device_interface, list) {
+ os_free(iface->ctx->ap_pin);
+ if (ap_pin) {
+ iface->ctx->ap_pin = os_strdup(ap_pin);
+ if (iface->ctx->ap_pin == NULL)
+ return -1;
+ } else
+ iface->ctx->ap_pin = NULL;
+ }
+
+ return 0;
+}
diff --git a/src/wps/wps_upnp.h b/src/wps/wps_upnp.h
new file mode 100644
index 0000000..87b7ab1
--- /dev/null
+++ b/src/wps/wps_upnp.h
@@ -0,0 +1,48 @@
+/*
+ * UPnP WPS Device
+ * Copyright (c) 2000-2003 Intel Corporation
+ * Copyright (c) 2006-2007 Sony Corporation
+ * Copyright (c) 2008-2009 Atheros Communications
+ * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
+ *
+ * See wps_upnp.c for more details on licensing and code history.
+ */
+
+#ifndef WPS_UPNP_H
+#define WPS_UPNP_H
+
+struct upnp_wps_device_sm;
+struct wps_context;
+struct wps_data;
+
+struct upnp_wps_peer {
+ struct wps_data *wps;
+};
+
+enum upnp_wps_wlanevent_type {
+ UPNP_WPS_WLANEVENT_TYPE_PROBE = 1,
+ UPNP_WPS_WLANEVENT_TYPE_EAP = 2
+};
+
+struct upnp_wps_device_ctx {
+ int (*rx_req_put_wlan_response)(
+ void *priv, enum upnp_wps_wlanevent_type ev_type,
+ const u8 *mac_addr, const struct wpabuf *msg,
+ enum wps_msg_type msg_type);
+
+ char *ap_pin;
+};
+
+struct upnp_wps_device_sm *
+upnp_wps_device_init(struct upnp_wps_device_ctx *ctx, struct wps_context *wps,
+ void *priv, char *net_if);
+void upnp_wps_device_deinit(struct upnp_wps_device_sm *sm, void *priv);
+
+int upnp_wps_device_send_wlan_event(struct upnp_wps_device_sm *sm,
+ const u8 from_mac_addr[ETH_ALEN],
+ enum upnp_wps_wlanevent_type ev_type,
+ const struct wpabuf *msg);
+int upnp_wps_subscribers(struct upnp_wps_device_sm *sm);
+int upnp_wps_set_ap_pin(struct upnp_wps_device_sm *sm, const char *ap_pin);
+
+#endif /* WPS_UPNP_H */
diff --git a/src/wps/wps_upnp_ap.c b/src/wps/wps_upnp_ap.c
new file mode 100644
index 0000000..501ecbc
--- /dev/null
+++ b/src/wps/wps_upnp_ap.c
@@ -0,0 +1,91 @@
+/*
+ * Wi-Fi Protected Setup - UPnP AP functionality
+ * Copyright (c) 2009, 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.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "eloop.h"
+#include "uuid.h"
+#include "wps_i.h"
+#include "wps_upnp.h"
+#include "wps_upnp_i.h"
+
+
+static void upnp_er_set_selected_timeout(void *eloop_ctx, void *timeout_ctx)
+{
+ struct subscription *s = eloop_ctx;
+ wpa_printf(MSG_DEBUG, "WPS: SetSelectedRegistrar from ER timed out");
+ s->selected_registrar = 0;
+ wps_registrar_selected_registrar_changed(s->reg);
+}
+
+
+int upnp_er_set_selected_registrar(struct wps_registrar *reg,
+ struct subscription *s,
+ const struct wpabuf *msg)
+{
+ struct wps_parse_attr attr;
+
+ wpa_hexdump_buf(MSG_MSGDUMP, "WPS: SetSelectedRegistrar attributes",
+ msg);
+ if (wps_validate_upnp_set_selected_registrar(msg) < 0)
+ return -1;
+
+ if (wps_parse_msg(msg, &attr) < 0)
+ return -1;
+
+ s->reg = reg;
+ eloop_cancel_timeout(upnp_er_set_selected_timeout, s, NULL);
+
+ os_memset(s->authorized_macs, 0, sizeof(s->authorized_macs));
+ if (attr.selected_registrar == NULL || *attr.selected_registrar == 0) {
+ wpa_printf(MSG_DEBUG, "WPS: SetSelectedRegistrar: Disable "
+ "Selected Registrar");
+ s->selected_registrar = 0;
+ } else {
+ s->selected_registrar = 1;
+ s->dev_password_id = attr.dev_password_id ?
+ WPA_GET_BE16(attr.dev_password_id) : DEV_PW_DEFAULT;
+ s->config_methods = attr.sel_reg_config_methods ?
+ WPA_GET_BE16(attr.sel_reg_config_methods) : -1;
+ if (attr.authorized_macs) {
+ int count = attr.authorized_macs_len / ETH_ALEN;
+ if (count > WPS_MAX_AUTHORIZED_MACS)
+ count = WPS_MAX_AUTHORIZED_MACS;
+ os_memcpy(s->authorized_macs, attr.authorized_macs,
+ count * ETH_ALEN);
+ } else if (!attr.version2) {
+#ifdef CONFIG_WPS2
+ wpa_printf(MSG_DEBUG, "WPS: Add broadcast "
+ "AuthorizedMACs for WPS 1.0 ER");
+ os_memset(s->authorized_macs, 0xff, ETH_ALEN);
+#endif /* CONFIG_WPS2 */
+ }
+ eloop_register_timeout(WPS_PBC_WALK_TIME, 0,
+ upnp_er_set_selected_timeout, s, NULL);
+ }
+
+ wps_registrar_selected_registrar_changed(reg);
+
+ return 0;
+}
+
+
+void upnp_er_remove_notification(struct subscription *s)
+{
+ s->selected_registrar = 0;
+ eloop_cancel_timeout(upnp_er_set_selected_timeout, s, NULL);
+ if (s->reg)
+ wps_registrar_selected_registrar_changed(s->reg);
+}
diff --git a/src/wps/wps_upnp_event.c b/src/wps/wps_upnp_event.c
new file mode 100644
index 0000000..2c8ed4f
--- /dev/null
+++ b/src/wps/wps_upnp_event.c
@@ -0,0 +1,423 @@
+/*
+ * UPnP WPS Device - Event processing
+ * Copyright (c) 2000-2003 Intel Corporation
+ * Copyright (c) 2006-2007 Sony Corporation
+ * Copyright (c) 2008-2009 Atheros Communications
+ * Copyright (c) 2009-2010, Jouni Malinen <j@w1.fi>
+ *
+ * See wps_upnp.c for more details on licensing and code history.
+ */
+
+#include "includes.h"
+#include <assert.h>
+
+#include "common.h"
+#include "eloop.h"
+#include "uuid.h"
+#include "http_client.h"
+#include "wps_defs.h"
+#include "wps_upnp.h"
+#include "wps_upnp_i.h"
+
+/*
+ * Event message generation (to subscribers)
+ *
+ * We make a separate copy for each message for each subscriber. This memory
+ * wasted could be limited (adding code complexity) by sharing copies, keeping
+ * a usage count and freeing when zero.
+ *
+ * Sending a message requires using a HTTP over TCP NOTIFY
+ * (like a PUT) which requires a number of states..
+ */
+
+#define MAX_EVENTS_QUEUED 20 /* How far behind queued events */
+#define MAX_FAILURES 10 /* Drop subscription after this many failures */
+
+/* How long to wait before sending event */
+#define EVENT_DELAY_SECONDS 0
+#define EVENT_DELAY_MSEC 0
+
+/*
+ * Event information that we send to each subscriber is remembered in this
+ * struct. The event cannot be sent by simple UDP; it has to be sent by a HTTP
+ * over TCP transaction which requires various states.. It may also need to be
+ * retried at a different address (if more than one is available).
+ *
+ * TODO: As an optimization we could share data between subscribers.
+ */
+struct wps_event_ {
+ struct dl_list list;
+ struct subscription *s; /* parent */
+ unsigned subscriber_sequence; /* which event for this subscription*/
+ unsigned int retry; /* which retry */
+ struct subscr_addr *addr; /* address to connect to */
+ struct wpabuf *data; /* event data to send */
+ struct http_client *http_event;
+};
+
+
+/* event_clean -- clean sockets etc. of event
+ * Leaves data, retry count etc. alone.
+ */
+static void event_clean(struct wps_event_ *e)
+{
+ if (e->s->current_event == e)
+ e->s->current_event = NULL;
+ http_client_free(e->http_event);
+ e->http_event = NULL;
+}
+
+
+/* event_delete -- delete single unqueued event
+ * (be sure to dequeue first if need be)
+ */
+static void event_delete(struct wps_event_ *e)
+{
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Delete event %p", e);
+ event_clean(e);
+ wpabuf_free(e->data);
+ os_free(e);
+}
+
+
+/* event_dequeue -- get next event from the queue
+ * Returns NULL if empty.
+ */
+static struct wps_event_ *event_dequeue(struct subscription *s)
+{
+ struct wps_event_ *e;
+ e = dl_list_first(&s->event_queue, struct wps_event_, list);
+ if (e) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Dequeue event %p for "
+ "subscription %p", e, s);
+ dl_list_del(&e->list);
+ }
+ return e;
+}
+
+
+/* event_delete_all -- delete entire event queue and current event */
+void event_delete_all(struct subscription *s)
+{
+ struct wps_event_ *e;
+ while ((e = event_dequeue(s)) != NULL)
+ event_delete(e);
+ if (s->current_event) {
+ event_delete(s->current_event);
+ /* will set: s->current_event = NULL; */
+ }
+}
+
+
+/**
+ * event_retry - Called when we had a failure delivering event msg
+ * @e: Event
+ * @do_next_address: skip address e.g. on connect fail
+ */
+static void event_retry(struct wps_event_ *e, int do_next_address)
+{
+ struct subscription *s = e->s;
+ struct upnp_wps_device_sm *sm = s->sm;
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Retry event %p for subscription %p",
+ e, s);
+ event_clean(e);
+ /* will set: s->current_event = NULL; */
+
+ if (do_next_address) {
+ e->retry++;
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Try address %d", e->retry);
+ }
+ if (e->retry >= dl_list_len(&s->addr_list)) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Giving up on sending event "
+ "for %s", e->addr->domain_and_port);
+ event_delete(e);
+ s->last_event_failed = 1;
+ if (!dl_list_empty(&s->event_queue))
+ event_send_all_later(s->sm);
+ return;
+ }
+ dl_list_add(&s->event_queue, &e->list);
+ event_send_all_later(sm);
+}
+
+
+static struct wpabuf * event_build_message(struct wps_event_ *e)
+{
+ struct wpabuf *buf;
+ char *b;
+
+ buf = wpabuf_alloc(1000 + wpabuf_len(e->data));
+ if (buf == NULL)
+ return NULL;
+ wpabuf_printf(buf, "NOTIFY %s HTTP/1.1\r\n", e->addr->path);
+ wpabuf_put_str(buf, "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n");
+ wpabuf_printf(buf, "HOST: %s\r\n", e->addr->domain_and_port);
+ wpabuf_put_str(buf, "CONTENT-TYPE: text/xml; charset=\"utf-8\"\r\n"
+ "NT: upnp:event\r\n"
+ "NTS: upnp:propchange\r\n");
+ wpabuf_put_str(buf, "SID: uuid:");
+ b = wpabuf_put(buf, 0);
+ uuid_bin2str(e->s->uuid, b, 80);
+ wpabuf_put(buf, os_strlen(b));
+ wpabuf_put_str(buf, "\r\n");
+ wpabuf_printf(buf, "SEQ: %u\r\n", e->subscriber_sequence);
+ wpabuf_printf(buf, "CONTENT-LENGTH: %d\r\n",
+ (int) wpabuf_len(e->data));
+ wpabuf_put_str(buf, "\r\n"); /* terminating empty line */
+ wpabuf_put_buf(buf, e->data);
+ return buf;
+}
+
+
+static void event_addr_failure(struct wps_event_ *e)
+{
+ struct subscription *s = e->s;
+
+ e->addr->num_failures++;
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Failed to send event %p to %s "
+ "(num_failures=%u)",
+ e, e->addr->domain_and_port, e->addr->num_failures);
+
+ if (e->addr->num_failures < MAX_FAILURES) {
+ /* Try other addresses, if available */
+ event_retry(e, 1);
+ return;
+ }
+
+ /*
+ * If other side doesn't like what we say, forget about them.
+ * (There is no way to tell other side that we are dropping them...).
+ */
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Deleting subscription %p "
+ "address %s due to errors", s, e->addr->domain_and_port);
+ dl_list_del(&e->addr->list);
+ subscr_addr_delete(e->addr);
+ e->addr = NULL;
+
+ if (dl_list_empty(&s->addr_list)) {
+ /* if we've given up on all addresses */
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Removing subscription %p "
+ "with no addresses", s);
+ dl_list_del(&s->list);
+ subscription_destroy(s);
+ return;
+ }
+
+ /* Try other addresses, if available */
+ event_retry(e, 0);
+}
+
+
+static void event_http_cb(void *ctx, struct http_client *c,
+ enum http_client_event event)
+{
+ struct wps_event_ *e = ctx;
+ struct subscription *s = e->s;
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP client callback: e=%p c=%p "
+ "event=%d", e, c, event);
+ switch (event) {
+ case HTTP_CLIENT_OK:
+ wpa_printf(MSG_DEBUG,
+ "WPS UPnP: Got event %p reply OK from %s",
+ e, e->addr->domain_and_port);
+ e->addr->num_failures = 0;
+ s->last_event_failed = 0;
+ event_delete(e);
+
+ /* Schedule sending more if there is more to send */
+ if (!dl_list_empty(&s->event_queue))
+ event_send_all_later(s->sm);
+ break;
+ case HTTP_CLIENT_FAILED:
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Event send failure");
+ event_addr_failure(e);
+ break;
+ case HTTP_CLIENT_INVALID_REPLY:
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Invalid reply");
+ event_addr_failure(e);
+ break;
+ case HTTP_CLIENT_TIMEOUT:
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Event send timeout");
+ event_addr_failure(e);
+ break;
+ }
+}
+
+
+/* event_send_start -- prepare to send a event message to subscriber
+ *
+ * This gets complicated because:
+ * -- The message is sent via TCP and we have to keep the stream open
+ * for 30 seconds to get a response... then close it.
+ * -- But we might have other event happen in the meantime...
+ * we have to queue them, if we lose them then the subscriber will
+ * be forced to unsubscribe and subscribe again.
+ * -- If multiple URLs are provided then we are supposed to try successive
+ * ones after 30 second timeout.
+ * -- The URLs might use domain names instead of dotted decimal addresses,
+ * and resolution of those may cause unwanted sleeping.
+ * -- Doing the initial TCP connect can take a while, so we have to come
+ * back after connection and then send the data.
+ *
+ * Returns nonzero on error;
+ *
+ * Prerequisite: No current event send (s->current_event == NULL)
+ * and non-empty queue.
+ */
+static int event_send_start(struct subscription *s)
+{
+ struct wps_event_ *e;
+ unsigned int itry;
+ struct wpabuf *buf;
+
+ /*
+ * Assume we are called ONLY with no current event and ONLY with
+ * nonempty event queue and ONLY with at least one address to send to.
+ */
+ if (dl_list_empty(&s->addr_list))
+ return -1;
+ if (s->current_event)
+ return -1;
+ if (dl_list_empty(&s->event_queue))
+ return -1;
+
+ s->current_event = e = event_dequeue(s);
+
+ /* Use address according to number of retries */
+ itry = 0;
+ dl_list_for_each(e->addr, &s->addr_list, struct subscr_addr, list)
+ if (itry++ == e->retry)
+ break;
+ if (itry < e->retry)
+ return -1;
+
+ buf = event_build_message(e);
+ if (buf == NULL) {
+ event_retry(e, 0);
+ return -1;
+ }
+
+ e->http_event = http_client_addr(&e->addr->saddr, buf, 0,
+ event_http_cb, e);
+ if (e->http_event == NULL) {
+ wpabuf_free(buf);
+ event_retry(e, 0);
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/* event_send_all_later_handler -- actually send events as needed */
+static void event_send_all_later_handler(void *eloop_data, void *user_ctx)
+{
+ struct upnp_wps_device_sm *sm = user_ctx;
+ struct subscription *s, *tmp;
+ int nerrors = 0;
+
+ sm->event_send_all_queued = 0;
+ dl_list_for_each_safe(s, tmp, &sm->subscriptions, struct subscription,
+ list) {
+ if (s->current_event == NULL /* not busy */ &&
+ !dl_list_empty(&s->event_queue) /* more to do */) {
+ if (event_send_start(s))
+ nerrors++;
+ }
+ }
+
+ if (nerrors) {
+ /* Try again later */
+ event_send_all_later(sm);
+ }
+}
+
+
+/* event_send_all_later -- schedule sending events to all subscribers
+ * that need it.
+ * This avoids two problems:
+ * -- After getting a subscription, we should not send the first event
+ * until after our reply is fully queued to be sent back,
+ * -- Possible stack depth or infinite recursion issues.
+ */
+void event_send_all_later(struct upnp_wps_device_sm *sm)
+{
+ /*
+ * The exact time in the future isn't too important. Waiting a bit
+ * might let us do several together.
+ */
+ if (sm->event_send_all_queued)
+ return;
+ sm->event_send_all_queued = 1;
+ eloop_register_timeout(EVENT_DELAY_SECONDS, EVENT_DELAY_MSEC,
+ event_send_all_later_handler, NULL, sm);
+}
+
+
+/* event_send_stop_all -- cleanup */
+void event_send_stop_all(struct upnp_wps_device_sm *sm)
+{
+ if (sm->event_send_all_queued)
+ eloop_cancel_timeout(event_send_all_later_handler, NULL, sm);
+ sm->event_send_all_queued = 0;
+}
+
+
+/**
+ * event_add - Add a new event to a queue
+ * @s: Subscription
+ * @data: Event data (is copied; caller retains ownership)
+ * @probereq: Whether this is a Probe Request event
+ * Returns: 0 on success, -1 on error, 1 on max event queue limit reached
+ */
+int event_add(struct subscription *s, const struct wpabuf *data, int probereq)
+{
+ struct wps_event_ *e;
+ unsigned int len;
+
+ len = dl_list_len(&s->event_queue);
+ if (len >= MAX_EVENTS_QUEUED) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Too many events queued for "
+ "subscriber %p", s);
+ if (probereq)
+ return 1;
+
+ /* Drop oldest entry to allow EAP event to be stored. */
+ e = event_dequeue(s);
+ if (!e)
+ return 1;
+ event_delete(e);
+ }
+
+ if (s->last_event_failed && probereq && len > 0) {
+ /*
+ * Avoid queuing frames for subscribers that may have left
+ * without unsubscribing.
+ */
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Do not queue more Probe "
+ "Request frames for subscription %p since last "
+ "delivery failed", s);
+ return -1;
+ }
+
+ e = os_zalloc(sizeof(*e));
+ if (e == NULL)
+ return -1;
+ dl_list_init(&e->list);
+ e->s = s;
+ e->data = wpabuf_dup(data);
+ if (e->data == NULL) {
+ os_free(e);
+ return -1;
+ }
+ e->subscriber_sequence = s->next_subscriber_sequence++;
+ if (s->next_subscriber_sequence == 0)
+ s->next_subscriber_sequence++;
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Queue event %p for subscriber %p "
+ "(queue len %u)", e, s, len + 1);
+ dl_list_add_tail(&s->event_queue, &e->list);
+ event_send_all_later(s->sm);
+ return 0;
+}
diff --git a/src/wps/wps_upnp_i.h b/src/wps/wps_upnp_i.h
new file mode 100644
index 0000000..3ecf05d
--- /dev/null
+++ b/src/wps/wps_upnp_i.h
@@ -0,0 +1,193 @@
+/*
+ * UPnP for WPS / internal definitions
+ * Copyright (c) 2000-2003 Intel Corporation
+ * Copyright (c) 2006-2007 Sony Corporation
+ * Copyright (c) 2008-2009 Atheros Communications
+ * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
+ *
+ * See wps_upnp.c for more details on licensing and code history.
+ */
+
+#ifndef WPS_UPNP_I_H
+#define WPS_UPNP_I_H
+
+#include "utils/list.h"
+#include "http.h"
+
+#define UPNP_MULTICAST_ADDRESS "239.255.255.250" /* for UPnP multicasting */
+#define UPNP_MULTICAST_PORT 1900 /* UDP port to monitor for UPnP */
+
+/* min subscribe time per UPnP standard */
+#define UPNP_SUBSCRIBE_SEC_MIN 1800
+/* subscribe time we use */
+#define UPNP_SUBSCRIBE_SEC (UPNP_SUBSCRIBE_SEC_MIN + 1)
+
+/* "filenames" used in URLs that we service via our "web server": */
+#define UPNP_WPS_DEVICE_XML_FILE "wps_device.xml"
+#define UPNP_WPS_SCPD_XML_FILE "wps_scpd.xml"
+#define UPNP_WPS_DEVICE_CONTROL_FILE "wps_control"
+#define UPNP_WPS_DEVICE_EVENT_FILE "wps_event"
+
+#define MULTICAST_MAX_READ 1600 /* max bytes we'll read for UPD request */
+
+
+struct upnp_wps_device_sm;
+struct wps_registrar;
+
+
+enum advertisement_type_enum {
+ ADVERTISE_UP = 0,
+ ADVERTISE_DOWN = 1,
+ MSEARCH_REPLY = 2
+};
+
+/*
+ * Advertisements are broadcast via UDP NOTIFYs, and are also the essence of
+ * the reply to UDP M-SEARCH requests. This struct handles both cases.
+ *
+ * A state machine is needed because a number of variant forms must be sent in
+ * separate packets and spread out in time to avoid congestion.
+ */
+struct advertisement_state_machine {
+ struct dl_list list;
+ enum advertisement_type_enum type;
+ int state;
+ int nerrors;
+ struct sockaddr_in client; /* for M-SEARCH replies */
+};
+
+
+/*
+ * An address of a subscriber (who may have multiple addresses). We are
+ * supposed to send (via TCP) updates to each subscriber, trying each address
+ * for a subscriber until we find one that seems to work.
+ */
+struct subscr_addr {
+ struct dl_list list;
+ char *domain_and_port; /* domain and port part of url */
+ char *path; /* "filepath" part of url (from "mem") */
+ struct sockaddr_in saddr; /* address for doing connect */
+ unsigned num_failures;
+};
+
+
+/*
+ * Subscribers to our events are recorded in this struct. This includes a max
+ * of one outgoing connection (sending an "event message") per subscriber. We
+ * also have to age out subscribers unless they renew.
+ */
+struct subscription {
+ struct dl_list list;
+ struct upnp_wps_device_sm *sm; /* parent */
+ time_t timeout_time; /* when to age out the subscription */
+ unsigned next_subscriber_sequence; /* number our messages */
+ /*
+ * This uuid identifies the subscription and is randomly generated by
+ * us and given to the subscriber when the subscription is accepted;
+ * and is then included with each event sent to the subscriber.
+ */
+ u8 uuid[UUID_LEN];
+ /* Linked list of address alternatives (rotate through on failure) */
+ struct dl_list addr_list;
+ struct dl_list event_queue; /* Queued event messages. */
+ struct wps_event_ *current_event; /* non-NULL if being sent (not in q)
+ */
+ int last_event_failed; /* Whether delivery of last event failed */
+
+ /* Information from SetSelectedRegistrar action */
+ u8 selected_registrar;
+ u16 dev_password_id;
+ u16 config_methods;
+ u8 authorized_macs[WPS_MAX_AUTHORIZED_MACS][ETH_ALEN];
+ struct wps_registrar *reg;
+};
+
+
+struct upnp_wps_device_interface {
+ struct dl_list list;
+ struct upnp_wps_device_ctx *ctx; /* callback table */
+ struct wps_context *wps;
+ void *priv;
+
+ /* FIX: maintain separate structures for each UPnP peer */
+ struct upnp_wps_peer peer;
+};
+
+/*
+ * Our instance data corresponding to the AP device. Note that there may be
+ * multiple wireless interfaces sharing the same UPnP device instance. Each
+ * such interface is stored in the list of struct upnp_wps_device_interface
+ * instances.
+ *
+ * This is known as an opaque struct declaration to users of the WPS UPnP code.
+ */
+struct upnp_wps_device_sm {
+ struct dl_list interfaces; /* struct upnp_wps_device_interface */
+ char *root_dir;
+ char *desc_url;
+ int started; /* nonzero if we are active */
+ u8 mac_addr[ETH_ALEN]; /* mac addr of network i.f. we use */
+ char *ip_addr_text; /* IP address of network i.f. we use */
+ unsigned ip_addr; /* IP address of network i.f. we use (host order) */
+ int multicast_sd; /* send multicast messages over this socket */
+ int ssdp_sd; /* receive discovery UPD packets on socket */
+ int ssdp_sd_registered; /* nonzero if we must unregister */
+ unsigned advertise_count; /* how many advertisements done */
+ struct advertisement_state_machine advertisement;
+ struct dl_list msearch_replies;
+ int web_port; /* our port that others get xml files from */
+ struct http_server *web_srv;
+ /* Note: subscriptions are kept in expiry order */
+ struct dl_list subscriptions;
+ int event_send_all_queued; /* if we are scheduled to send events soon
+ */
+
+ char *wlanevent; /* the last WLANEvent data */
+ enum upnp_wps_wlanevent_type wlanevent_type;
+ os_time_t last_event_sec;
+ unsigned int num_events_in_sec;
+};
+
+/* wps_upnp.c */
+void format_date(struct wpabuf *buf);
+struct subscription * subscription_start(struct upnp_wps_device_sm *sm,
+ const char *callback_urls);
+struct subscription * subscription_renew(struct upnp_wps_device_sm *sm,
+ const u8 uuid[UUID_LEN]);
+void subscription_destroy(struct subscription *s);
+struct subscription * subscription_find(struct upnp_wps_device_sm *sm,
+ const u8 uuid[UUID_LEN]);
+void subscr_addr_delete(struct subscr_addr *a);
+int send_wpabuf(int fd, struct wpabuf *buf);
+int get_netif_info(const char *net_if, unsigned *ip_addr, char **ip_addr_text,
+ u8 mac[ETH_ALEN]);
+
+/* 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,
+ int send_byebye);
+void ssdp_listener_stop(struct upnp_wps_device_sm *sm);
+int ssdp_listener_start(struct upnp_wps_device_sm *sm);
+int ssdp_listener_open(void);
+int add_ssdp_network(const char *net_if);
+int ssdp_open_multicast_sock(u32 ip_addr);
+int ssdp_open_multicast(struct upnp_wps_device_sm *sm);
+
+/* wps_upnp_web.c */
+int web_listener_start(struct upnp_wps_device_sm *sm);
+void web_listener_stop(struct upnp_wps_device_sm *sm);
+
+/* wps_upnp_event.c */
+int event_add(struct subscription *s, const struct wpabuf *data, int probereq);
+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);
+
+/* wps_upnp_ap.c */
+int upnp_er_set_selected_registrar(struct wps_registrar *reg,
+ struct subscription *s,
+ const struct wpabuf *msg);
+void upnp_er_remove_notification(struct subscription *s);
+
+#endif /* WPS_UPNP_I_H */
diff --git a/src/wps/wps_upnp_ssdp.c b/src/wps/wps_upnp_ssdp.c
new file mode 100644
index 0000000..c7fb158
--- /dev/null
+++ b/src/wps/wps_upnp_ssdp.c
@@ -0,0 +1,938 @@
+/*
+ * UPnP SSDP for WPS
+ * Copyright (c) 2000-2003 Intel Corporation
+ * Copyright (c) 2006-2007 Sony Corporation
+ * Copyright (c) 2008-2009 Atheros Communications
+ * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
+ *
+ * See wps_upnp.c for more details on licensing and code history.
+ */
+
+#include "includes.h"
+
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <net/route.h>
+
+#include "common.h"
+#include "uuid.h"
+#include "eloop.h"
+#include "wps.h"
+#include "wps_upnp.h"
+#include "wps_upnp_i.h"
+
+#define UPNP_CACHE_SEC (UPNP_CACHE_SEC_MIN + 1) /* cache time we use */
+#define UPNP_CACHE_SEC_MIN 1800 /* min cachable time per UPnP standard */
+#define UPNP_ADVERTISE_REPEAT 2 /* no more than 3 */
+#define MAX_MSEARCH 20 /* max simultaneous M-SEARCH replies ongoing */
+#define SSDP_TARGET "239.0.0.0"
+#define SSDP_NETMASK "255.0.0.0"
+
+
+/* Check tokens for equality, where tokens consist of letters, digits,
+ * underscore and hyphen, and are matched case insensitive.
+ */
+static int token_eq(const char *s1, const char *s2)
+{
+ int c1;
+ int c2;
+ int end1 = 0;
+ int end2 = 0;
+ for (;;) {
+ c1 = *s1++;
+ c2 = *s2++;
+ if (isalpha(c1) && isupper(c1))
+ c1 = tolower(c1);
+ if (isalpha(c2) && isupper(c2))
+ c2 = tolower(c2);
+ end1 = !(isalnum(c1) || c1 == '_' || c1 == '-');
+ end2 = !(isalnum(c2) || c2 == '_' || c2 == '-');
+ if (end1 || end2 || c1 != c2)
+ break;
+ }
+ return end1 && end2; /* reached end of both words? */
+}
+
+
+/* Return length of token (see above for definition of token) */
+static int token_length(const char *s)
+{
+ const char *begin = s;
+ for (;; s++) {
+ int c = *s;
+ int end = !(isalnum(c) || c == '_' || c == '-');
+ if (end)
+ break;
+ }
+ return s - begin;
+}
+
+
+/* return length of interword separation.
+ * This accepts only spaces/tabs and thus will not traverse a line
+ * or buffer ending.
+ */
+static int word_separation_length(const char *s)
+{
+ const char *begin = s;
+ for (;; s++) {
+ int c = *s;
+ if (c == ' ' || c == '\t')
+ continue;
+ break;
+ }
+ return s - begin;
+}
+
+
+/* No. of chars through (including) end of line */
+static int line_length(const char *l)
+{
+ const char *lp = l;
+ while (*lp && *lp != '\n')
+ lp++;
+ if (*lp == '\n')
+ lp++;
+ return lp - l;
+}
+
+
+/* No. of chars excluding trailing whitespace */
+static int line_length_stripped(const char *l)
+{
+ const char *lp = l + line_length(l);
+ while (lp > l && !isgraph(lp[-1]))
+ lp--;
+ return lp - l;
+}
+
+
+static int str_starts(const char *str, const char *start)
+{
+ return os_strncmp(str, start, os_strlen(start)) == 0;
+}
+
+
+/***************************************************************************
+ * Advertisements.
+ * These are multicast to the world to tell them we are here.
+ * The individual packets are spread out in time to limit loss,
+ * and then after a much longer period of time the whole sequence
+ * is repeated again (for NOTIFYs only).
+ **************************************************************************/
+
+/**
+ * next_advertisement - Build next message and advance the state machine
+ * @a: Advertisement state
+ * @islast: Buffer for indicating whether this is the last message (= 1)
+ * Returns: The new message (caller is responsible for freeing this)
+ *
+ * Note: next_advertisement is shared code with msearchreply_* functions
+ */
+static struct wpabuf *
+next_advertisement(struct upnp_wps_device_sm *sm,
+ struct advertisement_state_machine *a, int *islast)
+{
+ struct wpabuf *msg;
+ char *NTString = "";
+ char uuid_string[80];
+ struct upnp_wps_device_interface *iface;
+
+ *islast = 0;
+ iface = dl_list_first(&sm->interfaces,
+ struct upnp_wps_device_interface, list);
+ uuid_bin2str(iface->wps->uuid, uuid_string, sizeof(uuid_string));
+ msg = wpabuf_alloc(800); /* more than big enough */
+ if (msg == NULL)
+ goto fail;
+ switch (a->type) {
+ case ADVERTISE_UP:
+ case ADVERTISE_DOWN:
+ NTString = "NT";
+ wpabuf_put_str(msg, "NOTIFY * HTTP/1.1\r\n");
+ wpabuf_printf(msg, "HOST: %s:%d\r\n",
+ UPNP_MULTICAST_ADDRESS, UPNP_MULTICAST_PORT);
+ wpabuf_printf(msg, "CACHE-CONTROL: max-age=%d\r\n",
+ UPNP_CACHE_SEC);
+ wpabuf_printf(msg, "NTS: %s\r\n",
+ (a->type == ADVERTISE_UP ?
+ "ssdp:alive" : "ssdp:byebye"));
+ break;
+ case MSEARCH_REPLY:
+ NTString = "ST";
+ wpabuf_put_str(msg, "HTTP/1.1 200 OK\r\n");
+ wpabuf_printf(msg, "CACHE-CONTROL: max-age=%d\r\n",
+ UPNP_CACHE_SEC);
+
+ wpabuf_put_str(msg, "DATE: ");
+ format_date(msg);
+ wpabuf_put_str(msg, "\r\n");
+
+ wpabuf_put_str(msg, "EXT:\r\n");
+ break;
+ }
+
+ if (a->type != ADVERTISE_DOWN) {
+ /* Where others may get our XML files from */
+ wpabuf_printf(msg, "LOCATION: http://%s:%d/%s\r\n",
+ sm->ip_addr_text, sm->web_port,
+ UPNP_WPS_DEVICE_XML_FILE);
+ }
+
+ /* The SERVER line has three comma-separated fields:
+ * operating system / version
+ * upnp version
+ * software package / version
+ * However, only the UPnP version is really required, the
+ * others can be place holders... for security reasons
+ * it is better to NOT provide extra information.
+ */
+ wpabuf_put_str(msg, "SERVER: Unspecified, UPnP/1.0, Unspecified\r\n");
+
+ switch (a->state / UPNP_ADVERTISE_REPEAT) {
+ case 0:
+ wpabuf_printf(msg, "%s: upnp:rootdevice\r\n", NTString);
+ wpabuf_printf(msg, "USN: uuid:%s::upnp:rootdevice\r\n",
+ uuid_string);
+ break;
+ case 1:
+ wpabuf_printf(msg, "%s: uuid:%s\r\n", NTString, uuid_string);
+ wpabuf_printf(msg, "USN: uuid:%s\r\n", uuid_string);
+ break;
+ case 2:
+ wpabuf_printf(msg, "%s: urn:schemas-wifialliance-org:device:"
+ "WFADevice:1\r\n", NTString);
+ wpabuf_printf(msg, "USN: uuid:%s::urn:schemas-wifialliance-"
+ "org:device:WFADevice:1\r\n", uuid_string);
+ break;
+ case 3:
+ wpabuf_printf(msg, "%s: urn:schemas-wifialliance-org:service:"
+ "WFAWLANConfig:1\r\n", NTString);
+ wpabuf_printf(msg, "USN: uuid:%s::urn:schemas-wifialliance-"
+ "org:service:WFAWLANConfig:1\r\n", uuid_string);
+ break;
+ }
+ wpabuf_put_str(msg, "\r\n");
+
+ if (a->state + 1 >= 4 * UPNP_ADVERTISE_REPEAT)
+ *islast = 1;
+
+ return msg;
+
+fail:
+ wpabuf_free(msg);
+ return NULL;
+}
+
+
+static void advertisement_state_machine_handler(void *eloop_data,
+ void *user_ctx);
+
+
+/**
+ * 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,
+ 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;
+
+ 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(sm, 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++;
+ }
+}
+
+
+static void advertisement_state_machine_handler(void *eloop_data,
+ void *user_ctx)
+{
+ struct upnp_wps_device_sm *sm = user_ctx;
+ struct advertisement_state_machine *a = &sm->advertisement;
+ struct wpabuf *msg;
+ int next_timeout_msec = 100;
+ int next_timeout_sec = 0;
+ struct sockaddr_in dest;
+ int islast = 0;
+
+ /*
+ * Each is sent twice (in case lost) w/ 100 msec delay between;
+ * spec says no more than 3 times.
+ * One pair for rootdevice, one pair for uuid, and a pair each for
+ * each of the two urns.
+ * The entire sequence must be repeated before cache control timeout
+ * (which is min 1800 seconds),
+ * recommend random portion of half of the advertised cache control age
+ * to ensure against loss... perhaps 1800/4 + rand*1800/4 ?
+ * Delay random interval < 100 msec prior to initial sending.
+ * TTL of 4
+ */
+
+ wpa_printf(MSG_MSGDUMP, "WPS UPnP: Advertisement state=%d", a->state);
+ msg = next_advertisement(sm, a, &islast);
+ if (msg == NULL)
+ return;
+
+ 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);
+
+ if (sendto(sm->multicast_sd, wpabuf_head(msg), wpabuf_len(msg), 0,
+ (struct sockaddr *) &dest, sizeof(dest)) == -1) {
+ wpa_printf(MSG_ERROR, "WPS UPnP: Advertisement sendto failed:"
+ "%d (%s)", errno, strerror(errno));
+ next_timeout_msec = 0;
+ next_timeout_sec = 10; /* ... later */
+ } else if (islast) {
+ a->state = 0; /* wrap around */
+ if (a->type == ADVERTISE_DOWN) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: ADVERTISE_DOWN->UP");
+ a->type = ADVERTISE_UP;
+ /* do it all over again right away */
+ } else {
+ u16 r;
+ /*
+ * Start over again after a long timeout
+ * (see notes above)
+ */
+ next_timeout_msec = 0;
+ os_get_random((void *) &r, sizeof(r));
+ next_timeout_sec = UPNP_CACHE_SEC / 4 +
+ (((UPNP_CACHE_SEC / 4) * r) >> 16);
+ sm->advertise_count++;
+ wpa_printf(MSG_DEBUG, "WPS UPnP: ADVERTISE_UP (#%u); "
+ "next in %d sec",
+ sm->advertise_count, next_timeout_sec);
+ }
+ } else {
+ a->state++;
+ }
+
+ wpabuf_free(msg);
+
+ eloop_register_timeout(next_timeout_sec, next_timeout_msec,
+ advertisement_state_machine_handler, NULL, sm);
+}
+
+
+/**
+ * advertisement_state_machine_start - Start SSDP advertisements
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * Returns: 0 on success, -1 on failure
+ */
+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, 0);
+
+ /*
+ * Start out advertising down, this automatically switches
+ * to advertising up which signals our restart.
+ */
+ a->type = ADVERTISE_DOWN;
+ a->state = 0;
+ /* (other fields not used here) */
+
+ /* First timeout should be random interval < 100 msec */
+ next_timeout_msec = (100 * (os_random() & 0xFF)) >> 8;
+ return eloop_register_timeout(0, next_timeout_msec,
+ advertisement_state_machine_handler,
+ NULL, sm);
+}
+
+
+/***************************************************************************
+ * M-SEARCH replies
+ * These are very similar to the multicast advertisements, with some
+ * small changes in data content; and they are sent (UDP) to a specific
+ * unicast address instead of multicast.
+ * They are sent in response to a UDP M-SEARCH packet.
+ **************************************************************************/
+
+/**
+ * msearchreply_state_machine_stop - Stop M-SEARCH reply state machine
+ * @a: Selected advertisement/reply state
+ */
+void msearchreply_state_machine_stop(struct advertisement_state_machine *a)
+{
+ wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH stop");
+ dl_list_del(&a->list);
+ os_free(a);
+}
+
+
+static void msearchreply_state_machine_handler(void *eloop_data,
+ void *user_ctx)
+{
+ struct advertisement_state_machine *a = user_ctx;
+ struct upnp_wps_device_sm *sm = eloop_data;
+ struct wpabuf *msg;
+ int next_timeout_msec = 100;
+ int next_timeout_sec = 0;
+ int islast = 0;
+
+ /*
+ * Each response is sent twice (in case lost) w/ 100 msec delay
+ * between; spec says no more than 3 times.
+ * One pair for rootdevice, one pair for uuid, and a pair each for
+ * each of the two urns.
+ */
+
+ /* TODO: should only send the requested response types */
+
+ wpa_printf(MSG_MSGDUMP, "WPS UPnP: M-SEARCH reply state=%d (%s:%d)",
+ a->state, inet_ntoa(a->client.sin_addr),
+ ntohs(a->client.sin_port));
+ msg = next_advertisement(sm, a, &islast);
+ if (msg == NULL)
+ return;
+
+ /*
+ * Send it on the multicast socket to avoid having to set up another
+ * socket.
+ */
+ if (sendto(sm->multicast_sd, wpabuf_head(msg), wpabuf_len(msg), 0,
+ (struct sockaddr *) &a->client, sizeof(a->client)) < 0) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH reply sendto "
+ "errno %d (%s) for %s:%d",
+ errno, strerror(errno),
+ inet_ntoa(a->client.sin_addr),
+ ntohs(a->client.sin_port));
+ /* Ignore error and hope for the best */
+ }
+ wpabuf_free(msg);
+ if (islast) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH reply done");
+ msearchreply_state_machine_stop(a);
+ return;
+ }
+ a->state++;
+
+ wpa_printf(MSG_MSGDUMP, "WPS UPnP: M-SEARCH reply in %d.%03d sec",
+ next_timeout_sec, next_timeout_msec);
+ eloop_register_timeout(next_timeout_sec, next_timeout_msec,
+ msearchreply_state_machine_handler, sm, a);
+}
+
+
+/**
+ * msearchreply_state_machine_start - Reply to M-SEARCH discovery request
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * @client: Client address
+ * @mx: Maximum delay in seconds
+ *
+ * Use TTL of 4 (this was done when socket set up).
+ * A response should be given in randomized portion of min(MX,120) seconds
+ *
+ * UPnP-arch-DeviceArchitecture, 1.2.3:
+ * To be found, a device must send a UDP response to the source IP address and
+ * port that sent the request to the multicast channel. Devices respond if the
+ * ST header of the M-SEARCH request is "ssdp:all", "upnp:rootdevice", "uuid:"
+ * followed by a UUID that exactly matches one advertised by the device.
+ */
+static void msearchreply_state_machine_start(struct upnp_wps_device_sm *sm,
+ struct sockaddr_in *client,
+ int mx)
+{
+ struct advertisement_state_machine *a;
+ int next_timeout_sec;
+ int next_timeout_msec;
+ int replies;
+
+ replies = dl_list_len(&sm->msearch_replies);
+ wpa_printf(MSG_DEBUG, "WPS UPnP: M-SEARCH reply start (%d "
+ "outstanding)", replies);
+ if (replies >= MAX_MSEARCH) {
+ wpa_printf(MSG_INFO, "WPS UPnP: Too many outstanding "
+ "M-SEARCH replies");
+ return;
+ }
+
+ a = os_zalloc(sizeof(*a));
+ if (a == NULL)
+ return;
+ a->type = MSEARCH_REPLY;
+ a->state = 0;
+ 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;
+ next_timeout_msec = next_timeout_msec % 1000;
+ if (eloop_register_timeout(next_timeout_sec, next_timeout_msec,
+ msearchreply_state_machine_handler, sm,
+ a)) {
+ /* No way to recover (from malloc failure) */
+ goto fail;
+ }
+ /* Remember for future cleanup */
+ dl_list_add(&sm->msearch_replies, &a->list);
+ return;
+
+fail:
+ wpa_printf(MSG_INFO, "WPS UPnP: M-SEARCH reply failure!");
+ eloop_cancel_timeout(msearchreply_state_machine_handler, sm, a);
+ os_free(a);
+}
+
+
+/**
+ * ssdp_parse_msearch - Process a received M-SEARCH
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * @client: Client address
+ * @data: NULL terminated M-SEARCH message
+ *
+ * Given that we have received a header w/ M-SEARCH, act upon it
+ *
+ * Format of M-SEARCH (case insensitive!):
+ *
+ * First line must be:
+ * M-SEARCH * HTTP/1.1
+ * Other lines in arbitrary order:
+ * HOST:239.255.255.250:1900
+ * ST:<varies -- must match>
+ * MAN:"ssdp:discover"
+ * MX:<varies>
+ *
+ * It should be noted that when Microsoft Vista is still learning its IP
+ * address, it sends out host lines like: HOST:[FF02::C]:1900
+ */
+static void ssdp_parse_msearch(struct upnp_wps_device_sm *sm,
+ struct sockaddr_in *client, const char *data)
+{
+#ifndef CONFIG_NO_STDOUT_DEBUG
+ const char *start = data;
+#endif /* CONFIG_NO_STDOUT_DEBUG */
+ const char *end;
+ int got_host = 0;
+ int got_st = 0, st_match = 0;
+ int got_man = 0;
+ int got_mx = 0;
+ int mx = 0;
+
+ /*
+ * Skip first line M-SEARCH * HTTP/1.1
+ * (perhaps we should check remainder of the line for syntax)
+ */
+ data += line_length(data);
+
+ /* Parse remaining lines */
+ for (; *data != '\0'; data += line_length(data)) {
+ end = data + line_length_stripped(data);
+ if (token_eq(data, "host")) {
+ /* The host line indicates who the packet
+ * is addressed to... but do we really care?
+ * Note that Microsoft sometimes does funny
+ * stuff with the HOST: line.
+ */
+#if 0 /* could be */
+ data += token_length(data);
+ data += word_separation_length(data);
+ if (*data != ':')
+ goto bad;
+ data++;
+ data += word_separation_length(data);
+ /* UPNP_MULTICAST_ADDRESS */
+ if (!str_starts(data, "239.255.255.250"))
+ goto bad;
+ data += os_strlen("239.255.255.250");
+ if (*data == ':') {
+ if (!str_starts(data, ":1900"))
+ goto bad;
+ }
+#endif /* could be */
+ got_host = 1;
+ continue;
+ } else if (token_eq(data, "st")) {
+ /* There are a number of forms; we look
+ * for one that matches our case.
+ */
+ got_st = 1;
+ data += token_length(data);
+ data += word_separation_length(data);
+ if (*data != ':')
+ continue;
+ data++;
+ data += word_separation_length(data);
+ if (str_starts(data, "ssdp:all")) {
+ st_match = 1;
+ continue;
+ }
+ if (str_starts(data, "upnp:rootdevice")) {
+ st_match = 1;
+ continue;
+ }
+ if (str_starts(data, "uuid:")) {
+ char uuid_string[80];
+ struct upnp_wps_device_interface *iface;
+ iface = dl_list_first(
+ &sm->interfaces,
+ struct upnp_wps_device_interface,
+ list);
+ data += os_strlen("uuid:");
+ uuid_bin2str(iface->wps->uuid, uuid_string,
+ sizeof(uuid_string));
+ if (str_starts(data, uuid_string))
+ st_match = 1;
+ continue;
+ }
+#if 0
+ /* FIX: should we really reply to IGD string? */
+ if (str_starts(data, "urn:schemas-upnp-org:device:"
+ "InternetGatewayDevice:1")) {
+ st_match = 1;
+ continue;
+ }
+#endif
+ if (str_starts(data, "urn:schemas-wifialliance-org:"
+ "service:WFAWLANConfig:1")) {
+ st_match = 1;
+ continue;
+ }
+ if (str_starts(data, "urn:schemas-wifialliance-org:"
+ "device:WFADevice:1")) {
+ st_match = 1;
+ continue;
+ }
+ continue;
+ } else if (token_eq(data, "man")) {
+ data += token_length(data);
+ data += word_separation_length(data);
+ if (*data != ':')
+ continue;
+ data++;
+ data += word_separation_length(data);
+ if (!str_starts(data, "\"ssdp:discover\"")) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Unexpected "
+ "M-SEARCH man-field");
+ goto bad;
+ }
+ got_man = 1;
+ continue;
+ } else if (token_eq(data, "mx")) {
+ data += token_length(data);
+ data += word_separation_length(data);
+ if (*data != ':')
+ continue;
+ data++;
+ data += word_separation_length(data);
+ mx = atol(data);
+ got_mx = 1;
+ continue;
+ }
+ /* ignore anything else */
+ }
+ if (!got_host || !got_st || !got_man || !got_mx || mx < 0) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Invalid M-SEARCH: %d %d %d "
+ "%d mx=%d", got_host, got_st, got_man, got_mx, mx);
+ goto bad;
+ }
+ if (!st_match) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Ignored M-SEARCH (no ST "
+ "match)");
+ return;
+ }
+ if (mx > 120)
+ mx = 120; /* UPnP-arch-DeviceArchitecture, 1.2.3 */
+ msearchreply_state_machine_start(sm, client, mx);
+ return;
+
+bad:
+ wpa_printf(MSG_INFO, "WPS UPnP: Failed to parse M-SEARCH");
+ wpa_printf(MSG_MSGDUMP, "WPS UPnP: M-SEARCH data:\n%s", start);
+}
+
+
+/* Listening for (UDP) discovery (M-SEARCH) packets */
+
+/**
+ * ssdp_listener_stop - Stop SSDP listered
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ *
+ * This function stops the SSDP listener that was started by calling
+ * ssdp_listener_start().
+ */
+void ssdp_listener_stop(struct upnp_wps_device_sm *sm)
+{
+ if (sm->ssdp_sd_registered) {
+ eloop_unregister_sock(sm->ssdp_sd, EVENT_TYPE_READ);
+ sm->ssdp_sd_registered = 0;
+ }
+
+ if (sm->ssdp_sd != -1) {
+ close(sm->ssdp_sd);
+ sm->ssdp_sd = -1;
+ }
+
+ eloop_cancel_timeout(msearchreply_state_machine_handler, sm,
+ ELOOP_ALL_CTX);
+}
+
+
+static void ssdp_listener_handler(int sd, void *eloop_ctx, void *sock_ctx)
+{
+ struct upnp_wps_device_sm *sm = sock_ctx;
+ struct sockaddr_in addr; /* client address */
+ socklen_t addr_len;
+ int nread;
+ char buf[MULTICAST_MAX_READ], *pos;
+
+ addr_len = sizeof(addr);
+ nread = recvfrom(sm->ssdp_sd, buf, sizeof(buf) - 1, 0,
+ (struct sockaddr *) &addr, &addr_len);
+ if (nread <= 0)
+ return;
+ buf[nread] = '\0'; /* need null termination for algorithm */
+
+ if (str_starts(buf, "NOTIFY ")) {
+ /*
+ * Silently ignore NOTIFYs to avoid filling debug log with
+ * unwanted messages.
+ */
+ return;
+ }
+
+ pos = os_strchr(buf, '\n');
+ if (pos)
+ *pos = '\0';
+ wpa_printf(MSG_MSGDUMP, "WPS UPnP: Received SSDP packet from %s:%d: "
+ "%s", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), buf);
+ if (pos)
+ *pos = '\n';
+
+ /* Parse first line */
+ if (os_strncasecmp(buf, "M-SEARCH", os_strlen("M-SEARCH")) == 0 &&
+ !isgraph(buf[strlen("M-SEARCH")])) {
+ ssdp_parse_msearch(sm, &addr, buf);
+ return;
+ }
+
+ /* Ignore anything else */
+}
+
+
+int ssdp_listener_open(void)
+{
+ struct sockaddr_in addr;
+ struct ip_mreq mcast_addr;
+ int on = 1;
+ /* per UPnP spec, keep IP packet time to live (TTL) small */
+ unsigned char ttl = 4;
+ int sd;
+
+ sd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sd < 0)
+ goto fail;
+ if (fcntl(sd, F_SETFL, O_NONBLOCK) != 0)
+ goto fail;
+ if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)))
+ goto fail;
+ os_memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ addr.sin_port = htons(UPNP_MULTICAST_PORT);
+ if (bind(sd, (struct sockaddr *) &addr, sizeof(addr)))
+ goto fail;
+ os_memset(&mcast_addr, 0, sizeof(mcast_addr));
+ mcast_addr.imr_interface.s_addr = htonl(INADDR_ANY);
+ mcast_addr.imr_multiaddr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS);
+ if (setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ (char *) &mcast_addr, sizeof(mcast_addr)))
+ goto fail;
+ if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL,
+ &ttl, sizeof(ttl)))
+ goto fail;
+
+ return sd;
+
+fail:
+ if (sd >= 0)
+ close(sd);
+ return -1;
+}
+
+
+/**
+ * ssdp_listener_start - Set up for receiving discovery (UDP) packets
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * Returns: 0 on success, -1 on failure
+ *
+ * The SSDP listener is stopped by calling ssdp_listener_stop().
+ */
+int ssdp_listener_start(struct upnp_wps_device_sm *sm)
+{
+ sm->ssdp_sd = ssdp_listener_open();
+
+ if (eloop_register_sock(sm->ssdp_sd, EVENT_TYPE_READ,
+ ssdp_listener_handler, NULL, sm))
+ goto fail;
+ sm->ssdp_sd_registered = 1;
+ return 0;
+
+fail:
+ /* Error */
+ wpa_printf(MSG_ERROR, "WPS UPnP: ssdp_listener_start failed");
+ ssdp_listener_stop(sm);
+ return -1;
+}
+
+
+/**
+ * add_ssdp_network - Add routing entry for SSDP
+ * @net_if: Selected network interface name
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function assures that the multicast address will be properly
+ * handled by Linux networking code (by a modification to routing tables).
+ * This must be done per network interface. It really only needs to be done
+ * once after booting up, but it does not hurt to call this more frequently
+ * "to be safe".
+ */
+int add_ssdp_network(const char *net_if)
+{
+#ifdef __linux__
+ int ret = -1;
+ int sock = -1;
+ struct rtentry rt;
+ struct sockaddr_in *sin;
+
+ if (!net_if)
+ goto fail;
+
+ os_memset(&rt, 0, sizeof(rt));
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0)
+ goto fail;
+
+ rt.rt_dev = (char *) net_if;
+ 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 = 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);
+ rt.rt_flags = RTF_UP;
+ if (ioctl(sock, SIOCADDRT, &rt) < 0) {
+ if (errno == EPERM) {
+ wpa_printf(MSG_DEBUG, "add_ssdp_network: No "
+ "permissions to add routing table entry");
+ /* Continue to allow testing as non-root */
+ } else if (errno != EEXIST) {
+ wpa_printf(MSG_INFO, "add_ssdp_network() ioctl errno "
+ "%d (%s)", errno, strerror(errno));
+ goto fail;
+ }
+ }
+
+ ret = 0;
+
+fail:
+ if (sock >= 0)
+ close(sock);
+
+ return ret;
+#else /* __linux__ */
+ return 0;
+#endif /* __linux__ */
+}
+
+
+int ssdp_open_multicast_sock(u32 ip_addr)
+{
+ int sd;
+ /* per UPnP-arch-DeviceArchitecture, 1. Discovery, keep IP packet
+ * time to live (TTL) small */
+ unsigned char ttl = 4;
+
+ sd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sd < 0)
+ return -1;
+
+#if 0 /* maybe ok if we sometimes block on writes */
+ if (fcntl(sd, F_SETFL, O_NONBLOCK) != 0)
+ return -1;
+#endif
+
+ if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF,
+ &ip_addr, sizeof(ip_addr))) {
+ wpa_printf(MSG_DEBUG, "WPS: setsockopt(IP_MULTICAST_IF) %x: "
+ "%d (%s)", ip_addr, errno, strerror(errno));
+ return -1;
+ }
+ if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_TTL,
+ &ttl, sizeof(ttl))) {
+ wpa_printf(MSG_DEBUG, "WPS: setsockopt(IP_MULTICAST_TTL): "
+ "%d (%s)", errno, strerror(errno));
+ return -1;
+ }
+
+#if 0 /* not needed, because we don't receive using multicast_sd */
+ {
+ struct ip_mreq mreq;
+ mreq.imr_multiaddr.s_addr = inet_addr(UPNP_MULTICAST_ADDRESS);
+ mreq.imr_interface.s_addr = ip_addr;
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Multicast addr 0x%x if addr "
+ "0x%x",
+ mreq.imr_multiaddr.s_addr,
+ mreq.imr_interface.s_addr);
+ if (setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
+ sizeof(mreq))) {
+ wpa_printf(MSG_ERROR,
+ "WPS UPnP: setsockopt "
+ "IP_ADD_MEMBERSHIP errno %d (%s)",
+ errno, strerror(errno));
+ return -1;
+ }
+ }
+#endif /* not needed */
+
+ /*
+ * TODO: What about IP_MULTICAST_LOOP? It seems to be on by default?
+ * which aids debugging I suppose but isn't really necessary?
+ */
+
+ return sd;
+}
+
+
+/**
+ * ssdp_open_multicast - Open socket for sending multicast SSDP messages
+ * @sm: WPS UPnP state machine from upnp_wps_device_init()
+ * Returns: 0 on success, -1 on failure
+ */
+int ssdp_open_multicast(struct upnp_wps_device_sm *sm)
+{
+ sm->multicast_sd = ssdp_open_multicast_sock(sm->ip_addr);
+ if (sm->multicast_sd < 0)
+ return -1;
+ return 0;
+}
diff --git a/src/wps/wps_upnp_web.c b/src/wps/wps_upnp_web.c
new file mode 100644
index 0000000..917f60b
--- /dev/null
+++ b/src/wps/wps_upnp_web.c
@@ -0,0 +1,1324 @@
+/*
+ * UPnP WPS Device - Web connections
+ * Copyright (c) 2000-2003 Intel Corporation
+ * Copyright (c) 2006-2007 Sony Corporation
+ * Copyright (c) 2008-2009 Atheros Communications
+ * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
+ *
+ * See wps_upnp.c for more details on licensing and code history.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "base64.h"
+#include "uuid.h"
+#include "httpread.h"
+#include "http_server.h"
+#include "wps_i.h"
+#include "wps_upnp.h"
+#include "wps_upnp_i.h"
+#include "upnp_xml.h"
+
+/***************************************************************************
+ * Web connections (we serve pages of info about ourselves, handle
+ * requests, etc. etc.).
+ **************************************************************************/
+
+#define WEB_CONNECTION_TIMEOUT_SEC 30 /* Drop web connection after t.o. */
+#define WEB_CONNECTION_MAX_READ 8000 /* Max we'll read for TCP request */
+#define MAX_WEB_CONNECTIONS 10 /* max simultaneous web connects */
+
+
+static const char *urn_wfawlanconfig =
+ "urn:schemas-wifialliance-org:service:WFAWLANConfig:1";
+static const char *http_server_hdr =
+ "Server: unspecified, UPnP/1.0, unspecified\r\n";
+static const char *http_connection_close =
+ "Connection: close\r\n";
+
+/*
+ * "Files" that we serve via HTTP. The format of these files is given by
+ * WFA WPS specifications. Extra white space has been removed to save space.
+ */
+
+static const char wps_scpd_xml[] =
+"<?xml version=\"1.0\"?>\n"
+"<scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">\n"
+"<specVersion><major>1</major><minor>0</minor></specVersion>\n"
+"<actionList>\n"
+"<action>\n"
+"<name>GetDeviceInfo</name>\n"
+"<argumentList>\n"
+"<argument>\n"
+"<name>NewDeviceInfo</name>\n"
+"<direction>out</direction>\n"
+"<relatedStateVariable>DeviceInfo</relatedStateVariable>\n"
+"</argument>\n"
+"</argumentList>\n"
+"</action>\n"
+"<action>\n"
+"<name>PutMessage</name>\n"
+"<argumentList>\n"
+"<argument>\n"
+"<name>NewInMessage</name>\n"
+"<direction>in</direction>\n"
+"<relatedStateVariable>InMessage</relatedStateVariable>\n"
+"</argument>\n"
+"<argument>\n"
+"<name>NewOutMessage</name>\n"
+"<direction>out</direction>\n"
+"<relatedStateVariable>OutMessage</relatedStateVariable>\n"
+"</argument>\n"
+"</argumentList>\n"
+"</action>\n"
+"<action>\n"
+"<name>PutWLANResponse</name>\n"
+"<argumentList>\n"
+"<argument>\n"
+"<name>NewMessage</name>\n"
+"<direction>in</direction>\n"
+"<relatedStateVariable>Message</relatedStateVariable>\n"
+"</argument>\n"
+"<argument>\n"
+"<name>NewWLANEventType</name>\n"
+"<direction>in</direction>\n"
+"<relatedStateVariable>WLANEventType</relatedStateVariable>\n"
+"</argument>\n"
+"<argument>\n"
+"<name>NewWLANEventMAC</name>\n"
+"<direction>in</direction>\n"
+"<relatedStateVariable>WLANEventMAC</relatedStateVariable>\n"
+"</argument>\n"
+"</argumentList>\n"
+"</action>\n"
+"<action>\n"
+"<name>SetSelectedRegistrar</name>\n"
+"<argumentList>\n"
+"<argument>\n"
+"<name>NewMessage</name>\n"
+"<direction>in</direction>\n"
+"<relatedStateVariable>Message</relatedStateVariable>\n"
+"</argument>\n"
+"</argumentList>\n"
+"</action>\n"
+"</actionList>\n"
+"<serviceStateTable>\n"
+"<stateVariable sendEvents=\"no\">\n"
+"<name>Message</name>\n"
+"<dataType>bin.base64</dataType>\n"
+"</stateVariable>\n"
+"<stateVariable sendEvents=\"no\">\n"
+"<name>InMessage</name>\n"
+"<dataType>bin.base64</dataType>\n"
+"</stateVariable>\n"
+"<stateVariable sendEvents=\"no\">\n"
+"<name>OutMessage</name>\n"
+"<dataType>bin.base64</dataType>\n"
+"</stateVariable>\n"
+"<stateVariable sendEvents=\"no\">\n"
+"<name>DeviceInfo</name>\n"
+"<dataType>bin.base64</dataType>\n"
+"</stateVariable>\n"
+"<stateVariable sendEvents=\"yes\">\n"
+"<name>APStatus</name>\n"
+"<dataType>ui1</dataType>\n"
+"</stateVariable>\n"
+"<stateVariable sendEvents=\"yes\">\n"
+"<name>STAStatus</name>\n"
+"<dataType>ui1</dataType>\n"
+"</stateVariable>\n"
+"<stateVariable sendEvents=\"yes\">\n"
+"<name>WLANEvent</name>\n"
+"<dataType>bin.base64</dataType>\n"
+"</stateVariable>\n"
+"<stateVariable sendEvents=\"no\">\n"
+"<name>WLANEventType</name>\n"
+"<dataType>ui1</dataType>\n"
+"</stateVariable>\n"
+"<stateVariable sendEvents=\"no\">\n"
+"<name>WLANEventMAC</name>\n"
+"<dataType>string</dataType>\n"
+"</stateVariable>\n"
+"<stateVariable sendEvents=\"no\">\n"
+"<name>WLANResponse</name>\n"
+"<dataType>bin.base64</dataType>\n"
+"</stateVariable>\n"
+"</serviceStateTable>\n"
+"</scpd>\n"
+;
+
+
+static const char *wps_device_xml_prefix =
+ "<?xml version=\"1.0\"?>\n"
+ "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">\n"
+ "<specVersion>\n"
+ "<major>1</major>\n"
+ "<minor>0</minor>\n"
+ "</specVersion>\n"
+ "<device>\n"
+ "<deviceType>urn:schemas-wifialliance-org:device:WFADevice:1"
+ "</deviceType>\n";
+
+static const char *wps_device_xml_postfix =
+ "<serviceList>\n"
+ "<service>\n"
+ "<serviceType>urn:schemas-wifialliance-org:service:WFAWLANConfig:1"
+ "</serviceType>\n"
+ "<serviceId>urn:wifialliance-org:serviceId:WFAWLANConfig1</serviceId>"
+ "\n"
+ "<SCPDURL>" UPNP_WPS_SCPD_XML_FILE "</SCPDURL>\n"
+ "<controlURL>" UPNP_WPS_DEVICE_CONTROL_FILE "</controlURL>\n"
+ "<eventSubURL>" UPNP_WPS_DEVICE_EVENT_FILE "</eventSubURL>\n"
+ "</service>\n"
+ "</serviceList>\n"
+ "</device>\n"
+ "</root>\n";
+
+
+/* format_wps_device_xml -- produce content of "file" wps_device.xml
+ * (UPNP_WPS_DEVICE_XML_FILE)
+ */
+static void format_wps_device_xml(struct upnp_wps_device_sm *sm,
+ struct wpabuf *buf)
+{
+ const char *s;
+ char uuid_string[80];
+ struct upnp_wps_device_interface *iface;
+
+ iface = dl_list_first(&sm->interfaces,
+ struct upnp_wps_device_interface, list);
+
+ wpabuf_put_str(buf, wps_device_xml_prefix);
+
+ /*
+ * Add required fields with default values if not configured. Add
+ * optional and recommended fields only if configured.
+ */
+ s = iface->wps->friendly_name;
+ s = ((s && *s) ? s : "WPS Access Point");
+ xml_add_tagged_data(buf, "friendlyName", s);
+
+ s = iface->wps->dev.manufacturer;
+ s = ((s && *s) ? s : "");
+ xml_add_tagged_data(buf, "manufacturer", s);
+
+ if (iface->wps->manufacturer_url)
+ xml_add_tagged_data(buf, "manufacturerURL",
+ iface->wps->manufacturer_url);
+
+ if (iface->wps->model_description)
+ xml_add_tagged_data(buf, "modelDescription",
+ iface->wps->model_description);
+
+ s = iface->wps->dev.model_name;
+ s = ((s && *s) ? s : "");
+ xml_add_tagged_data(buf, "modelName", s);
+
+ if (iface->wps->dev.model_number)
+ xml_add_tagged_data(buf, "modelNumber",
+ iface->wps->dev.model_number);
+
+ if (iface->wps->model_url)
+ xml_add_tagged_data(buf, "modelURL", iface->wps->model_url);
+
+ if (iface->wps->dev.serial_number)
+ xml_add_tagged_data(buf, "serialNumber",
+ iface->wps->dev.serial_number);
+
+ uuid_bin2str(iface->wps->uuid, uuid_string, sizeof(uuid_string));
+ s = uuid_string;
+ /* Need "uuid:" prefix, thus we can't use xml_add_tagged_data()
+ * easily...
+ */
+ wpabuf_put_str(buf, "<UDN>uuid:");
+ xml_data_encode(buf, s, os_strlen(s));
+ wpabuf_put_str(buf, "</UDN>\n");
+
+ if (iface->wps->upc)
+ xml_add_tagged_data(buf, "UPC", iface->wps->upc);
+
+ wpabuf_put_str(buf, wps_device_xml_postfix);
+}
+
+
+static void http_put_reply_code(struct wpabuf *buf, enum http_reply_code code)
+{
+ wpabuf_put_str(buf, "HTTP/1.1 ");
+ switch (code) {
+ case HTTP_OK:
+ wpabuf_put_str(buf, "200 OK\r\n");
+ break;
+ case HTTP_BAD_REQUEST:
+ wpabuf_put_str(buf, "400 Bad request\r\n");
+ break;
+ case HTTP_PRECONDITION_FAILED:
+ wpabuf_put_str(buf, "412 Precondition failed\r\n");
+ break;
+ case HTTP_UNIMPLEMENTED:
+ wpabuf_put_str(buf, "501 Unimplemented\r\n");
+ break;
+ case HTTP_INTERNAL_SERVER_ERROR:
+ default:
+ wpabuf_put_str(buf, "500 Internal server error\r\n");
+ break;
+ }
+}
+
+
+static void http_put_date(struct wpabuf *buf)
+{
+ wpabuf_put_str(buf, "Date: ");
+ format_date(buf);
+ wpabuf_put_str(buf, "\r\n");
+}
+
+
+static void http_put_empty(struct wpabuf *buf, enum http_reply_code code)
+{
+ http_put_reply_code(buf, code);
+ wpabuf_put_str(buf, http_server_hdr);
+ wpabuf_put_str(buf, http_connection_close);
+ wpabuf_put_str(buf, "Content-Length: 0\r\n"
+ "\r\n");
+}
+
+
+/* Given that we have received a header w/ GET, act upon it
+ *
+ * Format of GET (case-insensitive):
+ *
+ * First line must be:
+ * GET /<file> HTTP/1.1
+ * Since we don't do anything fancy we just ignore other lines.
+ *
+ * Our response (if no error) which includes only required lines is:
+ * HTTP/1.1 200 OK
+ * Connection: close
+ * Content-Type: text/xml
+ * Date: <rfc1123-date>
+ *
+ * Header lines must end with \r\n
+ * Per RFC 2616, content-length: is not required but connection:close
+ * would appear to be required (given that we will be closing it!).
+ */
+static void web_connection_parse_get(struct upnp_wps_device_sm *sm,
+ struct http_request *hreq, char *filename)
+{
+ struct wpabuf *buf; /* output buffer, allocated */
+ char *put_length_here;
+ char *body_start;
+ enum {
+ GET_DEVICE_XML_FILE,
+ GET_SCPD_XML_FILE
+ } req;
+ size_t extra_len = 0;
+ int body_length;
+ char len_buf[10];
+ struct upnp_wps_device_interface *iface;
+
+ iface = dl_list_first(&sm->interfaces,
+ struct upnp_wps_device_interface, list);
+
+ /*
+ * It is not required that filenames be case insensitive but it is
+ * allowed and cannot hurt here.
+ */
+ if (filename == NULL)
+ filename = "(null)"; /* just in case */
+ if (os_strcasecmp(filename, UPNP_WPS_DEVICE_XML_FILE) == 0) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for device XML");
+ req = GET_DEVICE_XML_FILE;
+ extra_len = 3000;
+ if (iface->wps->friendly_name)
+ extra_len += os_strlen(iface->wps->friendly_name);
+ if (iface->wps->manufacturer_url)
+ extra_len += os_strlen(iface->wps->manufacturer_url);
+ if (iface->wps->model_description)
+ extra_len += os_strlen(iface->wps->model_description);
+ if (iface->wps->model_url)
+ extra_len += os_strlen(iface->wps->model_url);
+ if (iface->wps->upc)
+ extra_len += os_strlen(iface->wps->upc);
+ } else if (!os_strcasecmp(filename, UPNP_WPS_SCPD_XML_FILE)) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for SCPD XML");
+ req = GET_SCPD_XML_FILE;
+ extra_len = os_strlen(wps_scpd_xml);
+ } else {
+ /* File not found */
+ wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET file not found: %s",
+ filename);
+ buf = wpabuf_alloc(200);
+ if (buf == NULL) {
+ http_request_deinit(hreq);
+ return;
+ }
+ wpabuf_put_str(buf,
+ "HTTP/1.1 404 Not Found\r\n"
+ "Connection: close\r\n");
+
+ http_put_date(buf);
+
+ /* terminating empty line */
+ wpabuf_put_str(buf, "\r\n");
+
+ goto send_buf;
+ }
+
+ buf = wpabuf_alloc(1000 + extra_len);
+ if (buf == NULL) {
+ http_request_deinit(hreq);
+ return;
+ }
+
+ wpabuf_put_str(buf,
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/xml; charset=\"utf-8\"\r\n");
+ wpabuf_put_str(buf, "Server: Unspecified, UPnP/1.0, Unspecified\r\n");
+ wpabuf_put_str(buf, "Connection: close\r\n");
+ wpabuf_put_str(buf, "Content-Length: ");
+ /*
+ * We will paste the length in later, leaving some extra whitespace.
+ * HTTP code is supposed to be tolerant of extra whitespace.
+ */
+ put_length_here = wpabuf_put(buf, 0);
+ wpabuf_put_str(buf, " \r\n");
+
+ http_put_date(buf);
+
+ /* terminating empty line */
+ wpabuf_put_str(buf, "\r\n");
+
+ body_start = wpabuf_put(buf, 0);
+
+ switch (req) {
+ case GET_DEVICE_XML_FILE:
+ format_wps_device_xml(sm, buf);
+ break;
+ case GET_SCPD_XML_FILE:
+ wpabuf_put_str(buf, wps_scpd_xml);
+ break;
+ }
+
+ /* Now patch in the content length at the end */
+ body_length = (char *) wpabuf_put(buf, 0) - body_start;
+ os_snprintf(len_buf, 10, "%d", body_length);
+ os_memcpy(put_length_here, len_buf, os_strlen(len_buf));
+
+send_buf:
+ http_request_send_and_deinit(hreq, buf);
+}
+
+
+static enum http_reply_code
+web_process_get_device_info(struct upnp_wps_device_sm *sm,
+ struct wpabuf **reply, const char **replyname)
+{
+ static const char *name = "NewDeviceInfo";
+ struct wps_config cfg;
+ struct upnp_wps_device_interface *iface;
+ struct upnp_wps_peer *peer;
+
+ iface = dl_list_first(&sm->interfaces,
+ struct upnp_wps_device_interface, list);
+ peer = &iface->peer;
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: GetDeviceInfo");
+
+ if (iface->ctx->ap_pin == NULL)
+ return HTTP_INTERNAL_SERVER_ERROR;
+
+ /*
+ * Request for DeviceInfo, i.e., M1 TLVs. This is a start of WPS
+ * registration over UPnP with the AP acting as an Enrollee. It should
+ * be noted that this is frequently used just to get the device data,
+ * i.e., there may not be any intent to actually complete the
+ * registration.
+ */
+
+ if (peer->wps)
+ wps_deinit(peer->wps);
+
+ os_memset(&cfg, 0, sizeof(cfg));
+ cfg.wps = iface->wps;
+ cfg.pin = (u8 *) iface->ctx->ap_pin;
+ cfg.pin_len = os_strlen(iface->ctx->ap_pin);
+ peer->wps = wps_init(&cfg);
+ if (peer->wps) {
+ enum wsc_op_code op_code;
+ *reply = wps_get_msg(peer->wps, &op_code);
+ if (*reply == NULL) {
+ wps_deinit(peer->wps);
+ peer->wps = NULL;
+ }
+ } else
+ *reply = NULL;
+ if (*reply == NULL) {
+ wpa_printf(MSG_INFO, "WPS UPnP: Failed to get DeviceInfo");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ *replyname = name;
+ return HTTP_OK;
+}
+
+
+static enum http_reply_code
+web_process_put_message(struct upnp_wps_device_sm *sm, char *data,
+ struct wpabuf **reply, const char **replyname)
+{
+ struct wpabuf *msg;
+ static const char *name = "NewOutMessage";
+ enum http_reply_code ret;
+ enum wps_process_res res;
+ enum wsc_op_code op_code;
+ struct upnp_wps_device_interface *iface;
+
+ iface = dl_list_first(&sm->interfaces,
+ struct upnp_wps_device_interface, list);
+
+ /*
+ * PutMessage is used by external UPnP-based Registrar to perform WPS
+ * operation with the access point itself; as compared with
+ * PutWLANResponse which is for proxying.
+ */
+ wpa_printf(MSG_DEBUG, "WPS UPnP: PutMessage");
+ msg = xml_get_base64_item(data, "NewInMessage", &ret);
+ if (msg == NULL)
+ return ret;
+ res = wps_process_msg(iface->peer.wps, WSC_UPnP, msg);
+ if (res == WPS_FAILURE)
+ *reply = NULL;
+ else
+ *reply = wps_get_msg(iface->peer.wps, &op_code);
+ wpabuf_free(msg);
+ if (*reply == NULL)
+ return HTTP_INTERNAL_SERVER_ERROR;
+ *replyname = name;
+ return HTTP_OK;
+}
+
+
+static enum http_reply_code
+web_process_put_wlan_response(struct upnp_wps_device_sm *sm, char *data,
+ struct wpabuf **reply, const char **replyname)
+{
+ struct wpabuf *msg;
+ enum http_reply_code ret;
+ u8 macaddr[ETH_ALEN];
+ int ev_type;
+ int type;
+ char *val;
+ struct upnp_wps_device_interface *iface;
+ int ok = 0;
+
+ /*
+ * External UPnP-based Registrar is passing us a message to be proxied
+ * over to a Wi-Fi -based client of ours.
+ */
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: PutWLANResponse");
+ msg = xml_get_base64_item(data, "NewMessage", &ret);
+ if (msg == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Could not extract NewMessage "
+ "from PutWLANResponse");
+ return ret;
+ }
+ val = xml_get_first_item(data, "NewWLANEventType");
+ if (val == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: No NewWLANEventType in "
+ "PutWLANResponse");
+ wpabuf_free(msg);
+ return UPNP_ARG_VALUE_INVALID;
+ }
+ ev_type = atol(val);
+ os_free(val);
+ val = xml_get_first_item(data, "NewWLANEventMAC");
+ if (val == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: No NewWLANEventMAC in "
+ "PutWLANResponse");
+ wpabuf_free(msg);
+ return UPNP_ARG_VALUE_INVALID;
+ }
+ if (hwaddr_aton(val, macaddr)) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Invalid NewWLANEventMAC in "
+ "PutWLANResponse: '%s'", val);
+#ifdef CONFIG_WPS_STRICT
+ {
+ struct wps_parse_attr attr;
+ if (wps_parse_msg(msg, &attr) < 0 || attr.version2) {
+ wpabuf_free(msg);
+ os_free(val);
+ return UPNP_ARG_VALUE_INVALID;
+ }
+ }
+#endif /* CONFIG_WPS_STRICT */
+ if (hwaddr_aton2(val, macaddr) > 0) {
+ /*
+ * At least some versions of Intel PROset seem to be
+ * using dot-deliminated MAC address format here.
+ */
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Workaround - allow "
+ "incorrect MAC address format in "
+ "NewWLANEventMAC: %s -> " MACSTR,
+ val, MAC2STR(macaddr));
+ } else {
+ wpabuf_free(msg);
+ os_free(val);
+ return UPNP_ARG_VALUE_INVALID;
+ }
+ }
+ os_free(val);
+ if (ev_type == UPNP_WPS_WLANEVENT_TYPE_EAP) {
+ struct wps_parse_attr attr;
+ if (wps_parse_msg(msg, &attr) < 0 ||
+ attr.msg_type == NULL)
+ type = -1;
+ else
+ type = *attr.msg_type;
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Message Type %d", type);
+ } else
+ type = -1;
+ dl_list_for_each(iface, &sm->interfaces,
+ struct upnp_wps_device_interface, list) {
+ if (iface->ctx->rx_req_put_wlan_response &&
+ iface->ctx->rx_req_put_wlan_response(iface->priv, ev_type,
+ macaddr, msg, type)
+ == 0)
+ ok = 1;
+ }
+
+ if (!ok) {
+ wpa_printf(MSG_INFO, "WPS UPnP: Fail: sm->ctx->"
+ "rx_req_put_wlan_response");
+ wpabuf_free(msg);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ wpabuf_free(msg);
+ *replyname = NULL;
+ *reply = NULL;
+ return HTTP_OK;
+}
+
+
+static int find_er_addr(struct subscription *s, struct sockaddr_in *cli)
+{
+ struct subscr_addr *a;
+
+ dl_list_for_each(a, &s->addr_list, struct subscr_addr, list) {
+ if (cli->sin_addr.s_addr == a->saddr.sin_addr.s_addr)
+ return 1;
+ }
+ return 0;
+}
+
+
+static struct subscription * find_er(struct upnp_wps_device_sm *sm,
+ struct sockaddr_in *cli)
+{
+ struct subscription *s;
+ dl_list_for_each(s, &sm->subscriptions, struct subscription, list)
+ if (find_er_addr(s, cli))
+ return s;
+ return NULL;
+}
+
+
+static enum http_reply_code
+web_process_set_selected_registrar(struct upnp_wps_device_sm *sm,
+ struct sockaddr_in *cli, char *data,
+ struct wpabuf **reply,
+ const char **replyname)
+{
+ struct wpabuf *msg;
+ enum http_reply_code ret;
+ struct subscription *s;
+ struct upnp_wps_device_interface *iface;
+ int err = 0;
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: SetSelectedRegistrar");
+ s = find_er(sm, cli);
+ if (s == NULL) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Ignore SetSelectedRegistrar "
+ "from unknown ER");
+ return UPNP_ACTION_FAILED;
+ }
+ msg = xml_get_base64_item(data, "NewMessage", &ret);
+ if (msg == NULL)
+ return ret;
+ dl_list_for_each(iface, &sm->interfaces,
+ struct upnp_wps_device_interface, list) {
+ if (upnp_er_set_selected_registrar(iface->wps->registrar, s,
+ msg))
+ err = 1;
+ }
+ wpabuf_free(msg);
+ if (err)
+ return HTTP_INTERNAL_SERVER_ERROR;
+ *replyname = NULL;
+ *reply = NULL;
+ return HTTP_OK;
+}
+
+
+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 *soap_error_prefix =
+ "<s:Fault>\n"
+ "<faultcode>s:Client</faultcode>\n"
+ "<faultstring>UPnPError</faultstring>\n"
+ "<detail>\n"
+ "<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">\n";
+static const char *soap_error_postfix =
+ "<errorDescription>Error</errorDescription>\n"
+ "</UPnPError>\n"
+ "</detail>\n"
+ "</s:Fault>\n";
+
+static void web_connection_send_reply(struct http_request *req,
+ enum http_reply_code ret,
+ const char *action, int action_len,
+ const struct wpabuf *reply,
+ const char *replyname)
+{
+ struct wpabuf *buf;
+ char *replydata;
+ char *put_length_here = NULL;
+ char *body_start = NULL;
+
+ if (reply) {
+ size_t len;
+ replydata = (char *) base64_encode(wpabuf_head(reply),
+ wpabuf_len(reply), &len);
+ } else
+ replydata = NULL;
+
+ /* Parameters of the response:
+ * action(action_len) -- action we are responding to
+ * replyname -- a name we need for the reply
+ * replydata -- NULL or null-terminated string
+ */
+ buf = wpabuf_alloc(1000 + (replydata ? os_strlen(replydata) : 0U) +
+ (action_len > 0 ? action_len * 2 : 0));
+ if (buf == NULL) {
+ wpa_printf(MSG_INFO, "WPS UPnP: Cannot allocate reply to "
+ "POST");
+ os_free(replydata);
+ http_request_deinit(req);
+ return;
+ }
+
+ /*
+ * Assuming we will be successful, put in the output header first.
+ * Note: we do not keep connections alive (and httpread does
+ * not support it)... therefore we must have Connection: close.
+ */
+ if (ret == HTTP_OK) {
+ wpabuf_put_str(buf,
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/xml; "
+ "charset=\"utf-8\"\r\n");
+ } else {
+ wpabuf_printf(buf, "HTTP/1.1 %d Error\r\n", ret);
+ }
+ wpabuf_put_str(buf, http_connection_close);
+
+ wpabuf_put_str(buf, "Content-Length: ");
+ /*
+ * We will paste the length in later, leaving some extra whitespace.
+ * HTTP code is supposed to be tolerant of extra whitespace.
+ */
+ put_length_here = wpabuf_put(buf, 0);
+ wpabuf_put_str(buf, " \r\n");
+
+ http_put_date(buf);
+
+ /* terminating empty line */
+ wpabuf_put_str(buf, "\r\n");
+
+ body_start = wpabuf_put(buf, 0);
+
+ if (ret == HTTP_OK) {
+ wpabuf_put_str(buf, soap_prefix);
+ wpabuf_put_str(buf, "<u:");
+ wpabuf_put_data(buf, action, action_len);
+ wpabuf_put_str(buf, "Response xmlns:u=\"");
+ wpabuf_put_str(buf, urn_wfawlanconfig);
+ wpabuf_put_str(buf, "\">\n");
+ if (replydata && replyname) {
+ /* TODO: might possibly need to escape part of reply
+ * data? ...
+ * probably not, unlikely to have ampersand(&) or left
+ * angle bracket (<) in it...
+ */
+ wpabuf_printf(buf, "<%s>", replyname);
+ wpabuf_put_str(buf, replydata);
+ wpabuf_printf(buf, "</%s>\n", replyname);
+ }
+ wpabuf_put_str(buf, "</u:");
+ wpabuf_put_data(buf, action, action_len);
+ wpabuf_put_str(buf, "Response>\n");
+ wpabuf_put_str(buf, soap_postfix);
+ } else {
+ /* Error case */
+ wpabuf_put_str(buf, soap_prefix);
+ wpabuf_put_str(buf, soap_error_prefix);
+ wpabuf_printf(buf, "<errorCode>%d</errorCode>\n", ret);
+ wpabuf_put_str(buf, soap_error_postfix);
+ wpabuf_put_str(buf, soap_postfix);
+ }
+ os_free(replydata);
+
+ /* Now patch in the content length at the end */
+ if (body_start && put_length_here) {
+ int body_length = (char *) wpabuf_put(buf, 0) - body_start;
+ char len_buf[10];
+ os_snprintf(len_buf, sizeof(len_buf), "%d", body_length);
+ os_memcpy(put_length_here, len_buf, os_strlen(len_buf));
+ }
+
+ http_request_send_and_deinit(req, buf);
+}
+
+
+static const char * web_get_action(struct http_request *req,
+ size_t *action_len)
+{
+ const char *match;
+ int match_len;
+ char *b;
+ char *action;
+
+ *action_len = 0;
+ /* The SOAPAction line of the header tells us what we want to do */
+ b = http_request_get_hdr_line(req, "SOAPAction:");
+ if (b == NULL)
+ return NULL;
+ if (*b == '"')
+ b++;
+ else
+ return NULL;
+ match = urn_wfawlanconfig;
+ match_len = os_strlen(urn_wfawlanconfig) - 1;
+ if (os_strncasecmp(b, match, match_len))
+ return NULL;
+ b += match_len;
+ /* skip over version */
+ while (isgraph(*b) && *b != '#')
+ b++;
+ if (*b != '#')
+ return NULL;
+ b++;
+ /* Following the sharp(#) should be the action and a double quote */
+ action = b;
+ while (isgraph(*b) && *b != '"')
+ b++;
+ if (*b != '"')
+ return NULL;
+ *action_len = b - action;
+ return action;
+}
+
+
+/* Given that we have received a header w/ POST, act upon it
+ *
+ * Format of POST (case-insensitive):
+ *
+ * First line must be:
+ * POST /<file> HTTP/1.1
+ * Since we don't do anything fancy we just ignore other lines.
+ *
+ * Our response (if no error) which includes only required lines is:
+ * HTTP/1.1 200 OK
+ * Connection: close
+ * Content-Type: text/xml
+ * Date: <rfc1123-date>
+ *
+ * Header lines must end with \r\n
+ * Per RFC 2616, content-length: is not required but connection:close
+ * would appear to be required (given that we will be closing it!).
+ */
+static void web_connection_parse_post(struct upnp_wps_device_sm *sm,
+ struct sockaddr_in *cli,
+ struct http_request *req,
+ const char *filename)
+{
+ enum http_reply_code ret;
+ char *data = http_request_get_data(req); /* body of http msg */
+ const char *action = NULL;
+ size_t action_len = 0;
+ const char *replyname = NULL; /* argument name for the reply */
+ struct wpabuf *reply = NULL; /* data for the reply */
+
+ if (os_strcasecmp(filename, UPNP_WPS_DEVICE_CONTROL_FILE)) {
+ wpa_printf(MSG_INFO, "WPS UPnP: Invalid POST filename %s",
+ filename);
+ ret = HTTP_NOT_FOUND;
+ goto bad;
+ }
+
+ ret = UPNP_INVALID_ACTION;
+ action = web_get_action(req, &action_len);
+ if (action == NULL)
+ goto bad;
+
+ if (!os_strncasecmp("GetDeviceInfo", action, action_len))
+ ret = web_process_get_device_info(sm, &reply, &replyname);
+ else if (!os_strncasecmp("PutMessage", action, action_len))
+ ret = web_process_put_message(sm, data, &reply, &replyname);
+ else if (!os_strncasecmp("PutWLANResponse", action, action_len))
+ ret = web_process_put_wlan_response(sm, data, &reply,
+ &replyname);
+ else if (!os_strncasecmp("SetSelectedRegistrar", action, action_len))
+ ret = web_process_set_selected_registrar(sm, cli, data, &reply,
+ &replyname);
+ else
+ wpa_printf(MSG_INFO, "WPS UPnP: Unknown POST type");
+
+bad:
+ if (ret != HTTP_OK)
+ wpa_printf(MSG_INFO, "WPS UPnP: POST failure ret=%d", ret);
+ web_connection_send_reply(req, ret, action, action_len, reply,
+ replyname);
+ wpabuf_free(reply);
+}
+
+
+/* Given that we have received a header w/ SUBSCRIBE, act upon it
+ *
+ * Format of SUBSCRIBE (case-insensitive):
+ *
+ * First line must be:
+ * SUBSCRIBE /wps_event HTTP/1.1
+ *
+ * Our response (if no error) which includes only required lines is:
+ * HTTP/1.1 200 OK
+ * Server: xx, UPnP/1.0, xx
+ * SID: uuid:xxxxxxxxx
+ * Timeout: Second-<n>
+ * Content-Length: 0
+ * Date: xxxx
+ *
+ * Header lines must end with \r\n
+ * Per RFC 2616, content-length: is not required but connection:close
+ * would appear to be required (given that we will be closing it!).
+ */
+static void web_connection_parse_subscribe(struct upnp_wps_device_sm *sm,
+ struct http_request *req,
+ const char *filename)
+{
+ struct wpabuf *buf;
+ char *b;
+ char *hdr = http_request_get_hdr(req);
+ char *h;
+ char *match;
+ int match_len;
+ char *end;
+ int len;
+ int got_nt = 0;
+ u8 uuid[UUID_LEN];
+ int got_uuid = 0;
+ char *callback_urls = NULL;
+ struct subscription *s = NULL;
+ enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR;
+
+ buf = wpabuf_alloc(1000);
+ if (buf == NULL) {
+ http_request_deinit(req);
+ return;
+ }
+
+ wpa_hexdump_ascii(MSG_DEBUG, "WPS UPnP: HTTP SUBSCRIBE",
+ (u8 *) hdr, os_strlen(hdr));
+
+ /* Parse/validate headers */
+ h = hdr;
+ /* First line: SUBSCRIBE /wps_event HTTP/1.1
+ * has already been parsed.
+ */
+ if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) {
+ ret = HTTP_PRECONDITION_FAILED;
+ goto error;
+ }
+ wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP SUBSCRIBE for event");
+ end = os_strchr(h, '\n');
+
+ for (; end != NULL; h = end + 1) {
+ /* Option line by option line */
+ h = end + 1;
+ end = os_strchr(h, '\n');
+ if (end == NULL)
+ break; /* no unterminated lines allowed */
+
+ /* NT assures that it is our type of subscription;
+ * not used for a renewl.
+ **/
+ match = "NT:";
+ match_len = os_strlen(match);
+ if (os_strncasecmp(h, match, match_len) == 0) {
+ h += match_len;
+ while (*h == ' ' || *h == '\t')
+ h++;
+ match = "upnp:event";
+ match_len = os_strlen(match);
+ if (os_strncasecmp(h, match, match_len) != 0) {
+ ret = HTTP_BAD_REQUEST;
+ goto error;
+ }
+ got_nt = 1;
+ continue;
+ }
+ /* HOST should refer to us */
+#if 0
+ match = "HOST:";
+ match_len = os_strlen(match);
+ if (os_strncasecmp(h, match, match_len) == 0) {
+ h += match_len;
+ while (*h == ' ' || *h == '\t')
+ h++;
+ .....
+ }
+#endif
+ /* CALLBACK gives one or more URLs for NOTIFYs
+ * to be sent as a result of the subscription.
+ * Each URL is enclosed in angle brackets.
+ */
+ match = "CALLBACK:";
+ match_len = os_strlen(match);
+ if (os_strncasecmp(h, match, match_len) == 0) {
+ h += match_len;
+ while (*h == ' ' || *h == '\t')
+ h++;
+ len = end - h;
+ os_free(callback_urls);
+ callback_urls = os_malloc(len + 1);
+ if (callback_urls == NULL) {
+ ret = HTTP_INTERNAL_SERVER_ERROR;
+ goto error;
+ }
+ os_memcpy(callback_urls, h, len);
+ callback_urls[len] = 0;
+ continue;
+ }
+ /* SID is only for renewal */
+ match = "SID:";
+ match_len = os_strlen(match);
+ if (os_strncasecmp(h, match, match_len) == 0) {
+ h += match_len;
+ while (*h == ' ' || *h == '\t')
+ h++;
+ match = "uuid:";
+ match_len = os_strlen(match);
+ if (os_strncasecmp(h, match, match_len) != 0) {
+ ret = HTTP_BAD_REQUEST;
+ goto error;
+ }
+ h += match_len;
+ while (*h == ' ' || *h == '\t')
+ h++;
+ if (uuid_str2bin(h, uuid)) {
+ ret = HTTP_BAD_REQUEST;
+ goto error;
+ }
+ got_uuid = 1;
+ continue;
+ }
+ /* TIMEOUT is requested timeout, but apparently we can
+ * just ignore this.
+ */
+ }
+
+ if (got_uuid) {
+ /* renewal */
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Subscription renewal");
+ if (callback_urls) {
+ ret = HTTP_BAD_REQUEST;
+ goto error;
+ }
+ s = subscription_renew(sm, uuid);
+ if (s == NULL) {
+ char str[80];
+ uuid_bin2str(uuid, str, sizeof(str));
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Could not find "
+ "SID %s", str);
+ ret = HTTP_PRECONDITION_FAILED;
+ goto error;
+ }
+ } else if (callback_urls) {
+ wpa_printf(MSG_DEBUG, "WPS UPnP: New subscription");
+ if (!got_nt) {
+ ret = HTTP_PRECONDITION_FAILED;
+ goto error;
+ }
+ s = subscription_start(sm, callback_urls);
+ if (s == NULL) {
+ ret = HTTP_INTERNAL_SERVER_ERROR;
+ goto error;
+ }
+ } else {
+ ret = HTTP_PRECONDITION_FAILED;
+ goto error;
+ }
+
+ /* success */
+ http_put_reply_code(buf, HTTP_OK);
+ wpabuf_put_str(buf, http_server_hdr);
+ wpabuf_put_str(buf, http_connection_close);
+ wpabuf_put_str(buf, "Content-Length: 0\r\n");
+ wpabuf_put_str(buf, "SID: uuid:");
+ /* subscription id */
+ b = wpabuf_put(buf, 0);
+ uuid_bin2str(s->uuid, b, 80);
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Assigned SID %s", b);
+ wpabuf_put(buf, os_strlen(b));
+ wpabuf_put_str(buf, "\r\n");
+ wpabuf_printf(buf, "Timeout: Second-%d\r\n", UPNP_SUBSCRIBE_SEC);
+ http_put_date(buf);
+ /* And empty line to terminate header: */
+ wpabuf_put_str(buf, "\r\n");
+
+ os_free(callback_urls);
+ http_request_send_and_deinit(req, buf);
+ return;
+
+error:
+ /* Per UPnP spec:
+ * Errors
+ * Incompatible headers
+ * 400 Bad Request. If SID header and one of NT or CALLBACK headers
+ * are present, the publisher must respond with HTTP error
+ * 400 Bad Request.
+ * Missing or invalid CALLBACK
+ * 412 Precondition Failed. If CALLBACK header is missing or does not
+ * contain a valid HTTP URL, the publisher must respond with HTTP
+ * error 412 Precondition Failed.
+ * Invalid NT
+ * 412 Precondition Failed. If NT header does not equal upnp:event,
+ * the publisher must respond with HTTP error 412 Precondition
+ * Failed.
+ * [For resubscription, use 412 if unknown uuid].
+ * Unable to accept subscription
+ * 5xx. If a publisher is not able to accept a subscription (such as
+ * due to insufficient resources), it must respond with a
+ * HTTP 500-series error code.
+ * 599 Too many subscriptions (not a standard HTTP error)
+ */
+ wpa_printf(MSG_DEBUG, "WPS UPnP: SUBSCRIBE failed - return %d", ret);
+ http_put_empty(buf, ret);
+ http_request_send_and_deinit(req, buf);
+ os_free(callback_urls);
+}
+
+
+/* Given that we have received a header w/ UNSUBSCRIBE, act upon it
+ *
+ * Format of UNSUBSCRIBE (case-insensitive):
+ *
+ * First line must be:
+ * UNSUBSCRIBE /wps_event HTTP/1.1
+ *
+ * Our response (if no error) which includes only required lines is:
+ * HTTP/1.1 200 OK
+ * Content-Length: 0
+ *
+ * Header lines must end with \r\n
+ * Per RFC 2616, content-length: is not required but connection:close
+ * would appear to be required (given that we will be closing it!).
+ */
+static void web_connection_parse_unsubscribe(struct upnp_wps_device_sm *sm,
+ struct http_request *req,
+ const char *filename)
+{
+ struct wpabuf *buf;
+ char *hdr = http_request_get_hdr(req);
+ char *h;
+ char *match;
+ int match_len;
+ char *end;
+ u8 uuid[UUID_LEN];
+ int got_uuid = 0;
+ struct subscription *s = NULL;
+ enum http_reply_code ret = HTTP_INTERNAL_SERVER_ERROR;
+
+ /* Parse/validate headers */
+ h = hdr;
+ /* First line: UNSUBSCRIBE /wps_event HTTP/1.1
+ * has already been parsed.
+ */
+ if (os_strcasecmp(filename, UPNP_WPS_DEVICE_EVENT_FILE) != 0) {
+ ret = HTTP_PRECONDITION_FAILED;
+ goto send_msg;
+ }
+ wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP UNSUBSCRIBE for event");
+ end = os_strchr(h, '\n');
+
+ for (; end != NULL; h = end + 1) {
+ /* Option line by option line */
+ h = end + 1;
+ end = os_strchr(h, '\n');
+ if (end == NULL)
+ break; /* no unterminated lines allowed */
+
+ /* HOST should refer to us */
+#if 0
+ match = "HOST:";
+ match_len = os_strlen(match);
+ if (os_strncasecmp(h, match, match_len) == 0) {
+ h += match_len;
+ while (*h == ' ' || *h == '\t')
+ h++;
+ .....
+ }
+#endif
+ /* SID is only for renewal */
+ match = "SID:";
+ match_len = os_strlen(match);
+ if (os_strncasecmp(h, match, match_len) == 0) {
+ h += match_len;
+ while (*h == ' ' || *h == '\t')
+ h++;
+ match = "uuid:";
+ match_len = os_strlen(match);
+ if (os_strncasecmp(h, match, match_len) != 0) {
+ ret = HTTP_BAD_REQUEST;
+ goto send_msg;
+ }
+ h += match_len;
+ while (*h == ' ' || *h == '\t')
+ h++;
+ if (uuid_str2bin(h, uuid)) {
+ ret = HTTP_BAD_REQUEST;
+ goto send_msg;
+ }
+ got_uuid = 1;
+ continue;
+ }
+ }
+
+ if (got_uuid) {
+ s = subscription_find(sm, uuid);
+ if (s) {
+ struct subscr_addr *sa;
+ sa = dl_list_first(&s->addr_list, struct subscr_addr,
+ list);
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Unsubscribing %p %s",
+ s, (sa && sa->domain_and_port) ?
+ sa->domain_and_port : "-null-");
+ dl_list_del(&s->list);
+ subscription_destroy(s);
+ }
+ } else {
+ wpa_printf(MSG_INFO, "WPS UPnP: Unsubscribe fails (not "
+ "found)");
+ ret = HTTP_PRECONDITION_FAILED;
+ goto send_msg;
+ }
+
+ ret = HTTP_OK;
+
+send_msg:
+ buf = wpabuf_alloc(200);
+ if (buf == NULL) {
+ http_request_deinit(req);
+ return;
+ }
+ http_put_empty(buf, ret);
+ http_request_send_and_deinit(req, buf);
+}
+
+
+/* Send error in response to unknown requests */
+static void web_connection_unimplemented(struct http_request *req)
+{
+ struct wpabuf *buf;
+ buf = wpabuf_alloc(200);
+ if (buf == NULL) {
+ http_request_deinit(req);
+ return;
+ }
+ http_put_empty(buf, HTTP_UNIMPLEMENTED);
+ http_request_send_and_deinit(req, buf);
+}
+
+
+
+/* Called when we have gotten an apparently valid http request.
+ */
+static void web_connection_check_data(void *ctx, struct http_request *req)
+{
+ struct upnp_wps_device_sm *sm = ctx;
+ enum httpread_hdr_type htype = http_request_get_type(req);
+ char *filename = http_request_get_uri(req);
+ struct sockaddr_in *cli = http_request_get_cli_addr(req);
+
+ if (!filename) {
+ wpa_printf(MSG_INFO, "WPS UPnP: Could not get HTTP URI");
+ http_request_deinit(req);
+ return;
+ }
+ /* Trim leading slashes from filename */
+ while (*filename == '/')
+ filename++;
+
+ wpa_printf(MSG_DEBUG, "WPS UPnP: Got HTTP request type %d from %s:%d",
+ htype, inet_ntoa(cli->sin_addr), htons(cli->sin_port));
+
+ switch (htype) {
+ case HTTPREAD_HDR_TYPE_GET:
+ web_connection_parse_get(sm, req, filename);
+ break;
+ case HTTPREAD_HDR_TYPE_POST:
+ web_connection_parse_post(sm, cli, req, filename);
+ break;
+ case HTTPREAD_HDR_TYPE_SUBSCRIBE:
+ web_connection_parse_subscribe(sm, req, filename);
+ break;
+ case HTTPREAD_HDR_TYPE_UNSUBSCRIBE:
+ web_connection_parse_unsubscribe(sm, req, filename);
+ break;
+
+ /* We are not required to support M-POST; just plain
+ * POST is supposed to work, so we only support that.
+ * If for some reason we need to support M-POST, it is
+ * mostly the same as POST, with small differences.
+ */
+ default:
+ /* Send 501 for anything else */
+ web_connection_unimplemented(req);
+ break;
+ }
+}
+
+
+/*
+ * Listening for web connections
+ * We have a single TCP listening port, and hand off connections as we get
+ * them.
+ */
+
+void web_listener_stop(struct upnp_wps_device_sm *sm)
+{
+ http_server_deinit(sm->web_srv);
+ sm->web_srv = NULL;
+}
+
+
+int web_listener_start(struct upnp_wps_device_sm *sm)
+{
+ struct in_addr addr;
+ addr.s_addr = sm->ip_addr;
+ sm->web_srv = http_server_init(&addr, -1, web_connection_check_data,
+ sm);
+ if (sm->web_srv == NULL) {
+ web_listener_stop(sm);
+ return -1;
+ }
+ sm->web_port = http_server_get_port(sm->web_srv);
+
+ return 0;
+}
diff --git a/src/wps/wps_validate.c b/src/wps/wps_validate.c
new file mode 100644
index 0000000..c3071a0
--- /dev/null
+++ b/src/wps/wps_validate.c
@@ -0,0 +1,1981 @@
+/*
+ * Wi-Fi Protected Setup - Strict protocol validation routines
+ * Copyright (c) 2010, Atheros Communications, Inc.
+ *
+ * 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.
+ */
+
+#include "utils/includes.h"
+
+#include "utils/common.h"
+#include "wps_i.h"
+#include "wps.h"
+
+
+#ifndef WPS_STRICT_ALL
+#define WPS_STRICT_WPS2
+#endif /* WPS_STRICT_ALL */
+
+
+static int wps_validate_version(const u8 *version, int mandatory)
+{
+ if (version == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Version attribute "
+ "missing");
+ return -1;
+ }
+ return 0;
+ }
+ if (*version != 0x10) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Version attribute "
+ "value 0x%x", *version);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_version2(const u8 *version2, int mandatory)
+{
+ if (version2 == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Version2 attribute "
+ "missing");
+ return -1;
+ }
+ return 0;
+ }
+ if (*version2 < 0x20) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Version2 attribute "
+ "value 0x%x", *version2);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_request_type(const u8 *request_type, int mandatory)
+{
+ if (request_type == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Request Type "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ if (*request_type > 0x03) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Request Type "
+ "attribute value 0x%x", *request_type);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_response_type(const u8 *response_type, int mandatory)
+{
+ if (response_type == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Response Type "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ if (*response_type > 0x03) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Response Type "
+ "attribute value 0x%x", *response_type);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int valid_config_methods(u16 val, int wps2)
+{
+ if (wps2) {
+ if ((val & 0x6000) && !(val & WPS_CONFIG_DISPLAY)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Physical/Virtual "
+ "Display flag without old Display flag "
+ "set");
+ return 0;
+ }
+ if (!(val & 0x6000) && (val & WPS_CONFIG_DISPLAY)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Display flag "
+ "without Physical/Virtual Display flag");
+ return 0;
+ }
+ if ((val & 0x0600) && !(val & WPS_CONFIG_PUSHBUTTON)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Physical/Virtual "
+ "PushButton flag without old PushButton "
+ "flag set");
+ return 0;
+ }
+ if (!(val & 0x0600) && (val & WPS_CONFIG_PUSHBUTTON)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: PushButton flag "
+ "without Physical/Virtual PushButton flag");
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+
+static int wps_validate_config_methods(const u8 *config_methods, int wps2,
+ int mandatory)
+{
+ u16 val;
+
+ if (config_methods == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Configuration "
+ "Methods attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+
+ val = WPA_GET_BE16(config_methods);
+ if (!valid_config_methods(val, wps2)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Configuration "
+ "Methods attribute value 0x%04x", val);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_ap_config_methods(const u8 *config_methods, int wps2,
+ int mandatory)
+{
+ u16 val;
+
+ if (wps_validate_config_methods(config_methods, wps2, mandatory) < 0)
+ return -1;
+ if (config_methods == NULL)
+ return 0;
+ val = WPA_GET_BE16(config_methods);
+ if (val & WPS_CONFIG_PUSHBUTTON) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Configuration "
+ "Methods attribute value 0x%04x in AP info "
+ "(PushButton not allowed for registering new ER)",
+ val);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_uuid_e(const u8 *uuid_e, int mandatory)
+{
+ if (uuid_e == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: UUID-E "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+
+static int wps_validate_uuid_r(const u8 *uuid_r, int mandatory)
+{
+ if (uuid_r == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: UUID-R "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+
+static int wps_validate_primary_dev_type(const u8 *primary_dev_type,
+ int mandatory)
+{
+ if (primary_dev_type == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Primary Device Type "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+
+static int wps_validate_rf_bands(const u8 *rf_bands, int mandatory)
+{
+ if (rf_bands == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: RF Bands "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ if (*rf_bands != WPS_RF_24GHZ && *rf_bands != WPS_RF_50GHZ &&
+ *rf_bands != (WPS_RF_24GHZ | WPS_RF_50GHZ)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Rf Bands "
+ "attribute value 0x%x", *rf_bands);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_assoc_state(const u8 *assoc_state, int mandatory)
+{
+ u16 val;
+ if (assoc_state == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Association State "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ val = WPA_GET_BE16(assoc_state);
+ if (val > 4) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Association State "
+ "attribute value 0x%04x", val);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_config_error(const u8 *config_error, int mandatory)
+{
+ u16 val;
+
+ if (config_error == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Configuration Error "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ val = WPA_GET_BE16(config_error);
+ if (val > 18) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Configuration Error "
+ "attribute value 0x%04x", val);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_dev_password_id(const u8 *dev_password_id,
+ int mandatory)
+{
+ u16 val;
+
+ if (dev_password_id == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Device Password ID "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ val = WPA_GET_BE16(dev_password_id);
+ if (val >= 0x0006 && val <= 0x000f) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Device Password ID "
+ "attribute value 0x%04x", val);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_manufacturer(const u8 *manufacturer, size_t len,
+ int mandatory)
+{
+ if (manufacturer == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Manufacturer "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ if (len > 0 && manufacturer[len - 1] == 0) {
+ wpa_hexdump_ascii(MSG_INFO, "WPS-STRICT: Invalid Manufacturer "
+ "attribute value", manufacturer, len);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_model_name(const u8 *model_name, size_t len,
+ int mandatory)
+{
+ if (model_name == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Model Name "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ if (len > 0 && model_name[len - 1] == 0) {
+ wpa_hexdump_ascii(MSG_INFO, "WPS-STRICT: Invalid Model Name "
+ "attribute value", model_name, len);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_model_number(const u8 *model_number, size_t len,
+ int mandatory)
+{
+ if (model_number == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Model Number "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ if (len > 0 && model_number[len - 1] == 0) {
+ wpa_hexdump_ascii(MSG_INFO, "WPS-STRICT: Invalid Model Number "
+ "attribute value", model_number, len);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_serial_number(const u8 *serial_number, size_t len,
+ int mandatory)
+{
+ if (serial_number == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Serial Number "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ if (len > 0 && serial_number[len - 1] == 0) {
+ wpa_hexdump_ascii(MSG_INFO, "WPS-STRICT: Invalid Serial "
+ "Number attribute value",
+ serial_number, len);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_dev_name(const u8 *dev_name, size_t len,
+ int mandatory)
+{
+ if (dev_name == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Device Name "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ if (len > 0 && dev_name[len - 1] == 0) {
+ wpa_hexdump_ascii(MSG_INFO, "WPS-STRICT: Invalid Device Name "
+ "attribute value", dev_name, len);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_request_to_enroll(const u8 *request_to_enroll,
+ int mandatory)
+{
+ if (request_to_enroll == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Request to Enroll "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ if (*request_to_enroll > 0x01) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Request to Enroll "
+ "attribute value 0x%x", *request_to_enroll);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_req_dev_type(const u8 *req_dev_type[], size_t num,
+ int mandatory)
+{
+ if (num == 0) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Requested Device "
+ "Type attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+
+static int wps_validate_wps_state(const u8 *wps_state, int mandatory)
+{
+ if (wps_state == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Wi-Fi Protected "
+ "Setup State attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ if (*wps_state != WPS_STATE_NOT_CONFIGURED &&
+ *wps_state != WPS_STATE_CONFIGURED) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Wi-Fi Protected "
+ "Setup State attribute value 0x%x", *wps_state);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_ap_setup_locked(const u8 *ap_setup_locked,
+ int mandatory)
+{
+ if (ap_setup_locked == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: AP Setup Locked "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ if (*ap_setup_locked > 1) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid AP Setup Locked "
+ "attribute value 0x%x", *ap_setup_locked);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_selected_registrar(const u8 *selected_registrar,
+ int mandatory)
+{
+ if (selected_registrar == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Selected Registrar "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ if (*selected_registrar > 1) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Selected Registrar "
+ "attribute value 0x%x", *selected_registrar);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_sel_reg_config_methods(const u8 *config_methods,
+ int wps2, int mandatory)
+{
+ u16 val;
+
+ if (config_methods == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Selected Registrar "
+ "Configuration Methods attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+
+ val = WPA_GET_BE16(config_methods);
+ if (!valid_config_methods(val, wps2)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Selected Registrar "
+ "Configuration Methods attribute value 0x%04x",
+ val);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_authorized_macs(const u8 *authorized_macs, size_t len,
+ int mandatory)
+{
+ if (authorized_macs == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Authorized MACs "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ if (len > 30 && (len % ETH_ALEN) != 0) {
+ wpa_hexdump(MSG_INFO, "WPS-STRICT: Invalid Authorized "
+ "MACs attribute value", authorized_macs, len);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_msg_type(const u8 *msg_type, int mandatory)
+{
+ if (msg_type == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Message Type "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ if (*msg_type < WPS_Beacon || *msg_type > WPS_WSC_DONE) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Message Type "
+ "attribute value 0x%x", *msg_type);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_mac_addr(const u8 *mac_addr, int mandatory)
+{
+ if (mac_addr == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: MAC Address "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ if (mac_addr[0] & 0x01) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid MAC Address "
+ "attribute value " MACSTR, MAC2STR(mac_addr));
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_enrollee_nonce(const u8 *enrollee_nonce, int mandatory)
+{
+ if (enrollee_nonce == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Enrollee Nonce "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+
+static int wps_validate_registrar_nonce(const u8 *registrar_nonce,
+ int mandatory)
+{
+ if (registrar_nonce == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Registrar Nonce "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+
+static int wps_validate_public_key(const u8 *public_key, size_t len,
+ int mandatory)
+{
+ if (public_key == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Public Key "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ if (len != 192) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Public Key "
+ "attribute length %d", (int) len);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int num_bits_set(u16 val)
+{
+ int c;
+ for (c = 0; val; c++)
+ val &= val - 1;
+ return c;
+}
+
+
+static int wps_validate_auth_type_flags(const u8 *flags, int mandatory)
+{
+ u16 val;
+
+ if (flags == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Authentication Type "
+ "Flags attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ val = WPA_GET_BE16(flags);
+ if ((val & ~WPS_AUTH_TYPES) || !(val & WPS_AUTH_WPA2PSK)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Authentication Type "
+ "Flags attribute value 0x%04x", val);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_auth_type(const u8 *type, int mandatory)
+{
+ u16 val;
+
+ if (type == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Authentication Type "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ val = WPA_GET_BE16(type);
+ if ((val & ~WPS_AUTH_TYPES) || val == 0 ||
+ (num_bits_set(val) > 1 &&
+ val != (WPS_AUTH_WPAPSK | WPS_AUTH_WPA2PSK))) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Authentication Type "
+ "attribute value 0x%04x", val);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_encr_type_flags(const u8 *flags, int mandatory)
+{
+ u16 val;
+
+ if (flags == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Encryption Type "
+ "Flags attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ val = WPA_GET_BE16(flags);
+ if ((val & ~WPS_ENCR_TYPES) || !(val & WPS_ENCR_AES)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Encryption Type "
+ "Flags attribute value 0x%04x", val);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_encr_type(const u8 *type, int mandatory)
+{
+ u16 val;
+
+ if (type == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Encryption Type "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ val = WPA_GET_BE16(type);
+ if ((val & ~WPS_ENCR_TYPES) || val == 0 ||
+ (num_bits_set(val) > 1 && val != (WPS_ENCR_TKIP | WPS_ENCR_AES))) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Encryption Type "
+ "attribute value 0x%04x", val);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_conn_type_flags(const u8 *flags, int mandatory)
+{
+ if (flags == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Connection Type "
+ "Flags attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ if ((*flags & ~(WPS_CONN_ESS | WPS_CONN_IBSS)) ||
+ !(*flags & WPS_CONN_ESS)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Connection Type "
+ "Flags attribute value 0x%02x", *flags);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_os_version(const u8 *os_version, int mandatory)
+{
+ if (os_version == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: OS Version "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+
+static int wps_validate_authenticator(const u8 *authenticator, int mandatory)
+{
+ if (authenticator == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Authenticator "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+
+static int wps_validate_e_hash1(const u8 *hash, int mandatory)
+{
+ if (hash == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: E-Hash1 "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+
+static int wps_validate_e_hash2(const u8 *hash, int mandatory)
+{
+ if (hash == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: E-Hash2 "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+
+static int wps_validate_r_hash1(const u8 *hash, int mandatory)
+{
+ if (hash == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: R-Hash1 "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+
+static int wps_validate_r_hash2(const u8 *hash, int mandatory)
+{
+ if (hash == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: R-Hash2 "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+
+static int wps_validate_encr_settings(const u8 *encr_settings, size_t len,
+ int mandatory)
+{
+ if (encr_settings == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Encrypted Settings "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ if (len < 16) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Encrypted Settings "
+ "attribute length %d", (int) len);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_settings_delay_time(const u8 *delay, int mandatory)
+{
+ if (delay == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Settings Delay Time "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+
+static int wps_validate_r_snonce1(const u8 *nonce, int mandatory)
+{
+ if (nonce == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: R-SNonce1 "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+
+static int wps_validate_r_snonce2(const u8 *nonce, int mandatory)
+{
+ if (nonce == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: R-SNonce2 "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+
+static int wps_validate_e_snonce1(const u8 *nonce, int mandatory)
+{
+ if (nonce == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: E-SNonce1 "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+
+static int wps_validate_e_snonce2(const u8 *nonce, int mandatory)
+{
+ if (nonce == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: E-SNonce2 "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+
+static int wps_validate_key_wrap_auth(const u8 *auth, int mandatory)
+{
+ if (auth == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Key Wrap "
+ "Authenticator attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+
+static int wps_validate_ssid(const u8 *ssid, size_t ssid_len, int mandatory)
+{
+ if (ssid == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: SSID "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ if (ssid_len == 0 || ssid[ssid_len - 1] == 0) {
+ wpa_hexdump_ascii(MSG_INFO, "WPS-STRICT: Invalid SSID "
+ "attribute value", ssid, ssid_len);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_network_key_index(const u8 *idx, int mandatory)
+{
+ if (idx == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Network Key Index "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+
+static int wps_validate_network_idx(const u8 *idx, int mandatory)
+{
+ if (idx == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Network Index "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+
+static int wps_validate_network_key(const u8 *key, size_t key_len,
+ const u8 *encr_type, int mandatory)
+{
+ if (key == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Network Key "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ if (((encr_type == NULL || WPA_GET_BE16(encr_type) != WPS_ENCR_WEP) &&
+ key_len > 8 && key_len < 64 && key[key_len - 1] == 0) ||
+ key_len > 64) {
+ wpa_hexdump_ascii_key(MSG_INFO, "WPS-STRICT: Invalid Network "
+ "Key attribute value", key, key_len);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_network_key_shareable(const u8 *val, int mandatory)
+{
+ if (val == NULL) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Network Key "
+ "Shareable attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+ if (*val > 1) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Network Key "
+ "Shareable attribute value 0x%x", *val);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int wps_validate_cred(const u8 *cred, size_t len)
+{
+ struct wps_parse_attr attr;
+ struct wpabuf buf;
+
+ if (cred == NULL)
+ return -1;
+ wpabuf_set(&buf, cred, len);
+ if (wps_parse_msg(&buf, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse Credential");
+ return -1;
+ }
+
+ if (wps_validate_network_idx(attr.network_idx, 1) ||
+ wps_validate_ssid(attr.ssid, attr.ssid_len, 1) ||
+ wps_validate_auth_type(attr.auth_type, 1) ||
+ wps_validate_encr_type(attr.encr_type, 1) ||
+ wps_validate_network_key_index(attr.network_key_idx, 0) ||
+ wps_validate_network_key(attr.network_key, attr.network_key_len,
+ attr.encr_type, 1) ||
+ wps_validate_mac_addr(attr.mac_addr, 1) ||
+ wps_validate_network_key_shareable(attr.network_key_shareable, 0))
+ {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Credential");
+ return -1;
+ }
+
+
+ return 0;
+}
+
+
+static int wps_validate_credential(const u8 *cred[], size_t len[], size_t num,
+ int mandatory)
+{
+ size_t i;
+
+ if (num == 0) {
+ if (mandatory) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Credential "
+ "attribute missing");
+ return -1;
+ }
+ return 0;
+ }
+
+ for (i = 0; i < num; i++) {
+ if (wps_validate_cred(cred[i], len[i]) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+
+int wps_validate_beacon(const struct wpabuf *wps_ie)
+{
+ struct wps_parse_attr attr;
+ int wps2, sel_reg;
+
+ if (wps_ie == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No WPS IE in Beacon frame");
+ return -1;
+ }
+ if (wps_parse_msg(wps_ie, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse WPS IE in "
+ "Beacon frame");
+ return -1;
+ }
+
+ wps2 = attr.version2 != NULL;
+ sel_reg = attr.selected_registrar != NULL &&
+ *attr.selected_registrar != 0;
+ if (wps_validate_version(attr.version, 1) ||
+ wps_validate_wps_state(attr.wps_state, 1) ||
+ wps_validate_ap_setup_locked(attr.ap_setup_locked, 0) ||
+ wps_validate_selected_registrar(attr.selected_registrar, 0) ||
+ wps_validate_dev_password_id(attr.dev_password_id, sel_reg) ||
+ wps_validate_sel_reg_config_methods(attr.sel_reg_config_methods,
+ wps2, sel_reg) ||
+ wps_validate_uuid_e(attr.uuid_e, 0) ||
+ wps_validate_rf_bands(attr.rf_bands, 0) ||
+ wps_validate_version2(attr.version2, wps2) ||
+ wps_validate_authorized_macs(attr.authorized_macs,
+ attr.authorized_macs_len, 0)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Beacon frame");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+int wps_validate_beacon_probe_resp(const struct wpabuf *wps_ie, int probe,
+ const u8 *addr)
+{
+ struct wps_parse_attr attr;
+ int wps2, sel_reg;
+
+ if (wps_ie == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No WPS IE in "
+ "%sProbe Response frame", probe ? "" : "Beacon/");
+ return -1;
+ }
+ if (wps_parse_msg(wps_ie, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse WPS IE in "
+ "%sProbe Response frame", probe ? "" : "Beacon/");
+ return -1;
+ }
+
+ wps2 = attr.version2 != NULL;
+ sel_reg = attr.selected_registrar != NULL &&
+ *attr.selected_registrar != 0;
+ if (wps_validate_version(attr.version, 1) ||
+ wps_validate_wps_state(attr.wps_state, 1) ||
+ wps_validate_ap_setup_locked(attr.ap_setup_locked, 0) ||
+ wps_validate_selected_registrar(attr.selected_registrar, 0) ||
+ wps_validate_dev_password_id(attr.dev_password_id, sel_reg) ||
+ wps_validate_sel_reg_config_methods(attr.sel_reg_config_methods,
+ wps2, sel_reg) ||
+ wps_validate_response_type(attr.response_type, probe) ||
+ wps_validate_uuid_e(attr.uuid_e, probe) ||
+ wps_validate_manufacturer(attr.manufacturer, attr.manufacturer_len,
+ probe) ||
+ wps_validate_model_name(attr.model_name, attr.model_name_len,
+ probe) ||
+ wps_validate_model_number(attr.model_number, attr.model_number_len,
+ probe) ||
+ wps_validate_serial_number(attr.serial_number,
+ attr.serial_number_len, probe) ||
+ wps_validate_primary_dev_type(attr.primary_dev_type, probe) ||
+ wps_validate_dev_name(attr.dev_name, attr.dev_name_len, probe) ||
+ wps_validate_ap_config_methods(attr.config_methods, wps2, probe) ||
+ wps_validate_rf_bands(attr.rf_bands, 0) ||
+ wps_validate_version2(attr.version2, wps2) ||
+ wps_validate_authorized_macs(attr.authorized_macs,
+ attr.authorized_macs_len, 0)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid %sProbe Response "
+ "frame from " MACSTR, probe ? "" : "Beacon/",
+ MAC2STR(addr));
+#ifdef WPS_STRICT_WPS2
+ if (wps2)
+ return -1;
+#else /* WPS_STRICT_WPS2 */
+ return -1;
+#endif /* WPS_STRICT_WPS2 */
+ }
+
+ return 0;
+}
+
+
+int wps_validate_probe_req(const struct wpabuf *wps_ie, const u8 *addr)
+{
+ struct wps_parse_attr attr;
+ int wps2;
+
+ if (wps_ie == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No WPS IE in "
+ "Probe Request frame");
+ return -1;
+ }
+ if (wps_parse_msg(wps_ie, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse WPS IE in "
+ "Probe Request frame");
+ return -1;
+ }
+
+ wps2 = attr.version2 != NULL;
+ if (wps_validate_version(attr.version, 1) ||
+ wps_validate_request_type(attr.request_type, 1) ||
+ wps_validate_config_methods(attr.config_methods, wps2, 1) ||
+ wps_validate_uuid_e(attr.uuid_e, attr.uuid_r == NULL) ||
+ wps_validate_uuid_r(attr.uuid_r, attr.uuid_e == NULL) ||
+ wps_validate_primary_dev_type(attr.primary_dev_type, 1) ||
+ wps_validate_rf_bands(attr.rf_bands, 1) ||
+ wps_validate_assoc_state(attr.assoc_state, 1) ||
+ wps_validate_config_error(attr.config_error, 1) ||
+ wps_validate_dev_password_id(attr.dev_password_id, 1) ||
+ wps_validate_version2(attr.version2, wps2) ||
+ wps_validate_manufacturer(attr.manufacturer, attr.manufacturer_len,
+ wps2) ||
+ wps_validate_model_name(attr.model_name, attr.model_name_len,
+ wps2) ||
+ wps_validate_model_number(attr.model_number, attr.model_number_len,
+ wps2) ||
+ wps_validate_dev_name(attr.dev_name, attr.dev_name_len, wps2) ||
+ wps_validate_request_to_enroll(attr.request_to_enroll, 0) ||
+ wps_validate_req_dev_type(attr.req_dev_type, attr.num_req_dev_type,
+ 0)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid Probe Request "
+ "frame from " MACSTR, MAC2STR(addr));
+ return -1;
+ }
+
+ return 0;
+}
+
+
+int wps_validate_assoc_req(const struct wpabuf *wps_ie)
+{
+ struct wps_parse_attr attr;
+ int wps2;
+
+ if (wps_ie == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No WPS IE in "
+ "(Re)Association Request frame");
+ return -1;
+ }
+ if (wps_parse_msg(wps_ie, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse WPS IE in "
+ "(Re)Association Request frame");
+ return -1;
+ }
+
+ wps2 = attr.version2 != NULL;
+ if (wps_validate_version(attr.version, 1) ||
+ wps_validate_request_type(attr.request_type, 1) ||
+ wps_validate_version2(attr.version2, wps2)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid (Re)Association "
+ "Request frame");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+int wps_validate_assoc_resp(const struct wpabuf *wps_ie)
+{
+ struct wps_parse_attr attr;
+ int wps2;
+
+ if (wps_ie == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No WPS IE in "
+ "(Re)Association Response frame");
+ return -1;
+ }
+ if (wps_parse_msg(wps_ie, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse WPS IE in "
+ "(Re)Association Response frame");
+ return -1;
+ }
+
+ wps2 = attr.version2 != NULL;
+ if (wps_validate_version(attr.version, 1) ||
+ wps_validate_response_type(attr.response_type, 1) ||
+ wps_validate_version2(attr.version2, wps2)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid (Re)Association "
+ "Response frame");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+int wps_validate_m1(const struct wpabuf *tlvs)
+{
+ struct wps_parse_attr attr;
+ int wps2;
+
+ if (tlvs == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No TLVs in M1");
+ return -1;
+ }
+ if (wps_parse_msg(tlvs, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse attributes "
+ "in M1");
+ return -1;
+ }
+
+ wps2 = attr.version2 != NULL;
+ if (wps_validate_version(attr.version, 1) ||
+ wps_validate_msg_type(attr.msg_type, 1) ||
+ wps_validate_uuid_e(attr.uuid_e, 1) ||
+ wps_validate_mac_addr(attr.mac_addr, 1) ||
+ wps_validate_enrollee_nonce(attr.enrollee_nonce, 1) ||
+ wps_validate_public_key(attr.public_key, attr.public_key_len, 1) ||
+ wps_validate_auth_type_flags(attr.auth_type_flags, 1) ||
+ wps_validate_encr_type_flags(attr.encr_type_flags, 1) ||
+ wps_validate_conn_type_flags(attr.conn_type_flags, 1) ||
+ wps_validate_config_methods(attr.config_methods, wps2, 1) ||
+ wps_validate_wps_state(attr.wps_state, 1) ||
+ wps_validate_manufacturer(attr.manufacturer, attr.manufacturer_len,
+ 1) ||
+ wps_validate_model_name(attr.model_name, attr.model_name_len, 1) ||
+ wps_validate_model_number(attr.model_number, attr.model_number_len,
+ 1) ||
+ wps_validate_serial_number(attr.serial_number,
+ attr.serial_number_len, 1) ||
+ wps_validate_primary_dev_type(attr.primary_dev_type, 1) ||
+ wps_validate_dev_name(attr.dev_name, attr.dev_name_len, 1) ||
+ wps_validate_rf_bands(attr.rf_bands, 1) ||
+ wps_validate_assoc_state(attr.assoc_state, 1) ||
+ wps_validate_dev_password_id(attr.dev_password_id, 1) ||
+ wps_validate_config_error(attr.config_error, 1) ||
+ wps_validate_os_version(attr.os_version, 1) ||
+ wps_validate_version2(attr.version2, wps2) ||
+ wps_validate_request_to_enroll(attr.request_to_enroll, 0)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid M1");
+#ifdef WPS_STRICT_WPS2
+ if (wps2)
+ return -1;
+#else /* WPS_STRICT_WPS2 */
+ return -1;
+#endif /* WPS_STRICT_WPS2 */
+ }
+
+ return 0;
+}
+
+
+int wps_validate_m2(const struct wpabuf *tlvs)
+{
+ struct wps_parse_attr attr;
+ int wps2;
+
+ if (tlvs == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No TLVs in M2");
+ return -1;
+ }
+ if (wps_parse_msg(tlvs, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse attributes "
+ "in M2");
+ return -1;
+ }
+
+ wps2 = attr.version2 != NULL;
+ if (wps_validate_version(attr.version, 1) ||
+ wps_validate_msg_type(attr.msg_type, 1) ||
+ wps_validate_enrollee_nonce(attr.enrollee_nonce, 1) ||
+ wps_validate_registrar_nonce(attr.registrar_nonce, 1) ||
+ wps_validate_uuid_r(attr.uuid_r, 1) ||
+ wps_validate_public_key(attr.public_key, attr.public_key_len, 1) ||
+ wps_validate_auth_type_flags(attr.auth_type_flags, 1) ||
+ wps_validate_encr_type_flags(attr.encr_type_flags, 1) ||
+ wps_validate_conn_type_flags(attr.conn_type_flags, 1) ||
+ wps_validate_config_methods(attr.config_methods, wps2, 1) ||
+ wps_validate_manufacturer(attr.manufacturer, attr.manufacturer_len,
+ 1) ||
+ wps_validate_model_name(attr.model_name, attr.model_name_len, 1) ||
+ wps_validate_model_number(attr.model_number, attr.model_number_len,
+ 1) ||
+ wps_validate_serial_number(attr.serial_number,
+ attr.serial_number_len, 1) ||
+ wps_validate_primary_dev_type(attr.primary_dev_type, 1) ||
+ wps_validate_dev_name(attr.dev_name, attr.dev_name_len, 1) ||
+ wps_validate_rf_bands(attr.rf_bands, 1) ||
+ wps_validate_assoc_state(attr.assoc_state, 1) ||
+ wps_validate_config_error(attr.config_error, 1) ||
+ wps_validate_dev_password_id(attr.dev_password_id, 1) ||
+ wps_validate_os_version(attr.os_version, 1) ||
+ wps_validate_version2(attr.version2, wps2) ||
+ wps_validate_authenticator(attr.authenticator, 1)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid M2");
+#ifdef WPS_STRICT_WPS2
+ if (wps2)
+ return -1;
+#else /* WPS_STRICT_WPS2 */
+ return -1;
+#endif /* WPS_STRICT_WPS2 */
+ }
+
+ return 0;
+}
+
+
+int wps_validate_m2d(const struct wpabuf *tlvs)
+{
+ struct wps_parse_attr attr;
+ int wps2;
+
+ if (tlvs == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No TLVs in M2D");
+ return -1;
+ }
+ if (wps_parse_msg(tlvs, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse attributes "
+ "in M2D");
+ return -1;
+ }
+
+ wps2 = attr.version2 != NULL;
+ if (wps_validate_version(attr.version, 1) ||
+ wps_validate_msg_type(attr.msg_type, 1) ||
+ wps_validate_enrollee_nonce(attr.enrollee_nonce, 1) ||
+ wps_validate_registrar_nonce(attr.registrar_nonce, 1) ||
+ wps_validate_uuid_r(attr.uuid_r, 1) ||
+ wps_validate_auth_type_flags(attr.auth_type_flags, 1) ||
+ wps_validate_encr_type_flags(attr.encr_type_flags, 1) ||
+ wps_validate_conn_type_flags(attr.conn_type_flags, 1) ||
+ wps_validate_config_methods(attr.config_methods, wps2, 1) ||
+ wps_validate_manufacturer(attr.manufacturer, attr.manufacturer_len,
+ 1) ||
+ wps_validate_model_name(attr.model_name, attr.model_name_len, 1) ||
+ wps_validate_model_number(attr.model_number, attr.model_number_len,
+ 1) ||
+ wps_validate_serial_number(attr.serial_number,
+ attr.serial_number_len, 1) ||
+ wps_validate_primary_dev_type(attr.primary_dev_type, 1) ||
+ wps_validate_dev_name(attr.dev_name, attr.dev_name_len, 1) ||
+ wps_validate_rf_bands(attr.rf_bands, 1) ||
+ wps_validate_assoc_state(attr.assoc_state, 1) ||
+ wps_validate_config_error(attr.config_error, 1) ||
+ wps_validate_os_version(attr.os_version, 1) ||
+ wps_validate_version2(attr.version2, wps2)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid M2D");
+#ifdef WPS_STRICT_WPS2
+ if (wps2)
+ return -1;
+#else /* WPS_STRICT_WPS2 */
+ return -1;
+#endif /* WPS_STRICT_WPS2 */
+ }
+
+ return 0;
+}
+
+
+int wps_validate_m3(const struct wpabuf *tlvs)
+{
+ struct wps_parse_attr attr;
+ int wps2;
+
+ if (tlvs == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No TLVs in M3");
+ return -1;
+ }
+ if (wps_parse_msg(tlvs, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse attributes "
+ "in M3");
+ return -1;
+ }
+
+ wps2 = attr.version2 != NULL;
+ if (wps_validate_version(attr.version, 1) ||
+ wps_validate_msg_type(attr.msg_type, 1) ||
+ wps_validate_registrar_nonce(attr.registrar_nonce, 1) ||
+ wps_validate_e_hash1(attr.e_hash1, 1) ||
+ wps_validate_e_hash2(attr.e_hash2, 1) ||
+ wps_validate_version2(attr.version2, wps2) ||
+ wps_validate_authenticator(attr.authenticator, 1)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid M3");
+#ifdef WPS_STRICT_WPS2
+ if (wps2)
+ return -1;
+#else /* WPS_STRICT_WPS2 */
+ return -1;
+#endif /* WPS_STRICT_WPS2 */
+ }
+
+ return 0;
+}
+
+
+int wps_validate_m4(const struct wpabuf *tlvs)
+{
+ struct wps_parse_attr attr;
+ int wps2;
+
+ if (tlvs == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No TLVs in M4");
+ return -1;
+ }
+ if (wps_parse_msg(tlvs, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse attributes "
+ "in M4");
+ return -1;
+ }
+
+ wps2 = attr.version2 != NULL;
+ if (wps_validate_version(attr.version, 1) ||
+ wps_validate_msg_type(attr.msg_type, 1) ||
+ wps_validate_enrollee_nonce(attr.enrollee_nonce, 1) ||
+ wps_validate_r_hash1(attr.r_hash1, 1) ||
+ wps_validate_r_hash2(attr.r_hash2, 1) ||
+ wps_validate_encr_settings(attr.encr_settings,
+ attr.encr_settings_len, 1) ||
+ wps_validate_version2(attr.version2, wps2) ||
+ wps_validate_authenticator(attr.authenticator, 1)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid M4");
+#ifdef WPS_STRICT_WPS2
+ if (wps2)
+ return -1;
+#else /* WPS_STRICT_WPS2 */
+ return -1;
+#endif /* WPS_STRICT_WPS2 */
+ }
+
+ return 0;
+}
+
+
+int wps_validate_m4_encr(const struct wpabuf *tlvs, int wps2)
+{
+ struct wps_parse_attr attr;
+
+ if (tlvs == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No TLVs in M4 encrypted "
+ "settings");
+ return -1;
+ }
+ if (wps_parse_msg(tlvs, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse attributes "
+ "in M4 encrypted settings");
+ return -1;
+ }
+
+ if (wps_validate_r_snonce1(attr.r_snonce1, 1) ||
+ wps_validate_key_wrap_auth(attr.key_wrap_auth, 1)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid M4 encrypted "
+ "settings");
+#ifdef WPS_STRICT_WPS2
+ if (wps2)
+ return -1;
+#else /* WPS_STRICT_WPS2 */
+ return -1;
+#endif /* WPS_STRICT_WPS2 */
+ }
+
+ return 0;
+}
+
+
+int wps_validate_m5(const struct wpabuf *tlvs)
+{
+ struct wps_parse_attr attr;
+ int wps2;
+
+ if (tlvs == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No TLVs in M5");
+ return -1;
+ }
+ if (wps_parse_msg(tlvs, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse attributes "
+ "in M5");
+ return -1;
+ }
+
+ wps2 = attr.version2 != NULL;
+ if (wps_validate_version(attr.version, 1) ||
+ wps_validate_msg_type(attr.msg_type, 1) ||
+ wps_validate_registrar_nonce(attr.registrar_nonce, 1) ||
+ wps_validate_encr_settings(attr.encr_settings,
+ attr.encr_settings_len, 1) ||
+ wps_validate_version2(attr.version2, wps2) ||
+ wps_validate_authenticator(attr.authenticator, 1)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid M5");
+#ifdef WPS_STRICT_WPS2
+ if (wps2)
+ return -1;
+#else /* WPS_STRICT_WPS2 */
+ return -1;
+#endif /* WPS_STRICT_WPS2 */
+ }
+
+ return 0;
+}
+
+
+int wps_validate_m5_encr(const struct wpabuf *tlvs, int wps2)
+{
+ struct wps_parse_attr attr;
+
+ if (tlvs == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No TLVs in M5 encrypted "
+ "settings");
+ return -1;
+ }
+ if (wps_parse_msg(tlvs, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse attributes "
+ "in M5 encrypted settings");
+ return -1;
+ }
+
+ if (wps_validate_e_snonce1(attr.e_snonce1, 1) ||
+ wps_validate_key_wrap_auth(attr.key_wrap_auth, 1)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid M5 encrypted "
+ "settings");
+#ifdef WPS_STRICT_WPS2
+ if (wps2)
+ return -1;
+#else /* WPS_STRICT_WPS2 */
+ return -1;
+#endif /* WPS_STRICT_WPS2 */
+ }
+
+ return 0;
+}
+
+
+int wps_validate_m6(const struct wpabuf *tlvs)
+{
+ struct wps_parse_attr attr;
+ int wps2;
+
+ if (tlvs == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No TLVs in M6");
+ return -1;
+ }
+ if (wps_parse_msg(tlvs, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse attributes "
+ "in M6");
+ return -1;
+ }
+
+ wps2 = attr.version2 != NULL;
+ if (wps_validate_version(attr.version, 1) ||
+ wps_validate_msg_type(attr.msg_type, 1) ||
+ wps_validate_enrollee_nonce(attr.enrollee_nonce, 1) ||
+ wps_validate_encr_settings(attr.encr_settings,
+ attr.encr_settings_len, 1) ||
+ wps_validate_version2(attr.version2, wps2) ||
+ wps_validate_authenticator(attr.authenticator, 1)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid M6");
+#ifdef WPS_STRICT_WPS2
+ if (wps2)
+ return -1;
+#else /* WPS_STRICT_WPS2 */
+ return -1;
+#endif /* WPS_STRICT_WPS2 */
+ }
+
+ return 0;
+}
+
+
+int wps_validate_m6_encr(const struct wpabuf *tlvs, int wps2)
+{
+ struct wps_parse_attr attr;
+
+ if (tlvs == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No TLVs in M6 encrypted "
+ "settings");
+ return -1;
+ }
+ if (wps_parse_msg(tlvs, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse attributes "
+ "in M6 encrypted settings");
+ return -1;
+ }
+
+ if (wps_validate_r_snonce2(attr.r_snonce2, 1) ||
+ wps_validate_key_wrap_auth(attr.key_wrap_auth, 1)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid M6 encrypted "
+ "settings");
+#ifdef WPS_STRICT_WPS2
+ if (wps2)
+ return -1;
+#else /* WPS_STRICT_WPS2 */
+ return -1;
+#endif /* WPS_STRICT_WPS2 */
+ }
+
+ return 0;
+}
+
+
+int wps_validate_m7(const struct wpabuf *tlvs)
+{
+ struct wps_parse_attr attr;
+ int wps2;
+
+ if (tlvs == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No TLVs in M7");
+ return -1;
+ }
+ if (wps_parse_msg(tlvs, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse attributes "
+ "in M7");
+ return -1;
+ }
+
+ wps2 = attr.version2 != NULL;
+ if (wps_validate_version(attr.version, 1) ||
+ wps_validate_msg_type(attr.msg_type, 1) ||
+ wps_validate_registrar_nonce(attr.registrar_nonce, 1) ||
+ wps_validate_encr_settings(attr.encr_settings,
+ attr.encr_settings_len, 1) ||
+ wps_validate_settings_delay_time(attr.settings_delay_time, 0) ||
+ wps_validate_version2(attr.version2, wps2) ||
+ wps_validate_authenticator(attr.authenticator, 1)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid M7");
+#ifdef WPS_STRICT_WPS2
+ if (wps2)
+ return -1;
+#else /* WPS_STRICT_WPS2 */
+ return -1;
+#endif /* WPS_STRICT_WPS2 */
+ }
+
+ return 0;
+}
+
+
+int wps_validate_m7_encr(const struct wpabuf *tlvs, int ap, int wps2)
+{
+ struct wps_parse_attr attr;
+
+ if (tlvs == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No TLVs in M7 encrypted "
+ "settings");
+ return -1;
+ }
+ if (wps_parse_msg(tlvs, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse attributes "
+ "in M7 encrypted settings");
+ return -1;
+ }
+
+ if (wps_validate_e_snonce2(attr.e_snonce2, 1) ||
+ wps_validate_ssid(attr.ssid, attr.ssid_len, !ap) ||
+ wps_validate_mac_addr(attr.mac_addr, !ap) ||
+ wps_validate_auth_type(attr.auth_type, !ap) ||
+ wps_validate_encr_type(attr.encr_type, !ap) ||
+ wps_validate_network_key_index(attr.network_key_idx, 0) ||
+ wps_validate_network_key(attr.network_key, attr.network_key_len,
+ attr.encr_type, !ap) ||
+ wps_validate_key_wrap_auth(attr.key_wrap_auth, 1)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid M7 encrypted "
+ "settings");
+#ifdef WPS_STRICT_WPS2
+ if (wps2)
+ return -1;
+#else /* WPS_STRICT_WPS2 */
+ return -1;
+#endif /* WPS_STRICT_WPS2 */
+ }
+
+ return 0;
+}
+
+
+int wps_validate_m8(const struct wpabuf *tlvs)
+{
+ struct wps_parse_attr attr;
+ int wps2;
+
+ if (tlvs == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No TLVs in M8");
+ return -1;
+ }
+ if (wps_parse_msg(tlvs, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse attributes "
+ "in M8");
+ return -1;
+ }
+
+ wps2 = attr.version2 != NULL;
+ if (wps_validate_version(attr.version, 1) ||
+ wps_validate_msg_type(attr.msg_type, 1) ||
+ wps_validate_enrollee_nonce(attr.enrollee_nonce, 1) ||
+ wps_validate_encr_settings(attr.encr_settings,
+ attr.encr_settings_len, 1) ||
+ wps_validate_version2(attr.version2, wps2) ||
+ wps_validate_authenticator(attr.authenticator, 1)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid M8");
+#ifdef WPS_STRICT_WPS2
+ if (wps2)
+ return -1;
+#else /* WPS_STRICT_WPS2 */
+ return -1;
+#endif /* WPS_STRICT_WPS2 */
+ }
+
+ return 0;
+}
+
+
+int wps_validate_m8_encr(const struct wpabuf *tlvs, int ap, int wps2)
+{
+ struct wps_parse_attr attr;
+
+ if (tlvs == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No TLVs in M8 encrypted "
+ "settings");
+ return -1;
+ }
+ if (wps_parse_msg(tlvs, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse attributes "
+ "in M8 encrypted settings");
+ return -1;
+ }
+
+ if (wps_validate_ssid(attr.ssid, attr.ssid_len, ap) ||
+ wps_validate_auth_type(attr.auth_type, ap) ||
+ wps_validate_encr_type(attr.encr_type, ap) ||
+ wps_validate_network_key_index(attr.network_key_idx, 0) ||
+ wps_validate_mac_addr(attr.mac_addr, ap) ||
+ wps_validate_credential(attr.cred, attr.cred_len, attr.num_cred,
+ !ap) ||
+ wps_validate_key_wrap_auth(attr.key_wrap_auth, 1)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid M8 encrypted "
+ "settings");
+#ifdef WPS_STRICT_WPS2
+ if (wps2)
+ return -1;
+#else /* WPS_STRICT_WPS2 */
+ return -1;
+#endif /* WPS_STRICT_WPS2 */
+ }
+
+ return 0;
+}
+
+
+int wps_validate_wsc_ack(const struct wpabuf *tlvs)
+{
+ struct wps_parse_attr attr;
+ int wps2;
+
+ if (tlvs == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No TLVs in WSC_ACK");
+ return -1;
+ }
+ if (wps_parse_msg(tlvs, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse attributes "
+ "in WSC_ACK");
+ return -1;
+ }
+
+ wps2 = attr.version2 != NULL;
+ if (wps_validate_version(attr.version, 1) ||
+ wps_validate_msg_type(attr.msg_type, 1) ||
+ wps_validate_enrollee_nonce(attr.enrollee_nonce, 1) ||
+ wps_validate_registrar_nonce(attr.registrar_nonce, 1) ||
+ wps_validate_version2(attr.version2, wps2)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid WSC_ACK");
+#ifdef WPS_STRICT_WPS2
+ if (wps2)
+ return -1;
+#else /* WPS_STRICT_WPS2 */
+ return -1;
+#endif /* WPS_STRICT_WPS2 */
+ }
+
+ return 0;
+}
+
+
+int wps_validate_wsc_nack(const struct wpabuf *tlvs)
+{
+ struct wps_parse_attr attr;
+ int wps2;
+
+ if (tlvs == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No TLVs in WSC_NACK");
+ return -1;
+ }
+ if (wps_parse_msg(tlvs, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse attributes "
+ "in WSC_NACK");
+ return -1;
+ }
+
+ wps2 = attr.version2 != NULL;
+ if (wps_validate_version(attr.version, 1) ||
+ wps_validate_msg_type(attr.msg_type, 1) ||
+ wps_validate_enrollee_nonce(attr.enrollee_nonce, 1) ||
+ wps_validate_registrar_nonce(attr.registrar_nonce, 1) ||
+ wps_validate_config_error(attr.config_error, 1) ||
+ wps_validate_version2(attr.version2, wps2)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid WSC_NACK");
+#ifdef WPS_STRICT_WPS2
+ if (wps2)
+ return -1;
+#else /* WPS_STRICT_WPS2 */
+ return -1;
+#endif /* WPS_STRICT_WPS2 */
+ }
+
+ return 0;
+}
+
+
+int wps_validate_wsc_done(const struct wpabuf *tlvs)
+{
+ struct wps_parse_attr attr;
+ int wps2;
+
+ if (tlvs == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No TLVs in WSC_Done");
+ return -1;
+ }
+ if (wps_parse_msg(tlvs, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse attributes "
+ "in WSC_Done");
+ return -1;
+ }
+
+ wps2 = attr.version2 != NULL;
+ if (wps_validate_version(attr.version, 1) ||
+ wps_validate_msg_type(attr.msg_type, 1) ||
+ wps_validate_enrollee_nonce(attr.enrollee_nonce, 1) ||
+ wps_validate_registrar_nonce(attr.registrar_nonce, 1) ||
+ wps_validate_version2(attr.version2, wps2)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid WSC_Done");
+#ifdef WPS_STRICT_WPS2
+ if (wps2)
+ return -1;
+#else /* WPS_STRICT_WPS2 */
+ return -1;
+#endif /* WPS_STRICT_WPS2 */
+ }
+
+ return 0;
+}
+
+
+int wps_validate_upnp_set_selected_registrar(const struct wpabuf *tlvs)
+{
+ struct wps_parse_attr attr;
+ int wps2;
+ int sel_reg;
+
+ if (tlvs == NULL) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: No TLVs in "
+ "SetSelectedRegistrar");
+ return -1;
+ }
+ if (wps_parse_msg(tlvs, &attr) < 0) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Failed to parse attributes "
+ "in SetSelectedRegistrar");
+ return -1;
+ }
+
+ wps2 = attr.version2 != NULL;
+ sel_reg = attr.selected_registrar != NULL &&
+ *attr.selected_registrar != 0;
+ if (wps_validate_version(attr.version, 1) ||
+ wps_validate_dev_password_id(attr.dev_password_id, sel_reg) ||
+ wps_validate_sel_reg_config_methods(attr.sel_reg_config_methods,
+ wps2, sel_reg) ||
+ wps_validate_version2(attr.version2, wps2) ||
+ wps_validate_authorized_macs(attr.authorized_macs,
+ attr.authorized_macs_len, wps2) ||
+ wps_validate_uuid_r(attr.uuid_r, wps2)) {
+ wpa_printf(MSG_INFO, "WPS-STRICT: Invalid "
+ "SetSelectedRegistrar");
+#ifdef WPS_STRICT_WPS2
+ if (wps2)
+ return -1;
+#else /* WPS_STRICT_WPS2 */
+ return -1;
+#endif /* WPS_STRICT_WPS2 */
+ }
+
+ return 0;
+}