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., &amp; for ampersand(&) and &lt 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 &lt; and &gt; 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, "&lt;");
+			continue;
+		}
+		if (c == '>') {
+			wpabuf_put_str(buf, "&gt;");
+			continue;
+		}
+		if (c == '&') {
+			wpabuf_put_str(buf, "&amp;");
+			continue;
+		}
+		if (c == '\'') {
+			wpabuf_put_str(buf, "&apos;");
+			continue;
+		}
+		if (c == '"') {
+			wpabuf_put_str(buf, "&quot;");
+			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(&reg->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(&reg->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(&reg->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, &reg->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, &reg->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, &reg->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, &reg->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, &reg->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, &reg->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(&reg->wps->dev, beacon)) ||
+	    wps_build_wfa_ext(beacon, 0, auth_macs, count) ||
+	    wps_build_vendor_ext(&reg->wps->dev, beacon)) {
+		wpabuf_free(beacon);
+		wpabuf_free(probe);
+		return -1;
+	}
+
+#ifdef CONFIG_P2P
+	if (wps_build_dev_name(&reg->wps->dev, beacon) ||
+	    wps_build_primary_dev_type(&reg->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(&reg->wps->dev, probe) ||
+	    wps_build_probe_config_methods(reg, probe) ||
+	    wps_build_rf_bands(&reg->wps->dev, probe) ||
+	    wps_build_wfa_ext(probe, 0, auth_macs, count) ||
+	    wps_build_vendor_ext(&reg->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, &reg->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;
+}