Cumulative patch from commit 39b420f7b12b152f879d1bb8a5267b9a838e04dc

39b420f HS 2.0R2: Add parse_cert command for debugging purposes
c0d701a HS 2.0R2: Add OSU client implementation
d2bb2b4 Add os_file_exists()
1cb3eda HS 2.0R2: Add wrapper functions for libcurl
2cb8f96 HS 2.0R2: Add wrapper functions for libxml2
da4ec2c HS 2.0R2: Add wrapper for system browser
db3ca20 HS 2.0R2: Add wrapper for wpadebug browser on Android
2a10e57 HS 2.0R2: Add wrapper for Android browser for user interaction
d0b79d7 HS 2.0R2: Add wrapper functions for WebKit
91728f2 Add wpa_ctrl helper functions for upper level functionality
b4c26ef Clean up hostapd_config_fill() parsers
b2e32cd Fix memory leaks on wpa_config_parse_string() error paths
a0b728b Simplify hostapd_config_fill() error reporting
599f40d Remove extra indentation level from hostapd_config_fill()

Change-Id: I17b7089210da7e083c9fe5814e79cef99bbee80b
Signed-off-by: Dmitry Shmidt <dimitrysh@google.com>
diff --git a/src/common/wpa_helpers.c b/src/common/wpa_helpers.c
new file mode 100644
index 0000000..28913b9
--- /dev/null
+++ b/src/common/wpa_helpers.c
@@ -0,0 +1,292 @@
+/*
+ * wpa_supplicant ctrl_iface helpers
+ * Copyright (c) 2010-2011, Atheros Communications, Inc.
+ * Copyright (c) 2011-2012, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "includes.h"
+#include <time.h>
+
+#include "common.h"
+#include "wpa_ctrl.h"
+#include "wpa_helpers.h"
+
+
+char *wpas_ctrl_path = "/var/run/wpa_supplicant/";
+static int default_timeout = 60;
+
+
+static struct wpa_ctrl * wpa_open_ctrl(const char *ifname)
+{
+	char buf[128];
+	struct wpa_ctrl *ctrl;
+
+	os_snprintf(buf, sizeof(buf), "%s%s", wpas_ctrl_path, ifname);
+	ctrl = wpa_ctrl_open(buf);
+	if (ctrl == NULL)
+		printf("wpa_command: wpa_ctrl_open(%s) failed\n", buf);
+	return ctrl;
+}
+
+
+int wpa_command(const char *ifname, const char *cmd)
+{
+	struct wpa_ctrl *ctrl;
+	char buf[128];
+	size_t len;
+
+	printf("wpa_command(ifname='%s', cmd='%s')\n", ifname, cmd);
+	ctrl = wpa_open_ctrl(ifname);
+	if (ctrl == NULL)
+		return -1;
+	len = sizeof(buf);
+	if (wpa_ctrl_request(ctrl, cmd, strlen(cmd), buf, &len, NULL) < 0) {
+		printf("wpa_command: wpa_ctrl_request failed\n");
+		wpa_ctrl_close(ctrl);
+		return -1;
+	}
+	wpa_ctrl_close(ctrl);
+	buf[len] = '\0';
+	if (strncmp(buf, "FAIL", 4) == 0) {
+		printf("wpa_command: Command failed (FAIL received)\n");
+		return -1;
+	}
+	return 0;
+}
+
+
+int wpa_command_resp(const char *ifname, const char *cmd,
+		     char *resp, size_t resp_size)
+{
+	struct wpa_ctrl *ctrl;
+	size_t len;
+
+	printf("wpa_command(ifname='%s', cmd='%s')\n", ifname, cmd);
+	ctrl = wpa_open_ctrl(ifname);
+	if (ctrl == NULL)
+		return -1;
+	len = resp_size;
+	if (wpa_ctrl_request(ctrl, cmd, strlen(cmd), resp, &len, NULL) < 0) {
+		printf("wpa_command: wpa_ctrl_request failed\n");
+		wpa_ctrl_close(ctrl);
+		return -1;
+	}
+	wpa_ctrl_close(ctrl);
+	resp[len] = '\0';
+	return 0;
+}
+
+
+struct wpa_ctrl * open_wpa_mon(const char *ifname)
+{
+	struct wpa_ctrl *ctrl;
+
+	ctrl = wpa_open_ctrl(ifname);
+	if (ctrl == NULL)
+		return NULL;
+	if (wpa_ctrl_attach(ctrl) < 0) {
+		wpa_ctrl_close(ctrl);
+		return NULL;
+	}
+
+	return ctrl;
+}
+
+
+int get_wpa_cli_event2(struct wpa_ctrl *mon,
+		       const char *event, const char *event2,
+		       char *buf, size_t buf_size)
+{
+	int fd, ret;
+	fd_set rfd;
+	char *pos;
+	struct timeval tv;
+	time_t start, now;
+
+	printf("Waiting for wpa_cli event %s\n", event);
+	fd = wpa_ctrl_get_fd(mon);
+	if (fd < 0)
+		return -1;
+
+	time(&start);
+	while (1) {
+		size_t len;
+
+		FD_ZERO(&rfd);
+		FD_SET(fd, &rfd);
+		tv.tv_sec = default_timeout;
+		tv.tv_usec = 0;
+		ret = select(fd + 1, &rfd, NULL, NULL, &tv);
+		if (ret == 0) {
+			printf("Timeout on waiting for event %s\n", event);
+			return -1;
+		}
+		if (ret < 0) {
+			printf("select: %s\n", strerror(errno));
+			return -1;
+		}
+		len = buf_size;
+		if (wpa_ctrl_recv(mon, buf, &len) < 0) {
+			printf("Failure while waiting for event %s\n", event);
+			return -1;
+		}
+		if (len == buf_size)
+			len--;
+		buf[len] = '\0';
+
+		pos = strchr(buf, '>');
+		if (pos &&
+		    (strncmp(pos + 1, event, strlen(event)) == 0 ||
+		     (event2 &&
+		      strncmp(pos + 1, event2, strlen(event2)) == 0)))
+			return 0; /* Event found */
+
+		time(&now);
+		if ((int) (now - start) > default_timeout) {
+			printf("Timeout on waiting for event %s\n", event);
+			return -1;
+		}
+	}
+}
+
+
+int get_wpa_cli_event(struct wpa_ctrl *mon,
+		      const char *event, char *buf, size_t buf_size)
+{
+	return get_wpa_cli_event2(mon, event, NULL, buf, buf_size);
+}
+
+
+int get_wpa_status(const char *ifname, const char *field, char *obuf,
+		   size_t obuf_size)
+{
+	struct wpa_ctrl *ctrl;
+	char buf[4096];
+	char *pos, *end;
+	size_t len, flen;
+
+	ctrl = wpa_open_ctrl(ifname);
+	if (ctrl == NULL)
+		return -1;
+	len = sizeof(buf);
+	if (wpa_ctrl_request(ctrl, "STATUS", 6, buf, &len, NULL) < 0) {
+		wpa_ctrl_close(ctrl);
+		return -1;
+	}
+	wpa_ctrl_close(ctrl);
+	buf[len] = '\0';
+
+	flen = strlen(field);
+	pos = buf;
+	while (pos + flen < buf + len) {
+		if (pos > buf) {
+			if (*pos != '\n') {
+				pos++;
+				continue;
+			}
+			pos++;
+		}
+		if (strncmp(pos, field, flen) != 0 || pos[flen] != '=') {
+			pos++;
+			continue;
+		}
+		pos += flen + 1;
+		end = strchr(pos, '\n');
+		if (end == NULL)
+			return -1;
+		*end++ = '\0';
+		if (end - pos > (int) obuf_size)
+			return -1;
+		memcpy(obuf, pos, end - pos);
+		return 0;
+	}
+
+	return -1;
+}
+
+
+int wait_ip_addr(const char *ifname, int timeout)
+{
+	char ip[30];
+	int count = timeout;
+	struct wpa_ctrl *ctrl;
+
+	while (count > 0) {
+		printf("%s: ifname='%s' - %d seconds remaining\n",
+		       __func__, ifname, count);
+		count--;
+		if (get_wpa_status(ifname, "ip_address", ip, sizeof(ip)) == 0
+		    && strlen(ip) > 0) {
+			printf("IP address found: '%s'\n", ip);
+			return 0;
+		}
+		ctrl = wpa_open_ctrl(ifname);
+		if (ctrl == NULL)
+			return -1;
+		wpa_ctrl_close(ctrl);
+		sleep(1);
+	}
+	printf("%s: Could not get IP address for ifname='%s'", __func__,
+	       ifname);
+	return -1;
+}
+
+
+int add_network(const char *ifname)
+{
+	char res[30];
+
+	if (wpa_command_resp(ifname, "ADD_NETWORK", res, sizeof(res)) < 0)
+		return -1;
+	return atoi(res);
+}
+
+
+int set_network(const char *ifname, int id, const char *field,
+		const char *value)
+{
+	char buf[200];
+	snprintf(buf, sizeof(buf), "SET_NETWORK %d %s %s", id, field, value);
+	return wpa_command(ifname, buf);
+}
+
+
+int set_network_quoted(const char *ifname, int id, const char *field,
+		       const char *value)
+{
+	char buf[200];
+	snprintf(buf, sizeof(buf), "SET_NETWORK %d %s \"%s\"",
+		 id, field, value);
+	return wpa_command(ifname, buf);
+}
+
+
+int add_cred(const char *ifname)
+{
+	char res[30];
+
+	if (wpa_command_resp(ifname, "ADD_CRED", res, sizeof(res)) < 0)
+		return -1;
+	return atoi(res);
+}
+
+
+int set_cred(const char *ifname, int id, const char *field, const char *value)
+{
+	char buf[200];
+	snprintf(buf, sizeof(buf), "SET_CRED %d %s %s", id, field, value);
+	return wpa_command(ifname, buf);
+}
+
+
+int set_cred_quoted(const char *ifname, int id, const char *field,
+		    const char *value)
+{
+	char buf[200];
+	snprintf(buf, sizeof(buf), "SET_CRED %d %s \"%s\"",
+		 id, field, value);
+	return wpa_command(ifname, buf);
+}
diff --git a/src/common/wpa_helpers.h b/src/common/wpa_helpers.h
new file mode 100644
index 0000000..54c2872
--- /dev/null
+++ b/src/common/wpa_helpers.h
@@ -0,0 +1,37 @@
+/*
+ * wpa_supplicant ctrl_iface helpers
+ * Copyright (c) 2010-2011, Atheros Communications, Inc.
+ * Copyright (c) 2011-2012, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef WPA_HELPERS_H
+#define WPA_HELPERS_H
+
+int wpa_command(const char *ifname, const char *cmd);
+int wpa_command_resp(const char *ifname, const char *cmd,
+		     char *resp, size_t resp_size);
+int get_wpa_status(const char *ifname, const char *field, char *obuf,
+		   size_t obuf_size);
+
+struct wpa_ctrl * open_wpa_mon(const char *ifname);
+int wait_ip_addr(const char *ifname, int timeout);
+int get_wpa_cli_event(struct wpa_ctrl *mon,
+		      const char *event, char *buf, size_t buf_size);
+int get_wpa_cli_event2(struct wpa_ctrl *mon,
+		       const char *event, const char *event2,
+		       char *buf, size_t buf_size);
+
+int add_network(const char *ifname);
+int set_network(const char *ifname, int id, const char *field,
+		const char *value);
+int set_network_quoted(const char *ifname, int id, const char *field,
+		       const char *value);
+int add_cred(const char *ifname);
+int set_cred(const char *ifname, int id, const char *field, const char *value);
+int set_cred_quoted(const char *ifname, int id, const char *field,
+		    const char *value);
+
+#endif /* WPA_HELPERS_H */
diff --git a/src/utils/browser-android.c b/src/utils/browser-android.c
new file mode 100644
index 0000000..a066392
--- /dev/null
+++ b/src/utils/browser-android.c
@@ -0,0 +1,117 @@
+/*
+ * Hotspot 2.0 client - Web browser using Android browser
+ * Copyright (c) 2013, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "utils/eloop.h"
+#include "wps/http_server.h"
+#include "browser.h"
+
+
+struct browser_data {
+	int success;
+};
+
+
+static void browser_timeout(void *eloop_data, void *user_ctx)
+{
+	wpa_printf(MSG_INFO, "Timeout on waiting browser interaction to "
+		   "complete");
+	eloop_terminate();
+}
+
+
+static void http_req(void *ctx, struct http_request *req)
+{
+	struct browser_data *data = ctx;
+	struct wpabuf *resp;
+	const char *url;
+	int done = 0;
+
+	url = http_request_get_uri(req);
+	wpa_printf(MSG_INFO, "Browser response received: %s", url);
+
+	if (os_strcmp(url, "/") == 0) {
+		data->success = 1;
+		done = 1;
+	} else if (os_strncmp(url, "/osu/", 5) == 0) {
+		data->success = atoi(url + 5);
+		done = 1;
+	}
+
+	resp = wpabuf_alloc(1);
+	if (resp == NULL) {
+		http_request_deinit(req);
+		if (done)
+			eloop_terminate();
+		return;
+	}
+
+	if (done) {
+		eloop_cancel_timeout(browser_timeout, NULL, NULL);
+		eloop_register_timeout(0, 500000, browser_timeout, &data, NULL);
+	}
+
+	http_request_send_and_deinit(req, resp);
+}
+
+
+int hs20_web_browser(const char *url)
+{
+	char cmd[2000];
+	int ret;
+	struct http_server *http;
+	struct in_addr addr;
+	struct browser_data data;
+
+	wpa_printf(MSG_INFO, "Launching Android browser to %s", url);
+
+	os_memset(&data, 0, sizeof(data));
+
+	ret = os_snprintf(cmd, sizeof(cmd),
+			  "am start -a android.intent.action.VIEW -d '%s' "
+			  "-n com.android.browser/.BrowserActivity", url);
+	if (ret < 0 || (size_t) ret >= sizeof(cmd)) {
+		wpa_printf(MSG_ERROR, "Too long URL");
+		return -1;
+	}
+
+	if (eloop_init() < 0) {
+		wpa_printf(MSG_ERROR, "eloop_init failed");
+		return -1;
+	}
+	addr.s_addr = htonl((127 << 24) | 1);
+	http = http_server_init(&addr, 12345, http_req, &data);
+	if (http == NULL) {
+		wpa_printf(MSG_ERROR, "http_server_init failed");
+		eloop_destroy();
+		return -1;
+	}
+
+	if (system(cmd) != 0) {
+		wpa_printf(MSG_INFO, "Failed to launch Android browser");
+		eloop_cancel_timeout(browser_timeout, NULL, NULL);
+		http_server_deinit(http);
+		eloop_destroy();
+		return -1;
+	}
+
+	eloop_register_timeout(30, 0, browser_timeout, &data, NULL);
+	eloop_run();
+	eloop_cancel_timeout(browser_timeout, &data, NULL);
+	http_server_deinit(http);
+	eloop_destroy();
+
+	wpa_printf(MSG_INFO, "Closing Android browser");
+	if (system("input keyevent 3") != 0) {
+		wpa_printf(MSG_INFO, "Failed to inject keyevent");
+	}
+
+	return data.success;
+}
diff --git a/src/utils/browser-system.c b/src/utils/browser-system.c
new file mode 100644
index 0000000..2884d34
--- /dev/null
+++ b/src/utils/browser-system.c
@@ -0,0 +1,112 @@
+/*
+ * Hotspot 2.0 client - Web browser using system browser
+ * Copyright (c) 2013, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "utils/eloop.h"
+#include "wps/http_server.h"
+#include "browser.h"
+
+
+struct browser_data {
+	int success;
+};
+
+
+static void browser_timeout(void *eloop_data, void *user_ctx)
+{
+	wpa_printf(MSG_INFO, "Timeout on waiting browser interaction to "
+		   "complete");
+	eloop_terminate();
+}
+
+
+static void http_req(void *ctx, struct http_request *req)
+{
+	struct browser_data *data = ctx;
+	struct wpabuf *resp;
+	const char *url;
+	int done = 0;
+
+	url = http_request_get_uri(req);
+	wpa_printf(MSG_INFO, "Browser response received: %s", url);
+
+	if (os_strcmp(url, "/") == 0) {
+		data->success = 1;
+		done = 1;
+	} else if (os_strncmp(url, "/osu/", 5) == 0) {
+		data->success = atoi(url + 5);
+		done = 1;
+	}
+
+	resp = wpabuf_alloc(1);
+	if (resp == NULL) {
+		http_request_deinit(req);
+		if (done)
+			eloop_terminate();
+		return;
+	}
+
+	if (done) {
+		eloop_cancel_timeout(browser_timeout, NULL, NULL);
+		eloop_register_timeout(0, 500000, browser_timeout, &data, NULL);
+	}
+
+	http_request_send_and_deinit(req, resp);
+}
+
+
+int hs20_web_browser(const char *url)
+{
+	char cmd[2000];
+	int ret;
+	struct http_server *http;
+	struct in_addr addr;
+	struct browser_data data;
+
+	wpa_printf(MSG_INFO, "Launching Android browser to %s", url);
+
+	os_memset(&data, 0, sizeof(data));
+
+	ret = os_snprintf(cmd, sizeof(cmd), "x-www-browser '%s' &", url);
+	if (ret < 0 || (size_t) ret >= sizeof(cmd)) {
+		wpa_printf(MSG_ERROR, "Too long URL");
+		return -1;
+	}
+
+	if (eloop_init() < 0) {
+		wpa_printf(MSG_ERROR, "eloop_init failed");
+		return -1;
+	}
+	addr.s_addr = htonl((127 << 24) | 1);
+	http = http_server_init(&addr, 12345, http_req, &data);
+	if (http == NULL) {
+		wpa_printf(MSG_ERROR, "http_server_init failed");
+		eloop_destroy();
+		return -1;
+	}
+
+	if (system(cmd) != 0) {
+		wpa_printf(MSG_INFO, "Failed to launch browser");
+		eloop_cancel_timeout(browser_timeout, NULL, NULL);
+		http_server_deinit(http);
+		eloop_destroy();
+		return -1;
+	}
+
+	eloop_register_timeout(120, 0, browser_timeout, &data, NULL);
+	eloop_run();
+	eloop_cancel_timeout(browser_timeout, &data, NULL);
+	http_server_deinit(http);
+	eloop_destroy();
+
+	/* TODO: Close browser */
+
+	return data.success;
+}
diff --git a/src/utils/browser-wpadebug.c b/src/utils/browser-wpadebug.c
new file mode 100644
index 0000000..eeb8f65
--- /dev/null
+++ b/src/utils/browser-wpadebug.c
@@ -0,0 +1,123 @@
+/*
+ * Hotspot 2.0 client - Web browser using wpadebug on Android
+ * Copyright (c) 2013, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "utils/eloop.h"
+#include "wps/http_server.h"
+#include "browser.h"
+
+
+struct browser_data {
+	int success;
+};
+
+
+static void browser_timeout(void *eloop_data, void *user_ctx)
+{
+	wpa_printf(MSG_INFO, "Timeout on waiting browser interaction to "
+		   "complete");
+	eloop_terminate();
+}
+
+
+static void http_req(void *ctx, struct http_request *req)
+{
+	struct browser_data *data = ctx;
+	struct wpabuf *resp;
+	const char *url;
+	int done = 0;
+
+	url = http_request_get_uri(req);
+	wpa_printf(MSG_INFO, "Browser response received: %s", url);
+
+	if (os_strcmp(url, "/") == 0) {
+		data->success = 1;
+		done = 1;
+	} else if (os_strncmp(url, "/osu/", 5) == 0) {
+		data->success = atoi(url + 5);
+		done = 1;
+	}
+
+	resp = wpabuf_alloc(100);
+	if (resp == NULL) {
+		http_request_deinit(req);
+		if (done)
+			eloop_terminate();
+		return;
+	}
+	wpabuf_put_str(resp, "User input completed");
+
+	if (done) {
+		eloop_cancel_timeout(browser_timeout, NULL, NULL);
+		eloop_register_timeout(0, 500000, browser_timeout, &data, NULL);
+	}
+
+	http_request_send_and_deinit(req, resp);
+}
+
+
+int hs20_web_browser(const char *url)
+{
+	char cmd[2000];
+	int ret;
+	struct http_server *http;
+	struct in_addr addr;
+	struct browser_data data;
+
+	wpa_printf(MSG_INFO, "Launching wpadebug browser to %s", url);
+
+	os_memset(&data, 0, sizeof(data));
+
+	ret = os_snprintf(cmd, sizeof(cmd),
+			  "am start -a android.action.MAIN "
+			  "-c android.intent.category.LAUNCHER "
+			  "-n w1.fi.wpadebug/.WpaWebViewActivity "
+			  "-e w1.fi.wpadebug.URL '%s'", url);
+	if (ret < 0 || (size_t) ret >= sizeof(cmd)) {
+		wpa_printf(MSG_ERROR, "Too long URL");
+		return -1;
+	}
+
+	if (eloop_init() < 0) {
+		wpa_printf(MSG_ERROR, "eloop_init failed");
+		return -1;
+	}
+	addr.s_addr = htonl((127 << 24) | 1);
+	http = http_server_init(&addr, 12345, http_req, &data);
+	if (http == NULL) {
+		wpa_printf(MSG_ERROR, "http_server_init failed");
+		eloop_destroy();
+		return -1;
+	}
+
+	if (system(cmd) != 0) {
+		wpa_printf(MSG_INFO, "Failed to launch wpadebug browser");
+		eloop_cancel_timeout(browser_timeout, NULL, NULL);
+		http_server_deinit(http);
+		eloop_destroy();
+		return -1;
+	}
+
+	eloop_register_timeout(300, 0, browser_timeout, &data, NULL);
+	eloop_run();
+	eloop_cancel_timeout(browser_timeout, &data, NULL);
+	http_server_deinit(http);
+	eloop_destroy();
+
+	wpa_printf(MSG_INFO, "Closing Android browser");
+	if (system("am start -a android.action.MAIN "
+		   "-c android.intent.category.LAUNCHER "
+		   "-n w1.fi.wpadebug/.WpaWebViewActivity "
+		   "-e w1.fi.wpadebug.URL FINISH") != 0) {
+		wpa_printf(MSG_INFO, "Failed to close wpadebug browser");
+	}
+
+	return data.success;
+}
diff --git a/src/utils/browser.c b/src/utils/browser.c
new file mode 100644
index 0000000..9cf6152
--- /dev/null
+++ b/src/utils/browser.c
@@ -0,0 +1,219 @@
+/*
+ * Hotspot 2.0 client - Web browser using WebKit
+ * Copyright (c) 2013, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "includes.h"
+#include <webkit/webkit.h>
+
+#include "common.h"
+#include "browser.h"
+
+
+struct browser_context {
+	GtkWidget *win;
+	int success;
+	int progress;
+	char *hover_link;
+	char *title;
+};
+
+static void win_cb_destroy(GtkWidget *win, struct browser_context *ctx)
+{
+	wpa_printf(MSG_DEBUG, "BROWSER:%s", __func__);
+	gtk_main_quit();
+}
+
+
+static void browser_update_title(struct browser_context *ctx)
+{
+	char buf[100];
+
+	if (ctx->hover_link) {
+		gtk_window_set_title(GTK_WINDOW(ctx->win), ctx->hover_link);
+		return;
+	}
+
+	if (ctx->progress == 100) {
+		gtk_window_set_title(GTK_WINDOW(ctx->win),
+				     ctx->title ? ctx->title :
+				     "Hotspot 2.0 client");
+		return;
+	}
+
+	snprintf(buf, sizeof(buf), "[%d%%] %s", ctx->progress,
+		 ctx->title ? ctx->title : "Hotspot 2.0 client");
+	gtk_window_set_title(GTK_WINDOW(ctx->win), buf);
+}
+
+
+static void view_cb_notify_progress(WebKitWebView *view, GParamSpec *pspec,
+				    struct browser_context *ctx)
+{
+	ctx->progress = 100 * webkit_web_view_get_progress(view);
+	wpa_printf(MSG_DEBUG, "BROWSER:%s progress=%d", __func__,
+		   ctx->progress);
+	browser_update_title(ctx);
+}
+
+
+static void view_cb_notify_load_status(WebKitWebView *view, GParamSpec *pspec,
+				       struct browser_context *ctx)
+{
+	int status = webkit_web_view_get_load_status(view);
+	wpa_printf(MSG_DEBUG, "BROWSER:%s load-status=%d uri=%s",
+		   __func__, status, webkit_web_view_get_uri(view));
+}
+
+
+static void view_cb_resource_request_starting(WebKitWebView *view,
+					      WebKitWebFrame *frame,
+					      WebKitWebResource *res,
+					      WebKitNetworkRequest *req,
+					      WebKitNetworkResponse *resp,
+					      struct browser_context *ctx)
+{
+	const gchar *uri = webkit_network_request_get_uri(req);
+	wpa_printf(MSG_DEBUG, "BROWSER:%s uri=%s", __func__, uri);
+	if (g_str_has_suffix(uri, "/favicon.ico"))
+		webkit_network_request_set_uri(req, "about:blank");
+	if (g_str_has_prefix(uri, "osu://")) {
+		ctx->success = atoi(uri + 6);
+		gtk_main_quit();
+	}
+	if (g_str_has_prefix(uri, "http://localhost:12345")) {
+		/*
+		 * This is used as a special trigger to indicate that the
+		 * user exchange has been completed.
+		 */
+		ctx->success = 1;
+		gtk_main_quit();
+	}
+}
+
+
+static gboolean view_cb_mime_type_policy_decision(
+	WebKitWebView *view, WebKitWebFrame *frame, WebKitNetworkRequest *req,
+	gchar *mime, WebKitWebPolicyDecision *policy,
+	struct browser_context *ctx)
+{
+	wpa_printf(MSG_DEBUG, "BROWSER:%s mime=%s", __func__, mime);
+
+	if (!webkit_web_view_can_show_mime_type(view, mime)) {
+		webkit_web_policy_decision_download(policy);
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+
+static gboolean view_cb_download_requested(WebKitWebView *view,
+					   WebKitDownload *dl,
+					   struct browser_context *ctx)
+{
+	const gchar *uri;
+	uri = webkit_download_get_uri(dl);
+	wpa_printf(MSG_DEBUG, "BROWSER:%s uri=%s", __func__, uri);
+	return FALSE;
+}
+
+
+static void view_cb_hovering_over_link(WebKitWebView *view, gchar *title,
+				       gchar *uri, struct browser_context *ctx)
+{
+	wpa_printf(MSG_DEBUG, "BROWSER:%s title=%s uri=%s", __func__, title,
+		   uri);
+	os_free(ctx->hover_link);
+	if (uri)
+		ctx->hover_link = os_strdup(uri);
+	else
+		ctx->hover_link = NULL;
+
+	browser_update_title(ctx);
+}
+
+
+static void view_cb_title_changed(WebKitWebView *view, WebKitWebFrame *frame,
+				  const char *title,
+				  struct browser_context *ctx)
+{
+	wpa_printf(MSG_DEBUG, "BROWSER:%s title=%s", __func__, title);
+	os_free(ctx->title);
+	ctx->title = os_strdup(title);
+	browser_update_title(ctx);
+}
+
+
+int hs20_web_browser(const char *url)
+{
+	GtkWidget *scroll;
+	SoupSession *s;
+	WebKitWebView *view;
+	WebKitWebSettings *settings;
+	struct browser_context ctx;
+
+	memset(&ctx, 0, sizeof(ctx));
+	if (!gtk_init_check(NULL, NULL))
+		return -1;
+
+	s = webkit_get_default_session();
+	g_object_set(G_OBJECT(s), "ssl-ca-file",
+		     "/etc/ssl/certs/ca-certificates.crt", NULL);
+	g_object_set(G_OBJECT(s), "ssl-strict", FALSE, NULL);
+
+	ctx.win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+	gtk_window_set_wmclass(GTK_WINDOW(ctx.win), "Hotspot 2.0 client",
+			       "Hotspot 2.0 client");
+	gtk_window_set_default_size(GTK_WINDOW(ctx.win), 800, 600);
+
+	scroll = gtk_scrolled_window_new(NULL, NULL);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
+				       GTK_POLICY_NEVER, GTK_POLICY_NEVER);
+
+	g_signal_connect(G_OBJECT(ctx.win), "destroy",
+			 G_CALLBACK(win_cb_destroy), &ctx);
+
+	view = WEBKIT_WEB_VIEW(webkit_web_view_new());
+	g_signal_connect(G_OBJECT(view), "notify::progress",
+			 G_CALLBACK(view_cb_notify_progress), &ctx);
+	g_signal_connect(G_OBJECT(view), "notify::load-status",
+			 G_CALLBACK(view_cb_notify_load_status), &ctx);
+	g_signal_connect(G_OBJECT(view), "resource-request-starting",
+			 G_CALLBACK(view_cb_resource_request_starting), &ctx);
+	g_signal_connect(G_OBJECT(view), "mime-type-policy-decision-requested",
+			 G_CALLBACK(view_cb_mime_type_policy_decision), &ctx);
+	g_signal_connect(G_OBJECT(view), "download-requested",
+			 G_CALLBACK(view_cb_download_requested), &ctx);
+	g_signal_connect(G_OBJECT(view), "hovering-over-link",
+			 G_CALLBACK(view_cb_hovering_over_link), &ctx);
+	g_signal_connect(G_OBJECT(view), "title-changed",
+			 G_CALLBACK(view_cb_title_changed), &ctx);
+
+	gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(view));
+	gtk_container_add(GTK_CONTAINER(ctx.win), GTK_WIDGET(scroll));
+
+	gtk_widget_grab_focus(GTK_WIDGET(view));
+	gtk_widget_show_all(ctx.win);
+
+	settings = webkit_web_view_get_settings(view);
+	g_object_set(G_OBJECT(settings), "user-agent",
+		     "Mozilla/5.0 (X11; U; Unix; en-US) "
+		     "AppleWebKit/537.15 (KHTML, like Gecko) "
+		     "hs20-client/1.0", NULL);
+	g_object_set(G_OBJECT(settings), "auto-load-images", TRUE, NULL);
+
+	webkit_web_view_load_uri(view, url);
+
+	gtk_main();
+	gtk_widget_destroy(ctx.win);
+	while (gtk_events_pending())
+		gtk_main_iteration();
+
+	free(ctx.hover_link);
+	free(ctx.title);
+	return ctx.success;
+}
diff --git a/src/utils/browser.h b/src/utils/browser.h
new file mode 100644
index 0000000..aaa0eed
--- /dev/null
+++ b/src/utils/browser.h
@@ -0,0 +1,21 @@
+/*
+ * Hotspot 2.0 client - Web browser
+ * Copyright (c) 2013, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef BROWSER_H
+#define BROWSER_H
+
+#ifdef CONFIG_NO_BROWSER
+static inline int hs20_web_browser(const char *url)
+{
+	return -1;
+}
+#else /* CONFIG_NO_BROWSER */
+int hs20_web_browser(const char *url);
+#endif /* CONFIG_NO_BROWSER */
+
+#endif /* BROWSER_H */
diff --git a/src/utils/http-utils.h b/src/utils/http-utils.h
new file mode 100644
index 0000000..8d4399a
--- /dev/null
+++ b/src/utils/http-utils.h
@@ -0,0 +1,63 @@
+/*
+ * HTTP wrapper
+ * Copyright (c) 2012-2013, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef HTTP_UTILS_H
+#define HTTP_UTILS_H
+
+struct http_ctx;
+
+struct http_othername {
+	char *oid;
+	u8 *data;
+	size_t len;
+};
+
+#define HTTP_MAX_CERT_LOGO_HASH 32
+
+struct http_logo {
+	char *alg_oid;
+	u8 *hash;
+	size_t hash_len;
+	char *uri;
+};
+
+struct http_cert {
+	char **dnsname;
+	unsigned int num_dnsname;
+	struct http_othername *othername;
+	unsigned int num_othername;
+	struct http_logo *logo;
+	unsigned int num_logo;
+};
+
+int soap_init_client(struct http_ctx *ctx, const char *address,
+		     const char *ca_fname, const char *username,
+		     const char *password, const char *client_cert,
+		     const char *client_key);
+int soap_reinit_client(struct http_ctx *ctx);
+xml_node_t * soap_send_receive(struct http_ctx *ctx, xml_node_t *node);
+
+struct http_ctx * http_init_ctx(void *upper_ctx, struct xml_node_ctx *xml_ctx);
+void http_ocsp_set(struct http_ctx *ctx, int val);
+void http_deinit_ctx(struct http_ctx *ctx);
+
+int http_download_file(struct http_ctx *ctx, const char *url,
+		       const char *fname, const char *ca_fname);
+char * http_post(struct http_ctx *ctx, const char *url, const char *data,
+		 const char *content_type, const char *ext_hdr,
+		 const char *ca_fname,
+		 const char *username, const char *password,
+		 const char *client_cert, const char *client_key,
+		 size_t *resp_len);
+void http_set_cert_cb(struct http_ctx *ctx,
+		      int (*cb)(void *ctx, struct http_cert *cert),
+		      void *cb_ctx);
+const char * http_get_err(struct http_ctx *ctx);
+void http_parse_x509_certificate(struct http_ctx *ctx, const char *fname);
+
+#endif /* HTTP_UTILS_H */
diff --git a/src/utils/http_curl.c b/src/utils/http_curl.c
new file mode 100644
index 0000000..668c1a6
--- /dev/null
+++ b/src/utils/http_curl.c
@@ -0,0 +1,1642 @@
+/*
+ * HTTP wrapper for libcurl
+ * Copyright (c) 2012-2013, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "includes.h"
+#include <curl/curl.h>
+#ifdef EAP_TLS_OPENSSL
+#include <openssl/ssl.h>
+#include <openssl/asn1.h>
+#include <openssl/asn1t.h>
+#include <openssl/x509v3.h>
+
+#ifdef SSL_set_tlsext_status_type
+#ifndef OPENSSL_NO_TLSEXT
+#define HAVE_OCSP
+#include <openssl/err.h>
+#include <openssl/ocsp.h>
+#endif /* OPENSSL_NO_TLSEXT */
+#endif /* SSL_set_tlsext_status_type */
+#endif /* EAP_TLS_OPENSSL */
+
+#include "common.h"
+#include "xml-utils.h"
+#include "http-utils.h"
+
+
+struct http_ctx {
+	void *ctx;
+	struct xml_node_ctx *xml;
+	CURL *curl;
+	struct curl_slist *curl_hdr;
+	char *svc_address;
+	char *svc_ca_fname;
+	char *svc_username;
+	char *svc_password;
+	char *svc_client_cert;
+	char *svc_client_key;
+	char *curl_buf;
+	size_t curl_buf_len;
+
+	int (*cert_cb)(void *ctx, struct http_cert *cert);
+	void *cert_cb_ctx;
+
+	enum {
+		NO_OCSP, OPTIONAL_OCSP, MANDATORY_OCSP
+	} ocsp;
+	X509 *peer_cert;
+	X509 *peer_issuer;
+	X509 *peer_issuer_issuer;
+
+	const char *last_err;
+};
+
+
+static void clear_curl(struct http_ctx *ctx)
+{
+	if (ctx->curl) {
+		curl_easy_cleanup(ctx->curl);
+		ctx->curl = NULL;
+	}
+	if (ctx->curl_hdr) {
+		curl_slist_free_all(ctx->curl_hdr);
+		ctx->curl_hdr = NULL;
+	}
+}
+
+
+static void clone_str(char **dst, const char *src)
+{
+	os_free(*dst);
+	if (src)
+		*dst = os_strdup(src);
+	else
+		*dst = NULL;
+}
+
+
+static void debug_dump(struct http_ctx *ctx, const char *title,
+		       const char *buf, size_t len)
+{
+	char *txt = os_malloc(len + 1);
+	if (txt == NULL)
+		return;
+	os_memcpy(txt, buf, len);
+	txt[len] = '\0';
+	while (len > 0) {
+		len--;
+		if (txt[len] == '\r' || txt[len] == '\n')
+		    txt[len] = '\0';
+	}
+	wpa_printf(MSG_MSGDUMP, "%s[%s]", title, txt);
+	os_free(txt);
+}
+
+
+static int curl_cb_debug(CURL *curl, curl_infotype info, char *buf, size_t len,
+			 void *userdata)
+{
+	struct http_ctx *ctx = userdata;
+	switch (info) {
+	case CURLINFO_TEXT:
+		debug_dump(ctx, "CURLINFO_TEXT", buf, len);
+		break;
+	case CURLINFO_HEADER_IN:
+		debug_dump(ctx, "CURLINFO_HEADER_IN", buf, len);
+		break;
+	case CURLINFO_HEADER_OUT:
+		debug_dump(ctx, "CURLINFO_HEADER_OUT", buf, len);
+		break;
+	case CURLINFO_DATA_IN:
+		debug_dump(ctx, "CURLINFO_DATA_IN", buf, len);
+		break;
+	case CURLINFO_DATA_OUT:
+		debug_dump(ctx, "CURLINFO_DATA_OUT", buf, len);
+		break;
+	case CURLINFO_SSL_DATA_IN:
+		wpa_printf(MSG_DEBUG, "debug - CURLINFO_SSL_DATA_IN - %d",
+			   (int) len);
+		break;
+	case CURLINFO_SSL_DATA_OUT:
+		wpa_printf(MSG_DEBUG, "debug - CURLINFO_SSL_DATA_OUT - %d",
+			   (int) len);
+		break;
+	case CURLINFO_END:
+		wpa_printf(MSG_DEBUG, "debug - CURLINFO_END - %d",
+			   (int) len);
+		break;
+	}
+	return 0;
+}
+
+
+static size_t curl_cb_header(void *ptr, size_t size, size_t nmemb,
+			     void *userdata)
+{
+	struct http_ctx *ctx = userdata;
+	debug_dump(ctx, "curl header", ptr, size * nmemb);
+	return size * nmemb;
+}
+
+
+static size_t curl_cb_write(void *ptr, size_t size, size_t nmemb,
+			    void *userdata)
+{
+	struct http_ctx *ctx = userdata;
+	char *n;
+	debug_dump(ctx, "curl write", ptr, size * nmemb);
+	n = os_realloc(ctx->curl_buf, ctx->curl_buf_len + size * nmemb + 1);
+	if (n == NULL)
+		return 0;
+	ctx->curl_buf = n;
+	os_memcpy(n + ctx->curl_buf_len, ptr, size * nmemb);
+	n[ctx->curl_buf_len + size * nmemb] = '\0';
+	ctx->curl_buf_len += size * nmemb;
+	return size * nmemb;
+}
+
+
+#ifdef EAP_TLS_OPENSSL
+
+static void debug_dump_cert(const char *title, X509 *cert)
+{
+	BIO *out;
+	char *txt;
+	size_t rlen;
+
+	out = BIO_new(BIO_s_mem());
+	if (!out)
+		return;
+
+	X509_print_ex(out, cert, XN_FLAG_COMPAT, X509_FLAG_COMPAT);
+	rlen = BIO_ctrl_pending(out);
+	txt = os_malloc(rlen + 1);
+	if (txt) {
+		int res = BIO_read(out, txt, rlen);
+		if (res > 0) {
+			txt[res] = '\0';
+			wpa_printf(MSG_MSGDUMP, "%s:\n%s", title, txt);
+		}
+		os_free(txt);
+	}
+	BIO_free(out);
+}
+
+
+static void add_alt_name_othername(struct http_ctx *ctx, struct http_cert *cert,
+				   OTHERNAME *o)
+{
+	char txt[100];
+	int res;
+	struct http_othername *on;
+	ASN1_TYPE *val;
+
+	on = os_realloc_array(cert->othername, cert->num_othername + 1,
+			      sizeof(struct http_othername));
+	if (on == NULL)
+		return;
+	cert->othername = on;
+	on = &on[cert->num_othername];
+	os_memset(on, 0, sizeof(*on));
+
+	res = OBJ_obj2txt(txt, sizeof(txt), o->type_id, 1);
+	if (res < 0 || res >= (int) sizeof(txt))
+		return;
+
+	on->oid = os_strdup(txt);
+	if (on->oid == NULL)
+		return;
+
+	val = o->value;
+	on->data = val->value.octet_string->data;
+	on->len = val->value.octet_string->length;
+
+	cert->num_othername++;
+}
+
+
+static void add_alt_name_dns(struct http_ctx *ctx, struct http_cert *cert,
+			     ASN1_STRING *name)
+{
+	char *buf;
+	char **n;
+
+	buf = NULL;
+	if (ASN1_STRING_to_UTF8((unsigned char **) &buf, name) < 0)
+		return;
+
+	n = os_realloc_array(cert->dnsname, cert->num_dnsname + 1,
+			     sizeof(char *));
+	if (n == NULL)
+		return;
+
+	cert->dnsname = n;
+	n[cert->num_dnsname] = buf;
+	cert->num_dnsname++;
+}
+
+
+static void add_alt_name(struct http_ctx *ctx, struct http_cert *cert,
+			 const GENERAL_NAME *name)
+{
+	switch (name->type) {
+	case GEN_OTHERNAME:
+		add_alt_name_othername(ctx, cert, name->d.otherName);
+		break;
+	case GEN_DNS:
+		add_alt_name_dns(ctx, cert, name->d.dNSName);
+		break;
+	}
+}
+
+
+static void add_alt_names(struct http_ctx *ctx, struct http_cert *cert,
+			  GENERAL_NAMES *names)
+{
+	int num, i;
+
+	num = sk_GENERAL_NAME_num(names);
+	for (i = 0; i < num; i++) {
+		const GENERAL_NAME *name;
+		name = sk_GENERAL_NAME_value(names, i);
+		add_alt_name(ctx, cert, name);
+	}
+}
+
+
+/* RFC 3709 */
+
+typedef struct {
+	X509_ALGOR *hashAlg;
+	ASN1_OCTET_STRING *hashValue;
+} HashAlgAndValue;
+
+typedef struct {
+	STACK_OF(HashAlgAndValue) *refStructHash;
+	STACK_OF(ASN1_IA5STRING) *refStructURI;
+} LogotypeReference;
+
+typedef struct {
+	ASN1_IA5STRING *mediaType;
+	STACK_OF(HashAlgAndValue) *logotypeHash;
+	STACK_OF(ASN1_IA5STRING) *logotypeURI;
+} LogotypeDetails;
+
+typedef struct {
+	int type;
+	union {
+		ASN1_INTEGER *numBits;
+		ASN1_INTEGER *tableSize;
+	} d;
+} LogotypeImageResolution;
+
+typedef struct {
+	ASN1_INTEGER *type; /* LogotypeImageType ::= INTEGER */
+	ASN1_INTEGER *fileSize;
+	ASN1_INTEGER *xSize;
+	ASN1_INTEGER *ySize;
+	LogotypeImageResolution *resolution;
+	ASN1_IA5STRING *language;
+} LogotypeImageInfo;
+
+typedef struct {
+	LogotypeDetails *imageDetails;
+	LogotypeImageInfo *imageInfo;
+} LogotypeImage;
+
+typedef struct {
+	ASN1_INTEGER *fileSize;
+	ASN1_INTEGER *playTime;
+	ASN1_INTEGER *channels;
+	ASN1_INTEGER *sampleRate;
+	ASN1_IA5STRING *language;
+} LogotypeAudioInfo;
+
+typedef struct {
+	LogotypeDetails *audioDetails;
+	LogotypeAudioInfo *audioInfo;
+} LogotypeAudio;
+
+typedef struct {
+	STACK_OF(LogotypeImage) *image;
+	STACK_OF(LogotypeAudio) *audio;
+} LogotypeData;
+
+typedef struct {
+	int type;
+	union {
+		LogotypeData *direct;
+		LogotypeReference *indirect;
+	} d;
+} LogotypeInfo;
+
+typedef struct {
+	ASN1_OBJECT *logotypeType;
+	LogotypeInfo *info;
+} OtherLogotypeInfo;
+
+typedef struct {
+	STACK_OF(LogotypeInfo) *communityLogos;
+	LogotypeInfo *issuerLogo;
+	LogotypeInfo *subjectLogo;
+	STACK_OF(OtherLogotypeInfo) *otherLogos;
+} LogotypeExtn;
+
+ASN1_SEQUENCE(HashAlgAndValue) = {
+	ASN1_SIMPLE(HashAlgAndValue, hashAlg, X509_ALGOR),
+	ASN1_SIMPLE(HashAlgAndValue, hashValue, ASN1_OCTET_STRING)
+} ASN1_SEQUENCE_END(HashAlgAndValue);
+
+ASN1_SEQUENCE(LogotypeReference) = {
+	ASN1_SEQUENCE_OF(LogotypeReference, refStructHash, HashAlgAndValue),
+	ASN1_SEQUENCE_OF(LogotypeReference, refStructURI, ASN1_IA5STRING)
+} ASN1_SEQUENCE_END(LogotypeReference);
+
+ASN1_SEQUENCE(LogotypeDetails) = {
+	ASN1_SIMPLE(LogotypeDetails, mediaType, ASN1_IA5STRING),
+	ASN1_SEQUENCE_OF(LogotypeDetails, logotypeHash, HashAlgAndValue),
+	ASN1_SEQUENCE_OF(LogotypeDetails, logotypeURI, ASN1_IA5STRING)
+} ASN1_SEQUENCE_END(LogotypeDetails);
+
+ASN1_CHOICE(LogotypeImageResolution) = {
+	ASN1_IMP(LogotypeImageResolution, d.numBits, ASN1_INTEGER, 1),
+	ASN1_IMP(LogotypeImageResolution, d.tableSize, ASN1_INTEGER, 2)
+} ASN1_CHOICE_END(LogotypeImageResolution);
+
+ASN1_SEQUENCE(LogotypeImageInfo) = {
+	ASN1_IMP_OPT(LogotypeImageInfo, type, ASN1_INTEGER, 0),
+	ASN1_SIMPLE(LogotypeImageInfo, fileSize, ASN1_INTEGER),
+	ASN1_SIMPLE(LogotypeImageInfo, xSize, ASN1_INTEGER),
+	ASN1_SIMPLE(LogotypeImageInfo, ySize, ASN1_INTEGER),
+	ASN1_OPT(LogotypeImageInfo, resolution, LogotypeImageResolution),
+	ASN1_IMP_OPT(LogotypeImageInfo, language, ASN1_IA5STRING, 4),
+} ASN1_SEQUENCE_END(LogotypeImageInfo);
+
+ASN1_SEQUENCE(LogotypeImage) = {
+	ASN1_SIMPLE(LogotypeImage, imageDetails, LogotypeDetails),
+	ASN1_OPT(LogotypeImage, imageInfo, LogotypeImageInfo)
+} ASN1_SEQUENCE_END(LogotypeImage);
+
+ASN1_SEQUENCE(LogotypeAudioInfo) = {
+	ASN1_SIMPLE(LogotypeAudioInfo, fileSize, ASN1_INTEGER),
+	ASN1_SIMPLE(LogotypeAudioInfo, playTime, ASN1_INTEGER),
+	ASN1_SIMPLE(LogotypeAudioInfo, channels, ASN1_INTEGER),
+	ASN1_IMP_OPT(LogotypeAudioInfo, sampleRate, ASN1_INTEGER, 3),
+	ASN1_IMP_OPT(LogotypeAudioInfo, language, ASN1_IA5STRING, 4)
+} ASN1_SEQUENCE_END(LogotypeAudioInfo);
+
+ASN1_SEQUENCE(LogotypeAudio) = {
+	ASN1_SIMPLE(LogotypeAudio, audioDetails, LogotypeDetails),
+	ASN1_OPT(LogotypeAudio, audioInfo, LogotypeAudioInfo)
+} ASN1_SEQUENCE_END(LogotypeAudio);
+
+ASN1_SEQUENCE(LogotypeData) = {
+	ASN1_SEQUENCE_OF_OPT(LogotypeData, image, LogotypeImage),
+	ASN1_IMP_SEQUENCE_OF_OPT(LogotypeData, audio, LogotypeAudio, 1)
+} ASN1_SEQUENCE_END(LogotypeData);
+
+ASN1_CHOICE(LogotypeInfo) = {
+	ASN1_IMP(LogotypeInfo, d.direct, LogotypeData, 0),
+	ASN1_IMP(LogotypeInfo, d.indirect, LogotypeReference, 1)
+} ASN1_CHOICE_END(LogotypeInfo);
+
+ASN1_SEQUENCE(OtherLogotypeInfo) = {
+	ASN1_SIMPLE(OtherLogotypeInfo, logotypeType, ASN1_OBJECT),
+	ASN1_SIMPLE(OtherLogotypeInfo, info, LogotypeInfo)
+} ASN1_SEQUENCE_END(OtherLogotypeInfo);
+
+ASN1_SEQUENCE(LogotypeExtn) = {
+	ASN1_EXP_SEQUENCE_OF_OPT(LogotypeExtn, communityLogos, LogotypeInfo, 0),
+	ASN1_EXP_OPT(LogotypeExtn, issuerLogo, LogotypeInfo, 1),
+	ASN1_EXP_OPT(LogotypeExtn, issuerLogo, LogotypeInfo, 2),
+	ASN1_EXP_SEQUENCE_OF_OPT(LogotypeExtn, otherLogos, OtherLogotypeInfo, 3)
+} ASN1_SEQUENCE_END(LogotypeExtn);
+
+IMPLEMENT_ASN1_FUNCTIONS(LogotypeExtn);
+
+#define sk_LogotypeInfo_num(st) SKM_sk_num(LogotypeInfo, (st))
+#define sk_LogotypeInfo_value(st, i) SKM_sk_value(LogotypeInfo, (st), (i))
+#define sk_LogotypeImage_num(st) SKM_sk_num(LogotypeImage, (st))
+#define sk_LogotypeImage_value(st, i) SKM_sk_value(LogotypeImage, (st), (i))
+#define sk_LogotypeAudio_num(st) SKM_sk_num(LogotypeAudio, (st))
+#define sk_LogotypeAudio_value(st, i) SKM_sk_value(LogotypeAudio, (st), (i))
+#define sk_HashAlgAndValue_num(st) SKM_sk_num(HashAlgAndValue, (st))
+#define sk_HashAlgAndValue_value(st, i) SKM_sk_value(HashAlgAndValue, (st), (i))
+#define sk_ASN1_IA5STRING_num(st) SKM_sk_num(ASN1_IA5STRING, (st))
+#define sk_ASN1_IA5STRING_value(st, i) SKM_sk_value(ASN1_IA5STRING, (st), (i))
+
+
+static void add_logo(struct http_ctx *ctx, struct http_cert *hcert,
+		     HashAlgAndValue *hash, ASN1_IA5STRING *uri)
+{
+	char txt[100];
+	int res, len;
+	struct http_logo *n;
+
+	if (hash == NULL || uri == NULL)
+		return;
+
+	res = OBJ_obj2txt(txt, sizeof(txt), hash->hashAlg->algorithm, 1);
+	if (res < 0 || res >= (int) sizeof(txt))
+		return;
+
+	n = os_realloc_array(hcert->logo, hcert->num_logo + 1,
+			     sizeof(struct http_logo));
+	if (n == NULL)
+		return;
+	hcert->logo = n;
+	n = &hcert->logo[hcert->num_logo];
+	os_memset(n, 0, sizeof(*n));
+
+	n->alg_oid = os_strdup(txt);
+	if (n->alg_oid == NULL)
+		return;
+
+	n->hash_len = ASN1_STRING_length(hash->hashValue);
+	n->hash = os_malloc(n->hash_len);
+	if (n->hash == NULL) {
+		os_free(n->alg_oid);
+		return;
+	}
+	os_memcpy(n->hash, ASN1_STRING_data(hash->hashValue), n->hash_len);
+
+	len = ASN1_STRING_length(uri);
+	n->uri = os_malloc(len + 1);
+	if (n->uri == NULL) {
+		os_free(n->alg_oid);
+		os_free(n->hash);
+		return;
+	}
+	os_memcpy(n->uri, ASN1_STRING_data(uri), len);
+	n->uri[len] = '\0';
+
+	hcert->num_logo++;
+}
+
+
+static void add_logo_direct(struct http_ctx *ctx, struct http_cert *hcert,
+			    LogotypeData *data)
+{
+	int i, num;
+
+	if (data->image == NULL)
+		return;
+
+	num = sk_LogotypeImage_num(data->image);
+	for (i = 0; i < num; i++) {
+		LogotypeImage *image;
+		LogotypeDetails *details;
+		int j, hash_num, uri_num;
+		HashAlgAndValue *found_hash = NULL;
+
+		image = sk_LogotypeImage_value(data->image, i);
+		if (image == NULL)
+			continue;
+
+		details = image->imageDetails;
+		if (details == NULL)
+			continue;
+
+		hash_num = sk_HashAlgAndValue_num(details->logotypeHash);
+		for (j = 0; j < hash_num; j++) {
+			HashAlgAndValue *hash;
+			char txt[100];
+			int res;
+			hash = sk_HashAlgAndValue_value(details->logotypeHash,
+							j);
+			if (hash == NULL)
+				continue;
+			res = OBJ_obj2txt(txt, sizeof(txt),
+					  hash->hashAlg->algorithm, 1);
+			if (res < 0 || res >= (int) sizeof(txt))
+				continue;
+			if (os_strcmp(txt, "2.16.840.1.101.3.4.2.1") == 0) {
+				found_hash = hash;
+				break;
+			}
+		}
+
+		if (!found_hash) {
+			wpa_printf(MSG_DEBUG, "OpenSSL: No SHA256 hash found for the logo");
+			continue;
+		}
+
+		uri_num = sk_ASN1_IA5STRING_num(details->logotypeURI);
+		for (j = 0; j < uri_num; j++) {
+			ASN1_IA5STRING *uri;
+			uri = sk_ASN1_IA5STRING_value(details->logotypeURI, j);
+			add_logo(ctx, hcert, found_hash, uri);
+		}
+	}
+}
+
+
+static void add_logo_indirect(struct http_ctx *ctx, struct http_cert *hcert,
+			      LogotypeReference *ref)
+{
+	int j, hash_num, uri_num;
+
+	hash_num = sk_HashAlgAndValue_num(ref->refStructHash);
+	uri_num = sk_ASN1_IA5STRING_num(ref->refStructURI);
+	if (hash_num != uri_num) {
+		wpa_printf(MSG_INFO, "Unexpected LogotypeReference array size difference %d != %d",
+			   hash_num, uri_num);
+		return;
+	}
+
+	for (j = 0; j < hash_num; j++) {
+		HashAlgAndValue *hash;
+		ASN1_IA5STRING *uri;
+		hash = sk_HashAlgAndValue_value(ref->refStructHash, j);
+		uri = sk_ASN1_IA5STRING_value(ref->refStructURI, j);
+		add_logo(ctx, hcert, hash, uri);
+	}
+}
+
+
+static void i2r_HashAlgAndValue(HashAlgAndValue *hash, BIO *out, int indent)
+{
+	int i;
+	const unsigned char *data;
+
+	BIO_printf(out, "%*shashAlg: ", indent, "");
+	i2a_ASN1_OBJECT(out, hash->hashAlg->algorithm);
+	BIO_printf(out, "\n");
+
+	BIO_printf(out, "%*shashValue: ", indent, "");
+	data = hash->hashValue->data;
+	for (i = 0; i < hash->hashValue->length; i++)
+		BIO_printf(out, "%s%02x", i > 0 ? ":" : "", data[i]);
+	BIO_printf(out, "\n");
+}
+
+static void i2r_LogotypeDetails(LogotypeDetails *details, BIO *out, int indent)
+{
+	int i, num;
+
+	BIO_printf(out, "%*sLogotypeDetails\n", indent, "");
+	if (details->mediaType) {
+		BIO_printf(out, "%*smediaType: ", indent, "");
+		ASN1_STRING_print(out, details->mediaType);
+		BIO_printf(out, "\n");
+	}
+
+	num = details->logotypeHash ?
+		sk_HashAlgAndValue_num(details->logotypeHash) : 0;
+	for (i = 0; i < num; i++) {
+		HashAlgAndValue *hash;
+		hash = sk_HashAlgAndValue_value(details->logotypeHash, i);
+		i2r_HashAlgAndValue(hash, out, indent);
+	}
+
+	num = details->logotypeURI ?
+		sk_ASN1_IA5STRING_num(details->logotypeURI) : 0;
+	for (i = 0; i < num; i++) {
+		ASN1_IA5STRING *uri;
+		uri = sk_ASN1_IA5STRING_value(details->logotypeURI, i);
+		BIO_printf(out, "%*slogotypeURI: ", indent, "");
+		ASN1_STRING_print(out, uri);
+		BIO_printf(out, "\n");
+	}
+}
+
+static void i2r_LogotypeImageInfo(LogotypeImageInfo *info, BIO *out, int indent)
+{
+	long val;
+
+	BIO_printf(out, "%*sLogotypeImageInfo\n", indent, "");
+	if (info->type) {
+		val = ASN1_INTEGER_get(info->type);
+		BIO_printf(out, "%*stype: %ld\n", indent, "", val);
+	} else {
+		BIO_printf(out, "%*stype: default (1)\n", indent, "");
+	}
+	val = ASN1_INTEGER_get(info->xSize);
+	BIO_printf(out, "%*sxSize: %ld\n", indent, "", val);
+	val = ASN1_INTEGER_get(info->ySize);
+	BIO_printf(out, "%*sySize: %ld\n", indent, "", val);
+	if (info->resolution) {
+		BIO_printf(out, "%*sresolution\n", indent, "");
+		/* TODO */
+	}
+	if (info->language) {
+		BIO_printf(out, "%*slanguage: ", indent, "");
+		ASN1_STRING_print(out, info->language);
+		BIO_printf(out, "\n");
+	}
+}
+
+static void i2r_LogotypeImage(LogotypeImage *image, BIO *out, int indent)
+{
+	BIO_printf(out, "%*sLogotypeImage\n", indent, "");
+	if (image->imageDetails) {
+		i2r_LogotypeDetails(image->imageDetails, out, indent + 4);
+	}
+	if (image->imageInfo) {
+		i2r_LogotypeImageInfo(image->imageInfo, out, indent + 4);
+	}
+}
+
+static void i2r_LogotypeData(LogotypeData *data, const char *title, BIO *out,
+			     int indent)
+{
+	int i, num;
+
+	BIO_printf(out, "%*s%s - LogotypeData\n", indent, "", title);
+
+	num = data->image ? sk_LogotypeImage_num(data->image) : 0;
+	for (i = 0; i < num; i++) {
+		LogotypeImage *image = sk_LogotypeImage_value(data->image, i);
+		i2r_LogotypeImage(image, out, indent + 4);
+	}
+
+	num = data->audio ? sk_LogotypeAudio_num(data->audio) : 0;
+	for (i = 0; i < num; i++) {
+		BIO_printf(out, "%*saudio: TODO\n", indent, "");
+	}
+}
+
+static void i2r_LogotypeReference(LogotypeReference *ref, const char *title,
+				  BIO *out, int indent)
+{
+	int i, hash_num, uri_num;
+
+	BIO_printf(out, "%*s%s - LogotypeReference\n", indent, "", title);
+
+	hash_num = ref->refStructHash ?
+		sk_HashAlgAndValue_num(ref->refStructHash) : 0;
+	uri_num = ref->refStructURI ?
+		sk_ASN1_IA5STRING_num(ref->refStructURI) : 0;
+	if (hash_num != uri_num) {
+		BIO_printf(out, "%*sUnexpected LogotypeReference array size difference %d != %d\n",
+			   indent, "", hash_num, uri_num);
+		return;
+	}
+
+	for (i = 0; i < hash_num; i++) {
+		HashAlgAndValue *hash;
+		ASN1_IA5STRING *uri;
+
+		hash = sk_HashAlgAndValue_value(ref->refStructHash, i);
+		i2r_HashAlgAndValue(hash, out, indent);
+
+		uri = sk_ASN1_IA5STRING_value(ref->refStructURI, i);
+		BIO_printf(out, "%*srefStructURI: ", indent, "");
+		ASN1_STRING_print(out, uri);
+		BIO_printf(out, "\n");
+	}
+}
+
+static void i2r_LogotypeInfo(LogotypeInfo *info, const char *title, BIO *out,
+			     int indent)
+{
+	switch (info->type) {
+	case 0:
+		i2r_LogotypeData(info->d.direct, title, out, indent);
+		break;
+	case 1:
+		i2r_LogotypeReference(info->d.indirect, title, out, indent);
+		break;
+	}
+}
+
+static void debug_print_logotypeext(LogotypeExtn *logo)
+{
+	BIO *out;
+	int i, num;
+	int indent = 0;
+
+	out = BIO_new_fp(stdout, BIO_NOCLOSE);
+	if (out == NULL)
+		return;
+
+	if (logo->communityLogos) {
+		num = sk_LogotypeInfo_num(logo->communityLogos);
+		for (i = 0; i < num; i++) {
+			LogotypeInfo *info;
+			info = sk_LogotypeInfo_value(logo->communityLogos, i);
+			i2r_LogotypeInfo(info, "communityLogo", out, indent);
+		}
+	}
+
+	if (logo->issuerLogo) {
+		i2r_LogotypeInfo(logo->issuerLogo, "issuerLogo", out, indent );
+	}
+
+	if (logo->subjectLogo) {
+		i2r_LogotypeInfo(logo->subjectLogo, "subjectLogo", out, indent);
+	}
+
+	if (logo->otherLogos) {
+		BIO_printf(out, "%*sotherLogos - TODO\n", indent, "");
+	}
+
+	BIO_free(out);
+}
+
+
+static void add_logotype_ext(struct http_ctx *ctx, struct http_cert *hcert,
+			     X509 *cert)
+{
+	ASN1_OBJECT *obj;
+	int pos;
+	X509_EXTENSION *ext;
+	ASN1_OCTET_STRING *os;
+	LogotypeExtn *logo;
+	const unsigned char *data;
+	int i, num;
+
+	obj = OBJ_txt2obj("1.3.6.1.5.5.7.1.12", 0);
+	if (obj == NULL)
+		return;
+
+	pos = X509_get_ext_by_OBJ(cert, obj, -1);
+	if (pos < 0) {
+		wpa_printf(MSG_INFO, "No logotype extension included");
+		return;
+	}
+
+	wpa_printf(MSG_INFO, "Parsing logotype extension");
+	ext = X509_get_ext(cert, pos);
+	if (!ext) {
+		wpa_printf(MSG_INFO, "Could not get logotype extension");
+		return;
+	}
+
+	os = X509_EXTENSION_get_data(ext);
+	if (os == NULL) {
+		wpa_printf(MSG_INFO, "Could not get logotype extension data");
+		return;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "logotypeExtn",
+		    ASN1_STRING_data(os), ASN1_STRING_length(os));
+
+	data = ASN1_STRING_data(os);
+	logo = d2i_LogotypeExtn(NULL, &data, ASN1_STRING_length(os));
+	if (logo == NULL) {
+		wpa_printf(MSG_INFO, "Failed to parse logotypeExtn");
+		return;
+	}
+
+	if (wpa_debug_level < MSG_INFO)
+		debug_print_logotypeext(logo);
+
+	if (!logo->communityLogos) {
+		wpa_printf(MSG_INFO, "No communityLogos included");
+		LogotypeExtn_free(logo);
+		return;
+	}
+
+	num = sk_LogotypeInfo_num(logo->communityLogos);
+	for (i = 0; i < num; i++) {
+		LogotypeInfo *info;
+		info = sk_LogotypeInfo_value(logo->communityLogos, i);
+		switch (info->type) {
+		case 0:
+			add_logo_direct(ctx, hcert, info->d.direct);
+			break;
+		case 1:
+			add_logo_indirect(ctx, hcert, info->d.indirect);
+			break;
+		}
+	}
+
+	LogotypeExtn_free(logo);
+}
+
+
+static void parse_cert(struct http_ctx *ctx, struct http_cert *hcert,
+		       X509 *cert, GENERAL_NAMES **names)
+{
+	os_memset(hcert, 0, sizeof(*hcert));
+
+	*names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
+	if (*names)
+		add_alt_names(ctx, hcert, *names);
+
+	add_logotype_ext(ctx, hcert, cert);
+}
+
+
+static void parse_cert_free(struct http_cert *hcert, GENERAL_NAMES *names)
+{
+	unsigned int i;
+
+	for (i = 0; i < hcert->num_dnsname; i++)
+		OPENSSL_free(hcert->dnsname[i]);
+	os_free(hcert->dnsname);
+
+	for (i = 0; i < hcert->num_othername; i++)
+		os_free(hcert->othername[i].oid);
+	os_free(hcert->othername);
+
+	for (i = 0; i < hcert->num_logo; i++) {
+		os_free(hcert->logo[i].alg_oid);
+		os_free(hcert->logo[i].hash);
+		os_free(hcert->logo[i].uri);
+	}
+	os_free(hcert->logo);
+
+	sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free);
+}
+
+
+static int validate_server_cert(struct http_ctx *ctx, X509 *cert)
+{
+	GENERAL_NAMES *names;
+	struct http_cert hcert;
+	int ret;
+
+	if (ctx->cert_cb == NULL)
+		return 0;
+
+	if (0) {
+		BIO *out;
+		out = BIO_new_fp(stdout, BIO_NOCLOSE);
+		X509_print_ex(out, cert, XN_FLAG_COMPAT, X509_FLAG_COMPAT);
+		BIO_free(out);
+	}
+
+	parse_cert(ctx, &hcert, cert, &names);
+	ret = ctx->cert_cb(ctx->cert_cb_ctx, &hcert);
+	parse_cert_free(&hcert, names);
+
+	return ret;
+}
+
+
+void http_parse_x509_certificate(struct http_ctx *ctx, const char *fname)
+{
+	BIO *in, *out;
+	X509 *cert;
+	GENERAL_NAMES *names;
+	struct http_cert hcert;
+	unsigned int i;
+
+	in = BIO_new_file(fname, "r");
+	if (in == NULL) {
+		wpa_printf(MSG_ERROR, "Could not read '%s'", fname);
+		return;
+	}
+
+	cert = d2i_X509_bio(in, NULL);
+	BIO_free(in);
+
+	if (cert == NULL) {
+		wpa_printf(MSG_ERROR, "Could not parse certificate");
+		return;
+	}
+
+	out = BIO_new_fp(stdout, BIO_NOCLOSE);
+	if (out) {
+		X509_print_ex(out, cert, XN_FLAG_COMPAT,
+			      X509_FLAG_COMPAT);
+		BIO_free(out);
+	}
+
+	wpa_printf(MSG_INFO, "Additional parsing information:");
+	parse_cert(ctx, &hcert, cert, &names);
+	for (i = 0; i < hcert.num_othername; i++) {
+		if (os_strcmp(hcert.othername[i].oid,
+			      "1.3.6.1.4.1.40808.1.1.1") == 0) {
+			char *name = os_zalloc(hcert.othername[i].len + 1);
+			if (name) {
+				os_memcpy(name, hcert.othername[i].data,
+					  hcert.othername[i].len);
+				wpa_printf(MSG_INFO,
+					   "id-wfa-hotspot-friendlyName: %s",
+					   name);
+				os_free(name);
+			}
+			wpa_hexdump_ascii(MSG_INFO,
+					  "id-wfa-hotspot-friendlyName",
+					  hcert.othername[i].data,
+					  hcert.othername[i].len);
+		} else {
+			wpa_printf(MSG_INFO, "subjAltName[othername]: oid=%s",
+				   hcert.othername[i].oid);
+			wpa_hexdump_ascii(MSG_INFO, "unknown othername",
+					  hcert.othername[i].data,
+					  hcert.othername[i].len);
+		}
+	}
+	parse_cert_free(&hcert, names);
+
+	X509_free(cert);
+}
+
+
+static int curl_cb_ssl_verify(int preverify_ok, X509_STORE_CTX *x509_ctx)
+{
+	struct http_ctx *ctx;
+	X509 *cert;
+	int err, depth;
+	char buf[256];
+	X509_NAME *name;
+	const char *err_str;
+	SSL *ssl;
+	SSL_CTX *ssl_ctx;
+
+	ssl = X509_STORE_CTX_get_ex_data(x509_ctx,
+					 SSL_get_ex_data_X509_STORE_CTX_idx());
+	ssl_ctx = ssl->ctx;
+	ctx = SSL_CTX_get_app_data(ssl_ctx);
+
+	wpa_printf(MSG_DEBUG, "curl_cb_ssl_verify");
+
+	err = X509_STORE_CTX_get_error(x509_ctx);
+	err_str = X509_verify_cert_error_string(err);
+	depth = X509_STORE_CTX_get_error_depth(x509_ctx);
+	cert = X509_STORE_CTX_get_current_cert(x509_ctx);
+	if (!cert) {
+		wpa_printf(MSG_INFO, "No server certificate available");
+		ctx->last_err = "No server certificate available";
+		return 0;
+	}
+
+	if (depth == 0)
+		ctx->peer_cert = cert;
+	else if (depth == 1)
+		ctx->peer_issuer = cert;
+	else if (depth == 2)
+		ctx->peer_issuer_issuer = cert;
+
+	name = X509_get_subject_name(cert);
+	X509_NAME_oneline(name, buf, sizeof(buf));
+	wpa_printf(MSG_INFO, "Server certificate chain - depth=%d err=%d (%s) subject=%s",
+		   depth, err, err_str, buf);
+	debug_dump_cert("Server certificate chain - certificate", cert);
+
+	if (depth == 0 && preverify_ok && validate_server_cert(ctx, cert) < 0)
+		return 0;
+
+	if (!preverify_ok)
+		ctx->last_err = "TLS validation failed";
+
+	return preverify_ok;
+}
+
+
+#ifdef HAVE_OCSP
+
+static void ocsp_debug_print_resp(OCSP_RESPONSE *rsp)
+{
+	BIO *out;
+	size_t rlen;
+	char *txt;
+	int res;
+
+	out = BIO_new(BIO_s_mem());
+	if (!out)
+		return;
+
+	OCSP_RESPONSE_print(out, rsp, 0);
+	rlen = BIO_ctrl_pending(out);
+	txt = os_malloc(rlen + 1);
+	if (!txt) {
+		BIO_free(out);
+		return;
+	}
+
+	res = BIO_read(out, txt, rlen);
+	if (res > 0) {
+		txt[res] = '\0';
+		wpa_printf(MSG_MSGDUMP, "OpenSSL: OCSP Response\n%s", txt);
+	}
+	os_free(txt);
+	BIO_free(out);
+}
+
+
+static void tls_show_errors(const char *func, const char *txt)
+{
+	unsigned long err;
+
+	wpa_printf(MSG_DEBUG, "OpenSSL: %s - %s %s",
+		   func, txt, ERR_error_string(ERR_get_error(), NULL));
+
+	while ((err = ERR_get_error())) {
+		wpa_printf(MSG_DEBUG, "OpenSSL: pending error: %s",
+			   ERR_error_string(err, NULL));
+	}
+}
+
+
+static int ocsp_resp_cb(SSL *s, void *arg)
+{
+	struct http_ctx *ctx = arg;
+	const unsigned char *p;
+	int len, status, reason;
+	OCSP_RESPONSE *rsp;
+	OCSP_BASICRESP *basic;
+	OCSP_CERTID *id;
+	ASN1_GENERALIZEDTIME *produced_at, *this_update, *next_update;
+	X509_STORE *store;
+	STACK_OF(X509) *certs = NULL;
+
+	len = SSL_get_tlsext_status_ocsp_resp(s, &p);
+	if (!p) {
+		wpa_printf(MSG_DEBUG, "OpenSSL: No OCSP response received");
+		if (ctx->ocsp == MANDATORY_OCSP)
+			ctx->last_err = "No OCSP response received";
+		return (ctx->ocsp == MANDATORY_OCSP) ? 0 : 1;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "OpenSSL: OCSP response", p, len);
+
+	rsp = d2i_OCSP_RESPONSE(NULL, &p, len);
+	if (!rsp) {
+		wpa_printf(MSG_INFO, "OpenSSL: Failed to parse OCSP response");
+		ctx->last_err = "Failed to parse OCSP response";
+		return 0;
+	}
+
+	ocsp_debug_print_resp(rsp);
+
+	status = OCSP_response_status(rsp);
+	if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
+		wpa_printf(MSG_INFO, "OpenSSL: OCSP responder error %d (%s)",
+			   status, OCSP_response_status_str(status));
+		ctx->last_err = "OCSP responder error";
+		return 0;
+	}
+
+	basic = OCSP_response_get1_basic(rsp);
+	if (!basic) {
+		wpa_printf(MSG_INFO, "OpenSSL: Could not find BasicOCSPResponse");
+		ctx->last_err = "Could not find BasicOCSPResponse";
+		return 0;
+	}
+
+	store = SSL_CTX_get_cert_store(s->ctx);
+	if (ctx->peer_issuer) {
+		wpa_printf(MSG_DEBUG, "OpenSSL: Add issuer");
+		debug_dump_cert("OpenSSL: Issuer certificate",
+				ctx->peer_issuer);
+
+		if (X509_STORE_add_cert(store, ctx->peer_issuer) != 1) {
+			tls_show_errors(__func__,
+					"OpenSSL: Could not add issuer to certificate store\n");
+		}
+		certs = sk_X509_new_null();
+		if (certs) {
+			X509 *cert;
+			cert = X509_dup(ctx->peer_issuer);
+			if (cert && !sk_X509_push(certs, cert)) {
+				tls_show_errors(
+					__func__,
+					"OpenSSL: Could not add issuer to OCSP responder trust store\n");
+				X509_free(cert);
+				sk_X509_free(certs);
+				certs = NULL;
+			}
+			if (ctx->peer_issuer_issuer) {
+				X509 *cert;
+				cert = X509_dup(ctx->peer_issuer_issuer);
+				if (cert && !sk_X509_push(certs, cert)) {
+					tls_show_errors(
+						__func__,
+						"OpenSSL: Could not add issuer to OCSP responder trust store\n");
+					X509_free(cert);
+				}
+			}
+		}
+	}
+
+	status = OCSP_basic_verify(basic, certs, store, OCSP_TRUSTOTHER);
+	sk_X509_pop_free(certs, X509_free);
+	if (status <= 0) {
+		tls_show_errors(__func__,
+				"OpenSSL: OCSP response failed verification");
+		OCSP_BASICRESP_free(basic);
+		OCSP_RESPONSE_free(rsp);
+		ctx->last_err = "OCSP response failed verification";
+		return 0;
+	}
+
+	wpa_printf(MSG_DEBUG, "OpenSSL: OCSP response verification succeeded");
+
+	if (!ctx->peer_cert) {
+		wpa_printf(MSG_DEBUG, "OpenSSL: Peer certificate not available for OCSP status check");
+		OCSP_BASICRESP_free(basic);
+		OCSP_RESPONSE_free(rsp);
+		ctx->last_err = "Peer certificate not available for OCSP status check";
+		return 0;
+	}
+
+	if (!ctx->peer_issuer) {
+		wpa_printf(MSG_DEBUG, "OpenSSL: Peer issuer certificate not available for OCSP status check");
+		OCSP_BASICRESP_free(basic);
+		OCSP_RESPONSE_free(rsp);
+		ctx->last_err = "Peer issuer certificate not available for OCSP status check";
+		return 0;
+	}
+
+	id = OCSP_cert_to_id(NULL, ctx->peer_cert, ctx->peer_issuer);
+	if (!id) {
+		wpa_printf(MSG_DEBUG, "OpenSSL: Could not create OCSP certificate identifier");
+		OCSP_BASICRESP_free(basic);
+		OCSP_RESPONSE_free(rsp);
+		ctx->last_err = "Could not create OCSP certificate identifier";
+		return 0;
+	}
+
+	if (!OCSP_resp_find_status(basic, id, &status, &reason, &produced_at,
+				   &this_update, &next_update)) {
+		wpa_printf(MSG_INFO, "OpenSSL: Could not find current server certificate from OCSP response%s",
+			   (ctx->ocsp == MANDATORY_OCSP) ? "" :
+			   " (OCSP not required)");
+		OCSP_BASICRESP_free(basic);
+		OCSP_RESPONSE_free(rsp);
+		if (ctx->ocsp == MANDATORY_OCSP)
+
+			ctx->last_err = "Could not find current server certificate from OCSP response";
+		return (ctx->ocsp == MANDATORY_OCSP) ? 0 : 1;
+	}
+
+	if (!OCSP_check_validity(this_update, next_update, 5 * 60, -1)) {
+		tls_show_errors(__func__, "OpenSSL: OCSP status times invalid");
+		OCSP_BASICRESP_free(basic);
+		OCSP_RESPONSE_free(rsp);
+		ctx->last_err = "OCSP status times invalid";
+		return 0;
+	}
+
+	OCSP_BASICRESP_free(basic);
+	OCSP_RESPONSE_free(rsp);
+
+	wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status for server certificate: %s",
+		   OCSP_cert_status_str(status));
+
+	if (status == V_OCSP_CERTSTATUS_GOOD)
+		return 1;
+	if (status == V_OCSP_CERTSTATUS_REVOKED)
+		ctx->last_err = "Server certificate has been revoked";
+		return 0;
+	if (ctx->ocsp == MANDATORY_OCSP) {
+		wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status unknown, but OCSP required");
+		ctx->last_err = "OCSP status unknown";
+		return 0;
+	}
+	wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status unknown, but OCSP was not required, so allow connection to continue");
+	return 1;
+}
+
+
+static SSL_METHOD patch_ssl_method;
+static const SSL_METHOD *real_ssl_method;
+
+static int curl_patch_ssl_new(SSL *s)
+{
+	SSL_CTX *ssl = s->ctx;
+	int ret;
+
+	ssl->method = real_ssl_method;
+	s->method = real_ssl_method;
+
+	ret = s->method->ssl_new(s);
+	SSL_set_tlsext_status_type(s, TLSEXT_STATUSTYPE_ocsp);
+
+	return ret;
+}
+
+#endif /* HAVE_OCSP */
+
+
+static CURLcode curl_cb_ssl(CURL *curl, void *sslctx, void *parm)
+{
+	struct http_ctx *ctx = parm;
+	SSL_CTX *ssl = sslctx;
+
+	wpa_printf(MSG_DEBUG, "curl_cb_ssl");
+	SSL_CTX_set_app_data(ssl, ctx);
+	SSL_CTX_set_verify(ssl, SSL_VERIFY_PEER, curl_cb_ssl_verify);
+
+#ifdef HAVE_OCSP
+	if (ctx->ocsp != NO_OCSP) {
+		SSL_CTX_set_tlsext_status_cb(ssl, ocsp_resp_cb);
+		SSL_CTX_set_tlsext_status_arg(ssl, ctx);
+
+		/*
+		 * Use a temporary SSL_METHOD to get a callback on SSL_new()
+		 * from libcurl since there is no proper callback registration
+		 * available for this.
+		 */
+		os_memset(&patch_ssl_method, 0, sizeof(patch_ssl_method));
+		patch_ssl_method.ssl_new = curl_patch_ssl_new;
+		real_ssl_method = ssl->method;
+		ssl->method = &patch_ssl_method;
+	}
+#endif /* HAVE_OCSP */
+
+	return CURLE_OK;
+}
+
+#endif /* EAP_TLS_OPENSSL */
+
+
+static CURL * setup_curl_post(struct http_ctx *ctx, const char *address,
+			      const char *ca_fname, const char *username,
+			      const char *password, const char *client_cert,
+			      const char *client_key)
+{
+	CURL *curl;
+
+	wpa_printf(MSG_DEBUG, "Start HTTP client: address=%s ca_fname=%s "
+		   "username=%s", address, ca_fname, username);
+
+	curl = curl_easy_init();
+	if (curl == NULL)
+		return NULL;
+
+	curl_easy_setopt(curl, CURLOPT_URL, address);
+	curl_easy_setopt(curl, CURLOPT_POST, 1L);
+	if (ca_fname) {
+		curl_easy_setopt(curl, CURLOPT_CAINFO, ca_fname);
+		curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
+#ifdef EAP_TLS_OPENSSL
+		curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, curl_cb_ssl);
+		curl_easy_setopt(curl, CURLOPT_SSL_CTX_DATA, ctx);
+#endif /* EAP_TLS_OPENSSL */
+	} else {
+		curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
+	}
+	if (client_cert && client_key) {
+		curl_easy_setopt(curl, CURLOPT_SSLCERT, client_cert);
+		curl_easy_setopt(curl, CURLOPT_SSLKEY, client_key);
+	}
+	/* TODO: use curl_easy_getinfo() with CURLINFO_CERTINFO to fetch
+	 * information about the server certificate */
+	curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L);
+	curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_cb_debug);
+	curl_easy_setopt(curl, CURLOPT_DEBUGDATA, ctx);
+	curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_cb_header);
+	curl_easy_setopt(curl, CURLOPT_HEADERDATA, ctx);
+	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_cb_write);
+	curl_easy_setopt(curl, CURLOPT_WRITEDATA, ctx);
+	curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+	if (username) {
+		curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANYSAFE);
+		curl_easy_setopt(curl, CURLOPT_USERNAME, username);
+		curl_easy_setopt(curl, CURLOPT_PASSWORD, password);
+	}
+
+	return curl;
+}
+
+
+static int post_init_client(struct http_ctx *ctx, const char *address,
+			    const char *ca_fname, const char *username,
+			    const char *password, const char *client_cert,
+			    const char *client_key)
+{
+	char *pos;
+	int count;
+
+	clone_str(&ctx->svc_address, address);
+	clone_str(&ctx->svc_ca_fname, ca_fname);
+	clone_str(&ctx->svc_username, username);
+	clone_str(&ctx->svc_password, password);
+	clone_str(&ctx->svc_client_cert, client_cert);
+	clone_str(&ctx->svc_client_key, client_key);
+
+	/*
+	 * Workaround for Apache "Hostname 'FOO' provided via SNI and hostname
+	 * 'foo' provided via HTTP are different.
+	 */
+	for (count = 0, pos = ctx->svc_address; count < 3 && pos && *pos;
+	     pos++) {
+		if (*pos == '/')
+			count++;
+		*pos = tolower(*pos);
+	}
+
+	ctx->curl = setup_curl_post(ctx, ctx->svc_address, ca_fname, username,
+				    password, client_cert, client_key);
+	if (ctx->curl == NULL)
+		return -1;
+
+	return 0;
+}
+
+
+int soap_init_client(struct http_ctx *ctx, const char *address,
+		     const char *ca_fname, const char *username,
+		     const char *password, const char *client_cert,
+		     const char *client_key)
+{
+	if (post_init_client(ctx, address, ca_fname, username, password,
+			     client_cert, client_key) < 0)
+		return -1;
+
+	ctx->curl_hdr = curl_slist_append(ctx->curl_hdr,
+					  "Content-Type: application/soap+xml");
+	ctx->curl_hdr = curl_slist_append(ctx->curl_hdr, "SOAPAction: ");
+	ctx->curl_hdr = curl_slist_append(ctx->curl_hdr, "Expect:");
+	curl_easy_setopt(ctx->curl, CURLOPT_HTTPHEADER, ctx->curl_hdr);
+
+	return 0;
+}
+
+
+int soap_reinit_client(struct http_ctx *ctx)
+{
+	char *address = NULL;
+	char *ca_fname = NULL;
+	char *username = NULL;
+	char *password = NULL;
+	char *client_cert = NULL;
+	char *client_key = NULL;
+	int ret;
+
+	clear_curl(ctx);
+
+	clone_str(&address, ctx->svc_address);
+	clone_str(&ca_fname, ctx->svc_ca_fname);
+	clone_str(&username, ctx->svc_username);
+	clone_str(&password, ctx->svc_password);
+	clone_str(&client_cert, ctx->svc_client_cert);
+	clone_str(&client_key, ctx->svc_client_key);
+
+	ret = soap_init_client(ctx, address, ca_fname, username, password,
+			       client_cert, client_key);
+	os_free(address);
+	os_free(ca_fname);
+	os_free(username);
+	os_free(password);
+	os_free(client_cert);
+	os_free(client_key);
+	return ret;
+}
+
+
+static void free_curl_buf(struct http_ctx *ctx)
+{
+	os_free(ctx->curl_buf);
+	ctx->curl_buf = NULL;
+	ctx->curl_buf_len = 0;
+}
+
+
+xml_node_t * soap_send_receive(struct http_ctx *ctx, xml_node_t *node)
+{
+	char *str;
+	xml_node_t *envelope, *ret, *resp, *n;
+	CURLcode res;
+	long http = 0;
+
+	ctx->last_err = NULL;
+
+	wpa_printf(MSG_DEBUG, "SOAP: Sending message");
+	envelope = soap_build_envelope(ctx->xml, node);
+	str = xml_node_to_str(ctx->xml, envelope);
+	xml_node_free(ctx->xml, envelope);
+	wpa_printf(MSG_MSGDUMP, "SOAP[%s]", str);
+
+	curl_easy_setopt(ctx->curl, CURLOPT_POSTFIELDS, str);
+	free_curl_buf(ctx);
+
+	res = curl_easy_perform(ctx->curl);
+	if (res != CURLE_OK) {
+		if (!ctx->last_err)
+			ctx->last_err = curl_easy_strerror(res);
+		wpa_printf(MSG_ERROR, "curl_easy_perform() failed: %s",
+			   ctx->last_err);
+		os_free(str);
+		free_curl_buf(ctx);
+		return NULL;
+	}
+	os_free(str);
+
+	curl_easy_getinfo(ctx->curl, CURLINFO_RESPONSE_CODE, &http);
+	wpa_printf(MSG_DEBUG, "SOAP: Server response code %ld", http);
+	if (http != 200) {
+		ctx->last_err = "HTTP download failed";
+		wpa_printf(MSG_INFO, "HTTP download failed - code %ld", http);
+		free_curl_buf(ctx);
+		return NULL;
+	}
+
+	if (ctx->curl_buf == NULL)
+		return NULL;
+
+	wpa_printf(MSG_MSGDUMP, "Server response:\n%s", ctx->curl_buf);
+	resp = xml_node_from_buf(ctx->xml, ctx->curl_buf);
+	free_curl_buf(ctx);
+	if (resp == NULL) {
+		wpa_printf(MSG_INFO, "Could not parse SOAP response");
+		ctx->last_err = "Could not parse SOAP response";
+		return NULL;
+	}
+
+	ret = soap_get_body(ctx->xml, resp);
+	if (ret == NULL) {
+		wpa_printf(MSG_INFO, "Could not get SOAP body");
+		ctx->last_err = "Could not get SOAP body";
+		return NULL;
+	}
+
+	wpa_printf(MSG_DEBUG, "SOAP body localname: '%s'",
+		   xml_node_get_localname(ctx->xml, ret));
+	n = xml_node_copy(ctx->xml, ret);
+	xml_node_free(ctx->xml, resp);
+
+	return n;
+}
+
+
+struct http_ctx * http_init_ctx(void *upper_ctx, struct xml_node_ctx *xml_ctx)
+{
+	struct http_ctx *ctx;
+
+	ctx = os_zalloc(sizeof(*ctx));
+	if (ctx == NULL)
+		return NULL;
+	ctx->ctx = upper_ctx;
+	ctx->xml = xml_ctx;
+	ctx->ocsp = OPTIONAL_OCSP;
+
+	curl_global_init(CURL_GLOBAL_ALL);
+
+	return ctx;
+}
+
+
+void http_ocsp_set(struct http_ctx *ctx, int val)
+{
+	if (val == 0)
+		ctx->ocsp = NO_OCSP;
+	else if (val == 1)
+		ctx->ocsp = OPTIONAL_OCSP;
+	if (val == 2)
+		ctx->ocsp = MANDATORY_OCSP;
+}
+
+
+void http_deinit_ctx(struct http_ctx *ctx)
+{
+	clear_curl(ctx);
+	os_free(ctx->curl_buf);
+	curl_global_cleanup();
+
+	os_free(ctx->svc_address);
+	os_free(ctx->svc_ca_fname);
+	os_free(ctx->svc_username);
+	os_free(ctx->svc_password);
+	os_free(ctx->svc_client_cert);
+	os_free(ctx->svc_client_key);
+
+	os_free(ctx);
+}
+
+
+int http_download_file(struct http_ctx *ctx, const char *url,
+		       const char *fname, const char *ca_fname)
+{
+	CURL *curl;
+	FILE *f;
+	CURLcode res;
+	long http = 0;
+
+	ctx->last_err = NULL;
+
+	wpa_printf(MSG_DEBUG, "curl: Download file from %s to %s (ca=%s)",
+		   url, fname, ca_fname);
+	curl = curl_easy_init();
+	if (curl == NULL)
+		return -1;
+
+	f = fopen(fname, "wb");
+	if (f == NULL) {
+		curl_easy_cleanup(curl);
+		return -1;
+	}
+
+	curl_easy_setopt(curl, CURLOPT_URL, url);
+	if (ca_fname) {
+		curl_easy_setopt(curl, CURLOPT_CAINFO, ca_fname);
+		curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
+		curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L);
+	} else {
+		curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
+	}
+	curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_cb_debug);
+	curl_easy_setopt(curl, CURLOPT_DEBUGDATA, ctx);
+	curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_cb_header);
+	curl_easy_setopt(curl, CURLOPT_HEADERDATA, ctx);
+	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
+	curl_easy_setopt(curl, CURLOPT_WRITEDATA, f);
+	curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+
+	res = curl_easy_perform(curl);
+	if (res != CURLE_OK) {
+		if (!ctx->last_err)
+			ctx->last_err = curl_easy_strerror(res);
+		wpa_printf(MSG_ERROR, "curl_easy_perform() failed: %s",
+			   ctx->last_err);
+		curl_easy_cleanup(curl);
+		fclose(f);
+		return -1;
+	}
+
+	curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http);
+	wpa_printf(MSG_DEBUG, "curl: Server response code %ld", http);
+	if (http != 200) {
+		ctx->last_err = "HTTP download failed";
+		wpa_printf(MSG_INFO, "HTTP download failed - code %ld", http);
+		curl_easy_cleanup(curl);
+		fclose(f);
+		return -1;
+	}
+
+	curl_easy_cleanup(curl);
+	fclose(f);
+
+	return 0;
+}
+
+
+char * http_post(struct http_ctx *ctx, const char *url, const char *data,
+		 const char *content_type, const char *ext_hdr,
+		 const char *ca_fname,
+		 const char *username, const char *password,
+		 const char *client_cert, const char *client_key,
+		 size_t *resp_len)
+{
+	long http = 0;
+	CURLcode res;
+	char *ret;
+	CURL *curl;
+	struct curl_slist *curl_hdr = NULL;
+
+	ctx->last_err = NULL;
+	wpa_printf(MSG_DEBUG, "curl: HTTP POST to %s", url);
+	curl = setup_curl_post(ctx, url, ca_fname, username, password,
+			       client_cert, client_key);
+	if (curl == NULL)
+		return NULL;
+
+	if (content_type) {
+		char ct[200];
+		snprintf(ct, sizeof(ct), "Content-Type: %s", content_type);
+		curl_hdr = curl_slist_append(curl_hdr, ct);
+	}
+	if (ext_hdr)
+		curl_hdr = curl_slist_append(curl_hdr, ext_hdr);
+	curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_hdr);
+
+	curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
+	free_curl_buf(ctx);
+
+	res = curl_easy_perform(curl);
+	if (res != CURLE_OK) {
+		if (!ctx->last_err)
+			ctx->last_err = curl_easy_strerror(res);
+		wpa_printf(MSG_ERROR, "curl_easy_perform() failed: %s",
+			   ctx->last_err);
+		free_curl_buf(ctx);
+		return NULL;
+	}
+
+	curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http);
+	wpa_printf(MSG_DEBUG, "curl: Server response code %ld", http);
+	if (http != 200) {
+		ctx->last_err = "HTTP POST failed";
+		wpa_printf(MSG_INFO, "HTTP POST failed - code %ld", http);
+		free_curl_buf(ctx);
+		return NULL;
+	}
+
+	if (ctx->curl_buf == NULL)
+		return NULL;
+
+	ret = ctx->curl_buf;
+	if (resp_len)
+		*resp_len = ctx->curl_buf_len;
+	ctx->curl_buf = NULL;
+	ctx->curl_buf_len = 0;
+
+	wpa_printf(MSG_MSGDUMP, "Server response:\n%s", ret);
+
+	return ret;
+}
+
+
+void http_set_cert_cb(struct http_ctx *ctx,
+		      int (*cb)(void *ctx, struct http_cert *cert),
+		      void *cb_ctx)
+{
+	ctx->cert_cb = cb;
+	ctx->cert_cb_ctx = cb_ctx;
+}
+
+
+const char * http_get_err(struct http_ctx *ctx)
+{
+	return ctx->last_err;
+}
diff --git a/src/utils/os.h b/src/utils/os.h
index d63ac29..f019e26 100644
--- a/src/utils/os.h
+++ b/src/utils/os.h
@@ -240,6 +240,13 @@
 char * os_readfile(const char *name, size_t *len);
 
 /**
+ * os_file_exists - Check whether the specified file exists
+ * @fname: Path and name of the file
+ * Returns: 1 if the file exists or 0 if not
+ */
+int os_file_exists(const char *fname);
+
+/**
  * os_zalloc - Allocate and zero memory
  * @size: Number of bytes to allocate
  * Returns: Pointer to allocated and zeroed memory or %NULL on failure
diff --git a/src/utils/os_unix.c b/src/utils/os_unix.c
index fa67fdf..008ec6b 100644
--- a/src/utils/os_unix.c
+++ b/src/utils/os_unix.c
@@ -407,6 +407,16 @@
 }
 
 
+int os_file_exists(const char *fname)
+{
+	FILE *f = fopen(fname, "rb");
+	if (f == NULL)
+		return 0;
+	fclose(f);
+	return 1;
+}
+
+
 #ifndef WPA_TRACE
 void * os_zalloc(size_t size)
 {
diff --git a/src/utils/xml-utils.c b/src/utils/xml-utils.c
new file mode 100644
index 0000000..4916d29
--- /dev/null
+++ b/src/utils/xml-utils.c
@@ -0,0 +1,471 @@
+/*
+ * Generic XML helper functions
+ * Copyright (c) 2012-2013, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "xml-utils.h"
+
+
+static xml_node_t * get_node_uri_iter(struct xml_node_ctx *ctx,
+				      xml_node_t *root, char *uri)
+{
+	char *end;
+	xml_node_t *node;
+	const char *name;
+
+	end = strchr(uri, '/');
+	if (end)
+		*end++ = '\0';
+
+	node = root;
+	xml_node_for_each_sibling(ctx, node) {
+		xml_node_for_each_check(ctx, node);
+		name = xml_node_get_localname(ctx, node);
+		if (strcasecmp(name, uri) == 0)
+			break;
+	}
+
+	if (node == NULL)
+		return NULL;
+
+	if (end) {
+		return get_node_uri_iter(ctx, xml_node_first_child(ctx, node),
+					 end);
+	}
+
+	return node;
+}
+
+
+xml_node_t * get_node_uri(struct xml_node_ctx *ctx, xml_node_t *root,
+			  const char *uri)
+{
+	char *search;
+	xml_node_t *node;
+
+	search = os_strdup(uri);
+	if (search == NULL)
+		return NULL;
+
+	node = get_node_uri_iter(ctx, root, search);
+
+	os_free(search);
+	return node;
+}
+
+
+static xml_node_t * get_node_iter(struct xml_node_ctx *ctx,
+				  xml_node_t *root, const char *path)
+{
+	char *end;
+	xml_node_t *node;
+	const char *name;
+
+	end = os_strchr(path, '/');
+	if (end)
+		*end++ = '\0';
+
+	xml_node_for_each_child(ctx, node, root) {
+		xml_node_for_each_check(ctx, node);
+		name = xml_node_get_localname(ctx, node);
+		if (os_strcasecmp(name, path) == 0)
+			break;
+	}
+
+	if (node == NULL)
+		return NULL;
+	if (end)
+		return get_node_iter(ctx, node, end);
+	return node;
+}
+
+
+xml_node_t * get_node(struct xml_node_ctx *ctx, xml_node_t *root,
+		      const char *path)
+{
+	char *search;
+	xml_node_t *node;
+
+	search = os_strdup(path);
+	if (search == NULL)
+		return NULL;
+
+	node = get_node_iter(ctx, root, search);
+
+	os_free(search);
+	return node;
+}
+
+
+xml_node_t * get_child_node(struct xml_node_ctx *ctx, xml_node_t *root,
+			    const char *path)
+{
+	xml_node_t *node;
+	xml_node_t *match;
+
+	xml_node_for_each_child(ctx, node, root) {
+		xml_node_for_each_check(ctx, node);
+		match = get_node(ctx, node, path);
+		if (match)
+			return match;
+	}
+
+	return NULL;
+}
+
+
+xml_node_t * node_from_file(struct xml_node_ctx *ctx, const char *name)
+{
+	xml_node_t *node;
+	char *buf, *buf2, *start;
+	size_t len;
+
+	buf = os_readfile(name, &len);
+	if (buf == NULL)
+		return NULL;
+	buf2 = os_realloc(buf, len + 1);
+	if (buf2 == NULL) {
+		os_free(buf);
+		return NULL;
+	}
+	buf = buf2;
+	buf[len] = '\0';
+
+	start = os_strstr(buf, "<!DOCTYPE ");
+	if (start) {
+		char *pos = start + 1;
+		int count = 1;
+		while (*pos) {
+			if (*pos == '<')
+				count++;
+			else if (*pos == '>') {
+				count--;
+				if (count == 0) {
+					pos++;
+					break;
+				}
+			}
+			pos++;
+		}
+		if (count == 0) {
+			/* Remove DOCTYPE to allow the file to be parsed */
+			os_memset(start, ' ', pos - start);
+		}
+	}
+
+	node = xml_node_from_buf(ctx, buf);
+	os_free(buf);
+
+	return node;
+}
+
+
+int node_to_file(struct xml_node_ctx *ctx, const char *fname, xml_node_t *node)
+{
+	FILE *f;
+	char *str;
+
+	str = xml_node_to_str(ctx, node);
+	if (str == NULL)
+		return -1;
+
+	f = fopen(fname, "w");
+	if (!f) {
+		os_free(str);
+		return -1;
+	}
+
+	fprintf(f, "%s\n", str);
+	os_free(str);
+	fclose(f);
+
+	return 0;
+}
+
+
+static char * get_val(struct xml_node_ctx *ctx, xml_node_t *node)
+{
+	char *val, *pos;
+
+	val = xml_node_get_text(ctx, node);
+	if (val == NULL)
+		return NULL;
+	pos = val;
+	while (*pos) {
+		if (*pos != ' ' && *pos != '\t' && *pos != '\r' && *pos != '\n')
+			return val;
+		pos++;
+	}
+
+	return NULL;
+}
+
+
+static char * add_path(const char *prev, const char *leaf)
+{
+	size_t len;
+	char *new_uri;
+
+	if (prev == NULL)
+		return NULL;
+
+	len = os_strlen(prev) + 1 + os_strlen(leaf) + 1;
+	new_uri = os_malloc(len);
+	if (new_uri)
+		os_snprintf(new_uri, len, "%s/%s", prev, leaf);
+
+	return new_uri;
+}
+
+
+static void node_to_tnds(struct xml_node_ctx *ctx, xml_node_t *out,
+			 xml_node_t *in, const char *uri)
+{
+	xml_node_t *node;
+	xml_node_t *tnds;
+	const char *name;
+	char *val;
+	char *new_uri;
+
+	xml_node_for_each_child(ctx, node, in) {
+		xml_node_for_each_check(ctx, node);
+		name = xml_node_get_localname(ctx, node);
+
+		tnds = xml_node_create(ctx, out, NULL, "Node");
+		if (tnds == NULL)
+			return;
+		xml_node_create_text(ctx, tnds, NULL, "NodeName", name);
+
+		if (uri)
+			xml_node_create_text(ctx, tnds, NULL, "Path", uri);
+
+		val = get_val(ctx, node);
+		if (val) {
+			xml_node_create_text(ctx, tnds, NULL, "Value", val);
+			xml_node_get_text_free(ctx, val);
+		}
+
+		new_uri = add_path(uri, name);
+		node_to_tnds(ctx, new_uri ? out : tnds, node, new_uri);
+		os_free(new_uri);
+	}
+}
+
+
+static int add_ddfname(struct xml_node_ctx *ctx, xml_node_t *parent,
+		       const char *urn)
+{
+	xml_node_t *node;
+
+	node = xml_node_create(ctx, parent, NULL, "RTProperties");
+	if (node == NULL)
+		return -1;
+	node = xml_node_create(ctx, node, NULL, "Type");
+	if (node == NULL)
+		return -1;
+	xml_node_create_text(ctx, node, NULL, "DDFName", urn);
+	return 0;
+}
+
+
+xml_node_t * mo_to_tnds(struct xml_node_ctx *ctx, xml_node_t *mo,
+			int use_path, const char *urn, const char *ns_uri)
+{
+	xml_node_t *root;
+	xml_node_t *node;
+	const char *name;
+
+	root = xml_node_create_root(ctx, ns_uri, NULL, NULL, "MgmtTree");
+	if (root == NULL)
+		return NULL;
+
+	xml_node_create_text(ctx, root, NULL, "VerDTD", "1.2");
+
+	name = xml_node_get_localname(ctx, mo);
+
+	node = xml_node_create(ctx, root, NULL, "Node");
+	if (node == NULL)
+		goto fail;
+	xml_node_create_text(ctx, node, NULL, "NodeName", name);
+	if (urn)
+		add_ddfname(ctx, node, urn);
+
+	node_to_tnds(ctx, use_path ? root : node, mo, use_path ? name : NULL);
+
+	return root;
+
+fail:
+	xml_node_free(ctx, root);
+	return NULL;
+}
+
+
+static xml_node_t * get_first_child_node(struct xml_node_ctx *ctx,
+					 xml_node_t *node,
+					 const char *name)
+{
+	const char *lname;
+	xml_node_t *child;
+
+	xml_node_for_each_child(ctx, child, node) {
+		xml_node_for_each_check(ctx, child);
+		lname = xml_node_get_localname(ctx, child);
+		if (os_strcasecmp(lname, name) == 0)
+			return child;
+	}
+
+	return NULL;
+}
+
+
+static char * get_node_text(struct xml_node_ctx *ctx, xml_node_t *node,
+			    const char *node_name)
+{
+	node = get_first_child_node(ctx, node, node_name);
+	if (node == NULL)
+		return NULL;
+	return xml_node_get_text(ctx, node);
+}
+
+
+static xml_node_t * add_mo_node(struct xml_node_ctx *ctx, xml_node_t *root,
+				xml_node_t *node, const char *uri)
+{
+	char *nodename, *value, *path;
+	xml_node_t *parent;
+
+	nodename = get_node_text(ctx, node, "NodeName");
+	if (nodename == NULL)
+		return NULL;
+	value = get_node_text(ctx, node, "Value");
+
+	if (root == NULL) {
+		root = xml_node_create_root(ctx, NULL, NULL, NULL,
+					    nodename);
+		if (root && value)
+			xml_node_set_text(ctx, root, value);
+	} else {
+		if (uri == NULL) {
+			xml_node_get_text_free(ctx, nodename);
+			xml_node_get_text_free(ctx, value);
+			return NULL;
+		}
+		path = get_node_text(ctx, node, "Path");
+		if (path)
+			uri = path;
+		parent = get_node_uri(ctx, root, uri);
+		xml_node_get_text_free(ctx, path);
+		if (parent == NULL) {
+			printf("Could not find URI '%s'\n", uri);
+			xml_node_get_text_free(ctx, nodename);
+			xml_node_get_text_free(ctx, value);
+			return NULL;
+		}
+		if (value)
+			xml_node_create_text(ctx, parent, NULL, nodename,
+					     value);
+		else
+			xml_node_create(ctx, parent, NULL, nodename);
+	}
+
+	xml_node_get_text_free(ctx, nodename);
+	xml_node_get_text_free(ctx, value);
+
+	return root;
+}
+
+
+static xml_node_t * tnds_to_mo_iter(struct xml_node_ctx *ctx, xml_node_t *root,
+				    xml_node_t *node, const char *uri)
+{
+	xml_node_t *child;
+	const char *name;
+	char *nodename;
+
+	xml_node_for_each_sibling(ctx, node) {
+		xml_node_for_each_check(ctx, node);
+
+		nodename = get_node_text(ctx, node, "NodeName");
+		if (nodename == NULL)
+			return NULL;
+
+		name = xml_node_get_localname(ctx, node);
+		if (strcmp(name, "Node") == 0) {
+			if (root && !uri) {
+				printf("Invalid TNDS tree structure - "
+				       "multiple top level nodes\n");
+				xml_node_get_text_free(ctx, nodename);
+				return NULL;
+			}
+			root = add_mo_node(ctx, root, node, uri);
+		}
+
+		child = get_first_child_node(ctx, node, "Node");
+		if (child) {
+			if (uri == NULL)
+				tnds_to_mo_iter(ctx, root, child, nodename);
+			else {
+				char *new_uri;
+				new_uri = add_path(uri, nodename);
+				tnds_to_mo_iter(ctx, root, child, new_uri);
+				os_free(new_uri);
+			}
+		}
+		xml_node_get_text_free(ctx, nodename);
+	}
+
+	return root;
+}
+
+
+xml_node_t * tnds_to_mo(struct xml_node_ctx *ctx, xml_node_t *tnds)
+{
+	const char *name;
+	xml_node_t *node;
+
+	name = xml_node_get_localname(ctx, tnds);
+	if (name == NULL || os_strcmp(name, "MgmtTree") != 0)
+		return NULL;
+
+	node = get_first_child_node(ctx, tnds, "Node");
+	if (!node)
+		return NULL;
+	return tnds_to_mo_iter(ctx, NULL, node, NULL);
+}
+
+
+xml_node_t * soap_build_envelope(struct xml_node_ctx *ctx, xml_node_t *node)
+{
+	xml_node_t *envelope, *body;
+	xml_namespace_t *ns;
+
+	envelope = xml_node_create_root(
+		ctx, "http://www.w3.org/2003/05/soap-envelope", "soap12", &ns,
+		"Envelope");
+	if (envelope == NULL)
+		return NULL;
+	body = xml_node_create(ctx, envelope, ns, "Body");
+	xml_node_add_child(ctx, body, node);
+	return envelope;
+}
+
+
+xml_node_t * soap_get_body(struct xml_node_ctx *ctx, xml_node_t *soap)
+{
+	xml_node_t *body, *child;
+
+	body = get_node_uri(ctx, soap, "Envelope/Body");
+	if (body == NULL)
+		return NULL;
+	xml_node_for_each_child(ctx, child, body) {
+		xml_node_for_each_check(ctx, child);
+		return child;
+	}
+	return NULL;
+}
diff --git a/src/utils/xml-utils.h b/src/utils/xml-utils.h
new file mode 100644
index 0000000..0d8e0cb
--- /dev/null
+++ b/src/utils/xml-utils.h
@@ -0,0 +1,100 @@
+/*
+ * Generic XML helper functions
+ * Copyright (c) 2012-2013, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef XML_UTILS_H
+#define XML_UTILS_H
+
+struct xml_node_ctx;
+typedef struct xml_node xml_node_t;
+typedef struct xml_namespace_foo xml_namespace_t;
+
+/* XML library wrappers */
+
+int xml_validate(struct xml_node_ctx *ctx, xml_node_t *node,
+		 const char *xml_schema_fname, char **ret_err);
+int xml_validate_dtd(struct xml_node_ctx *ctx, xml_node_t *node,
+		     const char *dtd_fname, char **ret_err);
+void xml_node_free(struct xml_node_ctx *ctx, xml_node_t *node);
+xml_node_t * xml_node_get_parent(struct xml_node_ctx *ctx, xml_node_t *node);
+xml_node_t * xml_node_from_buf(struct xml_node_ctx *ctx, const char *buf);
+const char * xml_node_get_localname(struct xml_node_ctx *ctx,
+				    xml_node_t *node);
+char * xml_node_to_str(struct xml_node_ctx *ctx, xml_node_t *node);
+void xml_node_detach(struct xml_node_ctx *ctx, xml_node_t *node);
+void xml_node_add_child(struct xml_node_ctx *ctx, xml_node_t *parent,
+			xml_node_t *child);
+xml_node_t * xml_node_create_root(struct xml_node_ctx *ctx, const char *ns_uri,
+				  const char *ns_prefix,
+				  xml_namespace_t **ret_ns, const char *name);
+xml_node_t * xml_node_create(struct xml_node_ctx *ctx, xml_node_t *parent,
+			     xml_namespace_t *ns, const char *name);
+xml_node_t * xml_node_create_text(struct xml_node_ctx *ctx,
+				  xml_node_t *parent, xml_namespace_t *ns,
+				  const char *name, const char *value);
+xml_node_t * xml_node_create_text_ns(struct xml_node_ctx *ctx,
+				     xml_node_t *parent, const char *ns_uri,
+				     const char *name, const char *value);
+void xml_node_set_text(struct xml_node_ctx *ctx, xml_node_t *node,
+		       const char *value);
+int xml_node_add_attr(struct xml_node_ctx *ctx, xml_node_t *node,
+		      xml_namespace_t *ns, const char *name, const char *value);
+char * xml_node_get_attr_value(struct xml_node_ctx *ctx, xml_node_t *node,
+			       char *name);
+char * xml_node_get_attr_value_ns(struct xml_node_ctx *ctx, xml_node_t *node,
+				  const char *ns_uri, char *name);
+void xml_node_get_attr_value_free(struct xml_node_ctx *ctx, char *val);
+xml_node_t * xml_node_first_child(struct xml_node_ctx *ctx,
+				  xml_node_t *parent);
+xml_node_t * xml_node_next_sibling(struct xml_node_ctx *ctx,
+				   xml_node_t *node);
+int xml_node_is_element(struct xml_node_ctx *ctx, xml_node_t *node);
+char * xml_node_get_text(struct xml_node_ctx *ctx, xml_node_t *node);
+void xml_node_get_text_free(struct xml_node_ctx *ctx, char *val);
+char * xml_node_get_base64_text(struct xml_node_ctx *ctx, xml_node_t *node,
+				int *ret_len);
+xml_node_t * xml_node_copy(struct xml_node_ctx *ctx, xml_node_t *node);
+
+#define xml_node_for_each_child(ctx, child, parent) \
+for (child = xml_node_first_child(ctx, parent); \
+     child; \
+     child = xml_node_next_sibling(ctx, child))
+
+#define xml_node_for_each_sibling(ctx, node) \
+for (; \
+     node; \
+     node = xml_node_next_sibling(ctx, node))
+
+#define xml_node_for_each_check(ctx, child) \
+if (!xml_node_is_element(ctx, child)) \
+	continue
+
+typedef void (*debug_print_func)(void *ctx, int print, const char *fmt, ...)
+	__attribute__ ((format (printf, 3, 4)));
+
+
+struct xml_node_ctx * xml_node_init_ctx(void *upper_ctx,
+					const void *env);
+void xml_node_deinit_ctx(struct xml_node_ctx *ctx);
+
+
+xml_node_t * get_node_uri(struct xml_node_ctx *ctx, xml_node_t *root,
+			  const char *uri);
+xml_node_t * get_node(struct xml_node_ctx *ctx, xml_node_t *root,
+		      const char *path);
+xml_node_t * get_child_node(struct xml_node_ctx *ctx, xml_node_t *root,
+			    const char *path);
+xml_node_t * node_from_file(struct xml_node_ctx *ctx, const char *name);
+int node_to_file(struct xml_node_ctx *ctx, const char *fname, xml_node_t *node);
+xml_node_t * mo_to_tnds(struct xml_node_ctx *ctx, xml_node_t *mo,
+			int use_path, const char *urn, const char *ns_uri);
+xml_node_t * tnds_to_mo(struct xml_node_ctx *ctx, xml_node_t *tnds);
+
+xml_node_t * soap_build_envelope(struct xml_node_ctx *ctx, xml_node_t *node);
+xml_node_t * soap_get_body(struct xml_node_ctx *ctx, xml_node_t *soap);
+
+#endif /* XML_UTILS_H */
diff --git a/src/utils/xml_libxml2.c b/src/utils/xml_libxml2.c
new file mode 100644
index 0000000..c928394
--- /dev/null
+++ b/src/utils/xml_libxml2.c
@@ -0,0 +1,457 @@
+/*
+ * XML wrapper for libxml2
+ * Copyright (c) 2012-2013, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "includes.h"
+#define LIBXML_VALID_ENABLED
+#include <libxml/tree.h>
+#include <libxml/xmlschemastypes.h>
+
+#include "common.h"
+#include "base64.h"
+#include "xml-utils.h"
+
+
+struct xml_node_ctx {
+	void *ctx;
+};
+
+
+struct str_buf {
+	char *buf;
+	size_t len;
+};
+
+#define MAX_STR 1000
+
+static void add_str(void *ctx_ptr, const char *fmt, ...)
+{
+	struct str_buf *str = ctx_ptr;
+	va_list ap;
+	char *n;
+	int len;
+
+	n = os_realloc(str->buf, str->len + MAX_STR + 2);
+	if (n == NULL)
+		return;
+	str->buf = n;
+
+	va_start(ap, fmt);
+	len = vsnprintf(str->buf + str->len, MAX_STR, fmt, ap);
+	va_end(ap);
+	if (len >= MAX_STR)
+		len = MAX_STR - 1;
+	str->len += len;
+	str->buf[str->len] = '\0';
+}
+
+
+int xml_validate(struct xml_node_ctx *ctx, xml_node_t *node,
+		 const char *xml_schema_fname, char **ret_err)
+{
+	xmlDocPtr doc;
+	xmlNodePtr n;
+	xmlSchemaParserCtxtPtr pctx;
+	xmlSchemaValidCtxtPtr vctx;
+	xmlSchemaPtr schema;
+	int ret;
+	struct str_buf errors;
+
+	if (ret_err)
+		*ret_err = NULL;
+
+	doc = xmlNewDoc((xmlChar *) "1.0");
+	if (doc == NULL)
+		return -1;
+	n = xmlDocCopyNode((xmlNodePtr) node, doc, 1);
+	if (n == NULL) {
+		xmlFreeDoc(doc);
+		return -1;
+	}
+	xmlDocSetRootElement(doc, n);
+
+	os_memset(&errors, 0, sizeof(errors));
+
+	pctx = xmlSchemaNewParserCtxt(xml_schema_fname);
+	xmlSchemaSetParserErrors(pctx, (xmlSchemaValidityErrorFunc) add_str,
+				 (xmlSchemaValidityWarningFunc) add_str,
+				 &errors);
+	schema = xmlSchemaParse(pctx);
+	xmlSchemaFreeParserCtxt(pctx);
+
+	vctx = xmlSchemaNewValidCtxt(schema);
+	xmlSchemaSetValidErrors(vctx, (xmlSchemaValidityErrorFunc) add_str,
+				(xmlSchemaValidityWarningFunc) add_str,
+				&errors);
+
+	ret = xmlSchemaValidateDoc(vctx, doc);
+	xmlSchemaFreeValidCtxt(vctx);
+	xmlFreeDoc(doc);
+	xmlSchemaFree(schema);
+
+	if (ret == 0) {
+		os_free(errors.buf);
+		return 0;
+	} else if (ret > 0) {
+		if (ret_err)
+			*ret_err = errors.buf;
+		else
+			os_free(errors.buf);
+		return -1;
+	} else {
+		if (ret_err)
+			*ret_err = errors.buf;
+		else
+			os_free(errors.buf);
+		return -1;
+	}
+}
+
+
+int xml_validate_dtd(struct xml_node_ctx *ctx, xml_node_t *node,
+		     const char *dtd_fname, char **ret_err)
+{
+	xmlDocPtr doc;
+	xmlNodePtr n;
+	xmlValidCtxt vctx;
+	xmlDtdPtr dtd;
+	int ret;
+	struct str_buf errors;
+
+	if (ret_err)
+		*ret_err = NULL;
+
+	doc = xmlNewDoc((xmlChar *) "1.0");
+	if (doc == NULL)
+		return -1;
+	n = xmlDocCopyNode((xmlNodePtr) node, doc, 1);
+	if (n == NULL) {
+		xmlFreeDoc(doc);
+		return -1;
+	}
+	xmlDocSetRootElement(doc, n);
+
+	os_memset(&errors, 0, sizeof(errors));
+
+	dtd = xmlParseDTD(NULL, (const xmlChar *) dtd_fname);
+	if (dtd == NULL) {
+		xmlFreeDoc(doc);
+		return -1;
+	}
+
+	os_memset(&vctx, 0, sizeof(vctx));
+	vctx.userData = &errors;
+	vctx.error = add_str;
+	vctx.warning = add_str;
+	ret = xmlValidateDtd(&vctx, doc, dtd);
+	xmlFreeDoc(doc);
+	xmlFreeDtd(dtd);
+
+	if (ret == 1) {
+		os_free(errors.buf);
+		return 0;
+	} else {
+		if (ret_err)
+			*ret_err = errors.buf;
+		else
+			os_free(errors.buf);
+		return -1;
+	}
+}
+
+
+void xml_node_free(struct xml_node_ctx *ctx, xml_node_t *node)
+{
+	xmlFreeNode((xmlNodePtr) node);
+}
+
+
+xml_node_t * xml_node_get_parent(struct xml_node_ctx *ctx, xml_node_t *node)
+{
+	return (xml_node_t *) ((xmlNodePtr) node)->parent;
+}
+
+
+xml_node_t * xml_node_from_buf(struct xml_node_ctx *ctx, const char *buf)
+{
+	xmlDocPtr doc;
+	xmlNodePtr node;
+
+	doc = xmlParseMemory(buf, strlen(buf));
+	if (doc == NULL)
+		return NULL;
+	node = xmlDocGetRootElement(doc);
+	node = xmlCopyNode(node, 1);
+	xmlFreeDoc(doc);
+
+	return (xml_node_t *) node;
+}
+
+
+const char * xml_node_get_localname(struct xml_node_ctx *ctx,
+				    xml_node_t *node)
+{
+	return (const char *) ((xmlNodePtr) node)->name;
+}
+
+
+char * xml_node_to_str(struct xml_node_ctx *ctx, xml_node_t *node)
+{
+	xmlChar *buf;
+	int bufsiz;
+	char *ret, *pos;
+	xmlNodePtr n = (xmlNodePtr) node;
+	xmlDocPtr doc;
+
+	doc = xmlNewDoc((xmlChar *) "1.0");
+	n = xmlDocCopyNode(n, doc, 1);
+	xmlDocSetRootElement(doc, n);
+	xmlDocDumpFormatMemory(doc, &buf, &bufsiz, 0);
+	xmlFreeDoc(doc);
+	pos = (char *) buf;
+	if (strncmp(pos, "<?xml", 5) == 0) {
+		pos = strchr(pos, '>');
+		if (pos)
+			pos++;
+		while (pos && (*pos == '\r' || *pos == '\n'))
+			pos++;
+	}
+	if (pos)
+		ret = os_strdup(pos);
+	else
+		ret = NULL;
+	xmlFree(buf);
+
+	if (ret) {
+		pos = ret;
+		if (pos[0]) {
+			while (pos[1])
+				pos++;
+		}
+		while (pos >= ret && *pos == '\n')
+			*pos-- = '\0';
+	}
+
+	return ret;
+}
+
+
+void xml_node_detach(struct xml_node_ctx *ctx, xml_node_t *node)
+{
+	xmlUnlinkNode((xmlNodePtr) node);
+}
+
+
+void xml_node_add_child(struct xml_node_ctx *ctx, xml_node_t *parent,
+			xml_node_t *child)
+{
+	xmlAddChild((xmlNodePtr) parent, (xmlNodePtr) child);
+}
+
+
+xml_node_t * xml_node_create_root(struct xml_node_ctx *ctx, const char *ns_uri,
+				  const char *ns_prefix,
+				  xml_namespace_t **ret_ns, const char *name)
+{
+	xmlNodePtr node;
+	xmlNsPtr ns = NULL;
+
+	node = xmlNewNode(NULL, (const xmlChar *) name);
+	if (node == NULL)
+		return NULL;
+	if (ns_uri) {
+		ns = xmlNewNs(node, (const xmlChar *) ns_uri,
+			      (const xmlChar *) ns_prefix);
+		xmlSetNs(node, ns);
+	}
+
+	if (ret_ns)
+		*ret_ns = (xml_namespace_t *) ns;
+
+	return (xml_node_t *) node;
+}
+
+
+xml_node_t * xml_node_create(struct xml_node_ctx *ctx, xml_node_t *parent,
+			     xml_namespace_t *ns, const char *name)
+{
+	xmlNodePtr node;
+	node = xmlNewChild((xmlNodePtr) parent, (xmlNsPtr) ns,
+			   (const xmlChar *) name, NULL);
+	return (xml_node_t *) node;
+}
+
+
+xml_node_t * xml_node_create_text(struct xml_node_ctx *ctx,
+				  xml_node_t *parent, xml_namespace_t *ns,
+				  const char *name, const char *value)
+{
+	xmlNodePtr node;
+	node = xmlNewTextChild((xmlNodePtr) parent, (xmlNsPtr) ns,
+			       (const xmlChar *) name, (const xmlChar *) value);
+	return (xml_node_t *) node;
+}
+
+
+xml_node_t * xml_node_create_text_ns(struct xml_node_ctx *ctx,
+				     xml_node_t *parent, const char *ns_uri,
+				     const char *name, const char *value)
+{
+	xmlNodePtr node;
+	xmlNsPtr ns;
+
+	node = xmlNewTextChild((xmlNodePtr) parent, NULL,
+			       (const xmlChar *) name, (const xmlChar *) value);
+	ns = xmlNewNs(node, (const xmlChar *) ns_uri, NULL);
+	xmlSetNs(node, ns);
+	return (xml_node_t *) node;
+}
+
+
+void xml_node_set_text(struct xml_node_ctx *ctx, xml_node_t *node,
+		       const char *value)
+{
+	/* TODO: escape XML special chars in value */
+	xmlNodeSetContent((xmlNodePtr) node, (xmlChar *) value);
+}
+
+
+int xml_node_add_attr(struct xml_node_ctx *ctx, xml_node_t *node,
+		      xml_namespace_t *ns, const char *name, const char *value)
+{
+	xmlAttrPtr attr;
+
+	if (ns) {
+		attr = xmlNewNsProp((xmlNodePtr) node, (xmlNsPtr) ns,
+				    (const xmlChar *) name,
+				    (const xmlChar *) value);
+	} else {
+		attr = xmlNewProp((xmlNodePtr) node, (const xmlChar *) name,
+				  (const xmlChar *) value);
+	}
+
+	return attr ? 0 : -1;
+}
+
+
+char * xml_node_get_attr_value(struct xml_node_ctx *ctx, xml_node_t *node,
+			       char *name)
+{
+	return (char *) xmlGetNoNsProp((xmlNodePtr) node,
+				       (const xmlChar *) name);
+}
+
+
+char * xml_node_get_attr_value_ns(struct xml_node_ctx *ctx, xml_node_t *node,
+				  const char *ns_uri, char *name)
+{
+	return (char *) xmlGetNsProp((xmlNodePtr) node, (const xmlChar *) name,
+				     (const xmlChar *) ns_uri);
+}
+
+
+void xml_node_get_attr_value_free(struct xml_node_ctx *ctx, char *val)
+{
+	if (val)
+		xmlFree((xmlChar *) val);
+}
+
+
+xml_node_t * xml_node_first_child(struct xml_node_ctx *ctx,
+				  xml_node_t *parent)
+{
+	return (xml_node_t *) ((xmlNodePtr) parent)->children;
+}
+
+
+xml_node_t * xml_node_next_sibling(struct xml_node_ctx *ctx,
+				   xml_node_t *node)
+{
+	return (xml_node_t *) ((xmlNodePtr) node)->next;
+}
+
+
+int xml_node_is_element(struct xml_node_ctx *ctx, xml_node_t *node)
+{
+	return ((xmlNodePtr) node)->type == XML_ELEMENT_NODE;
+}
+
+
+char * xml_node_get_text(struct xml_node_ctx *ctx, xml_node_t *node)
+{
+	if (xmlChildElementCount((xmlNodePtr) node) > 0)
+		return NULL;
+	return (char *) xmlNodeGetContent((xmlNodePtr) node);
+}
+
+
+void xml_node_get_text_free(struct xml_node_ctx *ctx, char *val)
+{
+	if (val)
+		xmlFree((xmlChar *) val);
+}
+
+
+char * xml_node_get_base64_text(struct xml_node_ctx *ctx, xml_node_t *node,
+				int *ret_len)
+{
+	char *txt;
+	unsigned char *ret;
+	size_t len;
+
+	txt = xml_node_get_text(ctx, node);
+	if (txt == NULL)
+		return NULL;
+
+	ret = base64_decode((unsigned char *) txt, strlen(txt), &len);
+	if (ret_len)
+		*ret_len = len;
+	xml_node_get_text_free(ctx, txt);
+	if (ret == NULL)
+		return NULL;
+	txt = os_malloc(len + 1);
+	if (txt == NULL) {
+		os_free(ret);
+		return NULL;
+	}
+	os_memcpy(txt, ret, len);
+	txt[len] = '\0';
+	return txt;
+}
+
+
+xml_node_t * xml_node_copy(struct xml_node_ctx *ctx, xml_node_t *node)
+{
+	if (node == NULL)
+		return NULL;
+	return (xml_node_t *) xmlCopyNode((xmlNodePtr) node, 1);
+}
+
+
+struct xml_node_ctx * xml_node_init_ctx(void *upper_ctx,
+					const void *env)
+{
+	struct xml_node_ctx *xctx;
+
+	xctx = os_zalloc(sizeof(*xctx));
+	if (xctx == NULL)
+		return NULL;
+	xctx->ctx = upper_ctx;
+
+	LIBXML_TEST_VERSION
+
+	return xctx;
+}
+
+
+void xml_node_deinit_ctx(struct xml_node_ctx *ctx)
+{
+	xmlSchemaCleanupTypes();
+	xmlCleanupParser();
+	xmlMemoryDump();
+	os_free(ctx);
+}