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/hs20/client/spp_client.c b/hs20/client/spp_client.c
new file mode 100644
index 0000000..8022ccb
--- /dev/null
+++ b/hs20/client/spp_client.c
@@ -0,0 +1,1001 @@
+/*
+ * Hotspot 2.0 SPP client
+ * 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 <sys/stat.h>
+
+#include "common.h"
+#include "browser.h"
+#include "wpa_ctrl.h"
+#include "wpa_helpers.h"
+#include "xml-utils.h"
+#include "http-utils.h"
+#include "utils/base64.h"
+#include "crypto/crypto.h"
+#include "crypto/sha256.h"
+#include "osu_client.h"
+
+
+static int hs20_spp_update_response(struct hs20_osu_client *ctx,
+				    const char *session_id,
+				    const char *spp_status,
+				    const char *error_code);
+static void hs20_policy_update_complete(
+	struct hs20_osu_client *ctx, const char *pps_fname);
+
+
+static char * get_spp_attr_value(struct xml_node_ctx *ctx, xml_node_t *node,
+				 char *attr_name)
+{
+	return xml_node_get_attr_value_ns(ctx, node, SPP_NS_URI, attr_name);
+}
+
+
+static int hs20_spp_validate(struct hs20_osu_client *ctx, xml_node_t *node,
+			     const char *expected_name)
+{
+	struct xml_node_ctx *xctx = ctx->xml;
+	const char *name;
+	char *err;
+	int ret;
+
+	if (!xml_node_is_element(xctx, node))
+		return -1;
+
+	name = xml_node_get_localname(xctx, node);
+	if (name == NULL)
+		return -1;
+
+	if (strcmp(expected_name, name) != 0) {
+		wpa_printf(MSG_INFO, "Unexpected SOAP method name '%s' (expected '%s')",
+			   name, expected_name);
+		write_summary(ctx, "Unexpected SOAP method name '%s' (expected '%s')",
+			      name, expected_name);
+		return -1;
+	}
+
+	ret = xml_validate(xctx, node, "spp.xsd", &err);
+	if (ret < 0) {
+		wpa_printf(MSG_INFO, "XML schema validation error(s)\n%s", err);
+		write_summary(ctx, "SPP XML schema validation failed");
+		os_free(err);
+	}
+	return ret;
+}
+
+
+static void add_mo_container(struct xml_node_ctx *ctx, xml_namespace_t *ns,
+			     xml_node_t *parent, const char *urn,
+			     const char *fname)
+{
+	xml_node_t *node;
+	xml_node_t *fnode, *tnds;
+	char *str;
+
+	fnode = node_from_file(ctx, fname);
+	if (!fnode)
+		return;
+	tnds = mo_to_tnds(ctx, fnode, 0, urn, "syncml:dmddf1.2");
+	xml_node_free(ctx, fnode);
+	if (!tnds)
+		return;
+
+	str = xml_node_to_str(ctx, tnds);
+	xml_node_free(ctx, tnds);
+	if (str == NULL)
+		return;
+
+	node = xml_node_create_text(ctx, parent, ns, "moContainer", str);
+	if (node)
+		xml_node_add_attr(ctx, node, ns, "moURN", urn);
+	os_free(str);
+}
+
+
+static xml_node_t * build_spp_post_dev_data(struct hs20_osu_client *ctx,
+					    xml_namespace_t **ret_ns,
+					    const char *session_id,
+					    const char *reason)
+{
+	xml_namespace_t *ns;
+	xml_node_t *spp_node;
+
+	write_summary(ctx, "Building sppPostDevData requestReason='%s'",
+		      reason);
+	spp_node = xml_node_create_root(ctx->xml, SPP_NS_URI, "spp", &ns,
+					"sppPostDevData");
+	if (spp_node == NULL)
+		return NULL;
+	if (ret_ns)
+		*ret_ns = ns;
+
+	xml_node_add_attr(ctx->xml, spp_node, ns, "sppVersion", "1.0");
+	xml_node_add_attr(ctx->xml, spp_node, NULL, "requestReason", reason);
+	if (session_id)
+		xml_node_add_attr(ctx->xml, spp_node, ns, "sessionID",
+				  session_id);
+	xml_node_add_attr(ctx->xml, spp_node, NULL, "redirectURI",
+			  "http://localhost:12345/");
+
+	xml_node_create_text(ctx->xml, spp_node, ns, "supportedSPPVersions",
+			     "1.0");
+	xml_node_create_text(ctx->xml, spp_node, ns, "supportedMOList",
+			     URN_HS20_PPS " " URN_OMA_DM_DEVINFO " "
+			     URN_OMA_DM_DEVDETAIL " " URN_HS20_DEVDETAIL_EXT);
+
+	add_mo_container(ctx->xml, ns, spp_node, URN_OMA_DM_DEVINFO,
+			 "devinfo.xml");
+	add_mo_container(ctx->xml, ns, spp_node, URN_OMA_DM_DEVDETAIL,
+			 "devdetail.xml");
+
+	return spp_node;
+}
+
+
+static int process_update_node(struct hs20_osu_client *ctx, xml_node_t *pps,
+			       xml_node_t *update)
+{
+	xml_node_t *node, *parent, *tnds, *unode;
+	char *str;
+	const char *name;
+	char *uri, *pos;
+	char *cdata, *cdata_end;
+	size_t fqdn_len;
+
+	wpa_printf(MSG_INFO, "Processing updateNode");
+	debug_dump_node(ctx, "updateNode", update);
+
+	uri = get_spp_attr_value(ctx->xml, update, "managementTreeURI");
+	if (uri == NULL) {
+		wpa_printf(MSG_INFO, "No managementTreeURI present");
+		return -1;
+	}
+	wpa_printf(MSG_INFO, "managementTreeUri: '%s'", uri);
+
+	name = os_strrchr(uri, '/');
+	if (name == NULL) {
+		wpa_printf(MSG_INFO, "Unexpected URI");
+		xml_node_get_attr_value_free(ctx->xml, uri);
+		return -1;
+	}
+	name++;
+	wpa_printf(MSG_INFO, "Update interior node: '%s'", name);
+
+	str = xml_node_get_text(ctx->xml, update);
+	if (str == NULL) {
+		wpa_printf(MSG_INFO, "Could not extract MO text");
+		xml_node_get_attr_value_free(ctx->xml, uri);
+		return -1;
+	}
+	wpa_printf(MSG_DEBUG, "[hs20] nodeContainer text: '%s'", str);
+	cdata = strstr(str, "<![CDATA[");
+	cdata_end = strstr(str, "]]>");
+	if (cdata && cdata_end && cdata_end > cdata &&
+	    cdata < strstr(str, "MgmtTree") &&
+	    cdata_end > strstr(str, "/MgmtTree")) {
+		char *tmp;
+		wpa_printf(MSG_DEBUG, "[hs20] Removing extra CDATA container");
+		tmp = strdup(cdata + 9);
+		if (tmp) {
+			cdata_end = strstr(tmp, "]]>");
+			if (cdata_end)
+				*cdata_end = '\0';
+			wpa_printf(MSG_DEBUG, "[hs20] nodeContainer text with CDATA container removed: '%s'",
+				   tmp);
+			tnds = xml_node_from_buf(ctx->xml, tmp);
+			free(tmp);
+		} else
+			tnds = NULL;
+	} else
+		tnds = xml_node_from_buf(ctx->xml, str);
+	xml_node_get_text_free(ctx->xml, str);
+	if (tnds == NULL) {
+		wpa_printf(MSG_INFO, "[hs20] Could not parse nodeContainer text");
+		xml_node_get_attr_value_free(ctx->xml, uri);
+		return -1;
+	}
+
+	unode = tnds_to_mo(ctx->xml, tnds);
+	xml_node_free(ctx->xml, tnds);
+	if (unode == NULL) {
+		wpa_printf(MSG_INFO, "[hs20] Could not parse nodeContainer TNDS text");
+		xml_node_get_attr_value_free(ctx->xml, uri);
+		return -1;
+	}
+
+	debug_dump_node(ctx, "Parsed TNDS", unode);
+
+	if (get_node_uri(ctx->xml, unode, name) == NULL) {
+		wpa_printf(MSG_INFO, "[hs20] %s node not found", name);
+		xml_node_free(ctx->xml, unode);
+		xml_node_get_attr_value_free(ctx->xml, uri);
+		return -1;
+	}
+
+	if (os_strncasecmp(uri, "./Wi-Fi/", 8) != 0) {
+		wpa_printf(MSG_INFO, "Do not allow update outside ./Wi-Fi");
+		xml_node_free(ctx->xml, unode);
+		xml_node_get_attr_value_free(ctx->xml, uri);
+		return -1;
+	}
+	pos = uri + 8;
+
+	if (ctx->fqdn == NULL) {
+		wpa_printf(MSG_INFO, "FQDN not known");
+		xml_node_free(ctx->xml, unode);
+		xml_node_get_attr_value_free(ctx->xml, uri);
+		return -1;
+	}
+	fqdn_len = os_strlen(ctx->fqdn);
+	if (os_strncasecmp(pos, ctx->fqdn, fqdn_len) != 0 ||
+	    pos[fqdn_len] != '/') {
+		wpa_printf(MSG_INFO, "Do not allow update outside ./Wi-Fi/%s",
+			   ctx->fqdn);
+		xml_node_free(ctx->xml, unode);
+		xml_node_get_attr_value_free(ctx->xml, uri);
+		return -1;
+	}
+	pos += fqdn_len + 1;
+
+	if (os_strncasecmp(pos, "PerProviderSubscription/", 24) != 0) {
+		wpa_printf(MSG_INFO, "Do not allow update outside ./Wi-Fi/%s/PerProviderSubscription",
+			   ctx->fqdn);
+		xml_node_free(ctx->xml, unode);
+		xml_node_get_attr_value_free(ctx->xml, uri);
+		return -1;
+	}
+	pos += 24;
+
+	wpa_printf(MSG_INFO, "Update command for PPS node %s", pos);
+
+	node = get_node(ctx->xml, pps, pos);
+	if (node) {
+		parent = xml_node_get_parent(ctx->xml, node);
+		xml_node_detach(ctx->xml, node);
+		wpa_printf(MSG_INFO, "Replace '%s' node", name);
+	} else {
+		char *pos2;
+		pos2 = os_strrchr(pos, '/');
+		if (pos2 == NULL) {
+			parent = pps;
+		} else {
+			*pos2 = '\0';
+			parent = get_node(ctx->xml, pps, pos);
+		}
+		if (parent == NULL) {
+			wpa_printf(MSG_INFO, "Could not find parent %s", pos);
+			xml_node_free(ctx->xml, unode);
+			xml_node_get_attr_value_free(ctx->xml, uri);
+			return -1;
+		}
+		wpa_printf(MSG_INFO, "Add '%s' node", name);
+	}
+	xml_node_add_child(ctx->xml, parent, unode);
+
+	xml_node_get_attr_value_free(ctx->xml, uri);
+
+	return 0;
+}
+
+
+static int update_pps(struct hs20_osu_client *ctx, xml_node_t *update,
+		      const char *pps_fname, xml_node_t *pps)
+{
+	wpa_printf(MSG_INFO, "Updating PPS based on updateNode element(s)");
+	xml_node_for_each_sibling(ctx->xml, update) {
+		xml_node_for_each_check(ctx->xml, update);
+		if (process_update_node(ctx, pps, update) < 0)
+			return -1;
+	}
+
+	return update_pps_file(ctx, pps_fname, pps);
+}
+
+
+static void hs20_sub_rem_complete(struct hs20_osu_client *ctx,
+				  const char *pps_fname)
+{
+	/*
+	 * Update wpa_supplicant credentials and reconnect using updated
+	 * information.
+	 */
+	wpa_printf(MSG_INFO, "Updating wpa_supplicant credentials");
+	cmd_set_pps(ctx, pps_fname);
+
+	if (ctx->no_reconnect)
+		return;
+
+	wpa_printf(MSG_INFO, "Requesting reconnection with updated configuration");
+	if (wpa_command(ctx->ifname, "INTERWORKING_SELECT auto") < 0)
+		wpa_printf(MSG_ERROR, "Failed to request wpa_supplicant to reconnect");
+}
+
+
+static xml_node_t * hs20_spp_upload_mo(struct hs20_osu_client *ctx,
+				       xml_node_t *cmd,
+				       const char *session_id,
+				       const char *pps_fname)
+{
+	xml_namespace_t *ns;
+	xml_node_t *node, *ret_node;
+	char *urn;
+
+	urn = get_spp_attr_value(ctx->xml, cmd, "moURN");
+	if (!urn) {
+		wpa_printf(MSG_INFO, "No URN included");
+		return NULL;
+	}
+	wpa_printf(MSG_INFO, "Upload MO request - URN=%s", urn);
+	if (strcasecmp(urn, URN_HS20_PPS) != 0) {
+		wpa_printf(MSG_INFO, "Unsupported moURN");
+		xml_node_get_attr_value_free(ctx->xml, urn);
+		return NULL;
+	}
+	xml_node_get_attr_value_free(ctx->xml, urn);
+
+	if (!pps_fname) {
+		wpa_printf(MSG_INFO, "PPS file name no known");
+		return NULL;
+	}
+
+	node = build_spp_post_dev_data(ctx, &ns, session_id,
+				       "MO upload");
+	if (node == NULL)
+		return NULL;
+	add_mo_container(ctx->xml, ns, node, URN_HS20_PPS, pps_fname);
+
+	ret_node = soap_send_receive(ctx->http, node);
+	if (ret_node == NULL)
+		return NULL;
+
+	debug_dump_node(ctx, "Received response to MO upload", ret_node);
+
+	if (hs20_spp_validate(ctx, ret_node, "sppPostDevDataResponse") < 0) {
+		wpa_printf(MSG_INFO, "SPP validation failed");
+		xml_node_free(ctx->xml, ret_node);
+		return NULL;
+	}
+
+	return ret_node;
+}
+
+
+static int hs20_add_mo(struct hs20_osu_client *ctx, xml_node_t *add_mo,
+		       char *fname, size_t fname_len)
+{
+	char *uri, *urn;
+	int ret;
+
+	debug_dump_node(ctx, "Received addMO", add_mo);
+
+	urn = get_spp_attr_value(ctx->xml, add_mo, "moURN");
+	if (urn == NULL) {
+		wpa_printf(MSG_INFO, "[hs20] No moURN in addMO");
+		return -1;
+	}
+	wpa_printf(MSG_INFO, "addMO - moURN: '%s'", urn);
+	if (strcasecmp(urn, URN_HS20_PPS) != 0) {
+		wpa_printf(MSG_INFO, "[hs20] Unsupported MO in addMO");
+		xml_node_get_attr_value_free(ctx->xml, urn);
+		return -1;
+	}
+	xml_node_get_attr_value_free(ctx->xml, urn);
+
+	uri = get_spp_attr_value(ctx->xml, add_mo, "managementTreeURI");
+	if (uri == NULL) {
+		wpa_printf(MSG_INFO, "[hs20] No managementTreeURI in addMO");
+		return -1;
+	}
+	wpa_printf(MSG_INFO, "addMO - managementTreeURI: '%s'", uri);
+
+	ret = hs20_add_pps_mo(ctx, uri, add_mo, fname, fname_len);
+	xml_node_get_attr_value_free(ctx->xml, uri);
+	return ret;
+}
+
+
+static int process_spp_user_input_response(struct hs20_osu_client *ctx,
+					   const char *session_id,
+					   xml_node_t *add_mo)
+{
+	int ret;
+	char fname[300];
+
+	debug_dump_node(ctx, "addMO", add_mo);
+
+	wpa_printf(MSG_INFO, "Subscription registration completed");
+
+	if (hs20_add_mo(ctx, add_mo, fname, sizeof(fname)) < 0) {
+		wpa_printf(MSG_INFO, "Could not add MO");
+		ret = hs20_spp_update_response(
+			ctx, session_id,
+			"Error occurred",
+			"MO addition or update failed");
+		return 0;
+	}
+
+	ret = hs20_spp_update_response(ctx, session_id, "OK", NULL);
+	if (ret == 0)
+		hs20_sub_rem_complete(ctx, fname);
+
+	return 0;
+}
+
+
+static xml_node_t * hs20_spp_user_input_completed(struct hs20_osu_client *ctx,
+						    const char *session_id)
+{
+	xml_node_t *node, *ret_node;
+
+	node = build_spp_post_dev_data(ctx, NULL, session_id,
+				       "User input completed");
+	if (node == NULL)
+		return NULL;
+
+	ret_node = soap_send_receive(ctx->http, node);
+	if (!ret_node) {
+		if (soap_reinit_client(ctx->http) < 0)
+			return NULL;
+		wpa_printf(MSG_INFO, "Try to finish with re-opened connection");
+		node = build_spp_post_dev_data(ctx, NULL, session_id,
+					       "User input completed");
+		if (node == NULL)
+			return NULL;
+		ret_node = soap_send_receive(ctx->http, node);
+		if (ret_node == NULL)
+			return NULL;
+		wpa_printf(MSG_INFO, "Continue with new connection");
+	}
+
+	if (hs20_spp_validate(ctx, ret_node, "sppPostDevDataResponse") < 0) {
+		wpa_printf(MSG_INFO, "SPP validation failed");
+		xml_node_free(ctx->xml, ret_node);
+		return NULL;
+	}
+
+	return ret_node;
+}
+
+
+static xml_node_t * hs20_spp_get_certificate(struct hs20_osu_client *ctx,
+					     xml_node_t *cmd,
+					     const char *session_id,
+					     const char *pps_fname)
+{
+	xml_namespace_t *ns;
+	xml_node_t *node, *ret_node;
+	int res;
+
+	wpa_printf(MSG_INFO, "Client certificate enrollment");
+
+	res = osu_get_certificate(ctx, cmd);
+	if (res < 0)
+		wpa_printf(MSG_INFO, "EST simpleEnroll failed");
+
+	node = build_spp_post_dev_data(ctx, &ns, session_id,
+				       res == 0 ?
+				       "Certificate enrollment completed" :
+				       "Certificate enrollment failed");
+	if (node == NULL)
+		return NULL;
+
+	ret_node = soap_send_receive(ctx->http, node);
+	if (ret_node == NULL)
+		return NULL;
+
+	debug_dump_node(ctx, "Received response to certificate enrollment "
+			"completed", ret_node);
+
+	if (hs20_spp_validate(ctx, ret_node, "sppPostDevDataResponse") < 0) {
+		wpa_printf(MSG_INFO, "SPP validation failed");
+		xml_node_free(ctx->xml, ret_node);
+		return NULL;
+	}
+
+	return ret_node;
+}
+
+
+static int hs20_spp_exec(struct hs20_osu_client *ctx, xml_node_t *exec,
+			 const char *session_id, const char *pps_fname,
+			 xml_node_t *pps, xml_node_t **ret_node)
+{
+	xml_node_t *cmd;
+	const char *name;
+	char *uri;
+	char *id = strdup(session_id);
+
+	if (id == NULL)
+		return -1;
+
+	*ret_node = NULL;
+
+	debug_dump_node(ctx, "exec", exec);
+
+	xml_node_for_each_child(ctx->xml, cmd, exec) {
+		xml_node_for_each_check(ctx->xml, cmd);
+		break;
+	}
+	if (!cmd) {
+		wpa_printf(MSG_INFO, "exec command element not found (cmd=%p)",
+			   cmd);
+		free(id);
+		return -1;
+	}
+
+	name = xml_node_get_localname(ctx->xml, cmd);
+
+	if (strcasecmp(name, "launchBrowserToURI") == 0) {
+		int res;
+		uri = xml_node_get_text(ctx->xml, cmd);
+		if (!uri) {
+			wpa_printf(MSG_INFO, "No URI found");
+			free(id);
+			return -1;
+		}
+		wpa_printf(MSG_INFO, "Launch browser to URI '%s'", uri);
+		write_summary(ctx, "Launch browser to URI '%s'", uri);
+		res = hs20_web_browser(uri);
+		xml_node_get_text_free(ctx->xml, uri);
+		if (res > 0) {
+			wpa_printf(MSG_INFO, "User response in browser completed successfully - sessionid='%s'",
+				   id);
+			write_summary(ctx, "User response in browser completed successfully");
+			*ret_node = hs20_spp_user_input_completed(ctx, id);
+			free(id);
+			return *ret_node ? 0 : -1;
+		} else {
+			wpa_printf(MSG_INFO, "Failed to receive user response");
+			write_summary(ctx, "Failed to receive user response");
+			hs20_spp_update_response(
+				ctx, id, "Error occurred", "Other");
+			free(id);
+			return -1;
+		}
+		return 0;
+	}
+
+	if (strcasecmp(name, "uploadMO") == 0) {
+		if (pps_fname == NULL)
+			return -1;
+		*ret_node = hs20_spp_upload_mo(ctx, cmd, id,
+					       pps_fname);
+		free(id);
+		return *ret_node ? 0 : -1;
+	}
+
+	if (strcasecmp(name, "getCertificate") == 0) {
+		*ret_node = hs20_spp_get_certificate(ctx, cmd, id,
+						     pps_fname);
+		free(id);
+		return *ret_node ? 0 : -1;
+	}
+
+	wpa_printf(MSG_INFO, "Unsupported exec command: '%s'", name);
+	free(id);
+	return -1;
+}
+
+
+enum spp_post_dev_data_use {
+	SPP_SUBSCRIPTION_REMEDIATION,
+	SPP_POLICY_UPDATE,
+	SPP_SUBSCRIPTION_REGISTRATION,
+};
+
+static void process_spp_post_dev_data_response(
+	struct hs20_osu_client *ctx,
+	enum spp_post_dev_data_use use, xml_node_t *node,
+	const char *pps_fname, xml_node_t *pps)
+{
+	xml_node_t *child;
+	char *status = NULL;
+	xml_node_t *update = NULL, *exec = NULL, *add_mo = NULL, *no_mo = NULL;
+	char *session_id = NULL;
+
+	debug_dump_node(ctx, "sppPostDevDataResponse node", node);
+
+	status = get_spp_attr_value(ctx->xml, node, "sppStatus");
+	if (status == NULL) {
+		wpa_printf(MSG_INFO, "No sppStatus attribute");
+		goto out;
+	}
+	write_summary(ctx, "Received sppPostDevDataResponse sppStatus='%s'",
+		      status);
+
+	session_id = get_spp_attr_value(ctx->xml, node, "sessionID");
+	if (session_id == NULL) {
+		wpa_printf(MSG_INFO, "No sessionID attribute");
+		goto out;
+	}
+
+	wpa_printf(MSG_INFO, "[hs20] sppPostDevDataResponse - sppStatus: '%s'  sessionID: '%s'",
+		   status, session_id);
+
+	xml_node_for_each_child(ctx->xml, child, node) {
+		const char *name;
+		xml_node_for_each_check(ctx->xml, child);
+		debug_dump_node(ctx, "child", child);
+		name = xml_node_get_localname(ctx->xml, child);
+		wpa_printf(MSG_INFO, "localname: '%s'", name);
+		if (!update && strcasecmp(name, "updateNode") == 0)
+			update = child;
+		if (!exec && strcasecmp(name, "exec") == 0)
+			exec = child;
+		if (!add_mo && strcasecmp(name, "addMO") == 0)
+			add_mo = child;
+		if (!no_mo && strcasecmp(name, "noMOUpdate") == 0)
+			no_mo = child;
+	}
+
+	if (use == SPP_SUBSCRIPTION_REMEDIATION &&
+	    strcasecmp(status,
+		       "Remediation complete, request sppUpdateResponse") == 0)
+	{
+		int res, ret;
+		if (!update && !no_mo) {
+			wpa_printf(MSG_INFO, "No updateNode or noMOUpdate element");
+			goto out;
+		}
+		wpa_printf(MSG_INFO, "Subscription remediation completed");
+		res = update_pps(ctx, update, pps_fname, pps);
+		if (res < 0)
+			wpa_printf(MSG_INFO, "Failed to update PPS MO");
+		ret = hs20_spp_update_response(
+			ctx, session_id,
+			res < 0 ? "Error occurred" : "OK",
+			res < 0 ? "MO addition or update failed" : NULL);
+		if (res == 0 && ret == 0)
+			hs20_sub_rem_complete(ctx, pps_fname);
+		goto out;
+	}
+
+	if (use == SPP_SUBSCRIPTION_REMEDIATION &&
+	    strcasecmp(status, "Exchange complete, release TLS connection") ==
+	    0) {
+		if (!no_mo) {
+			wpa_printf(MSG_INFO, "No noMOUpdate element");
+			goto out;
+		}
+		wpa_printf(MSG_INFO, "Subscription remediation completed (no MO update)");
+		goto out;
+	}
+
+	if (use == SPP_POLICY_UPDATE &&
+	    strcasecmp(status, "Update complete, request sppUpdateResponse") ==
+	    0) {
+		int res, ret;
+		wpa_printf(MSG_INFO, "Policy update received - update PPS");
+		res = update_pps(ctx, update, pps_fname, pps);
+		ret = hs20_spp_update_response(
+			ctx, session_id,
+			res < 0 ? "Error occurred" : "OK",
+			res < 0 ? "MO addition or update failed" : NULL);
+		if (res == 0 && ret == 0)
+			hs20_policy_update_complete(ctx, pps_fname);
+		goto out;
+	}
+
+	if (use == SPP_SUBSCRIPTION_REGISTRATION &&
+	    strcasecmp(status, "Provisioning complete, request "
+		       "sppUpdateResponse")  == 0) {
+		if (!add_mo) {
+			wpa_printf(MSG_INFO, "No addMO element - not sure what to do next");
+			goto out;
+		}
+		process_spp_user_input_response(ctx, session_id, add_mo);
+		node = NULL;
+		goto out;
+	}
+
+	if (strcasecmp(status, "No update available at this time") == 0) {
+		wpa_printf(MSG_INFO, "No update available at this time");
+		goto out;
+	}
+
+	if (strcasecmp(status, "OK") == 0) {
+		int res;
+		xml_node_t *ret;
+
+		if (!exec) {
+			wpa_printf(MSG_INFO, "No exec element - not sure what to do next");
+			goto out;
+		}
+		res = hs20_spp_exec(ctx, exec, session_id,
+				    pps_fname, pps, &ret);
+		/* xml_node_free(ctx->xml, node); */
+		node = NULL;
+		if (res == 0 && ret)
+			process_spp_post_dev_data_response(ctx, use,
+							   ret, pps_fname, pps);
+		goto out;
+	}
+
+	if (strcasecmp(status, "Error occurred") == 0) {
+		xml_node_t *err;
+		char *code = NULL;
+		err = get_node(ctx->xml, node, "sppError");
+		if (err)
+			code = xml_node_get_attr_value(ctx->xml, err,
+						       "errorCode");
+		wpa_printf(MSG_INFO, "Error occurred - errorCode=%s",
+			   code ? code : "N/A");
+		xml_node_get_attr_value_free(ctx->xml, code);
+		goto out;
+	}
+
+	wpa_printf(MSG_INFO,
+		   "[hs20] Unsupported sppPostDevDataResponse sppStatus '%s'",
+		   status);
+out:
+	xml_node_get_attr_value_free(ctx->xml, status);
+	xml_node_get_attr_value_free(ctx->xml, session_id);
+	xml_node_free(ctx->xml, node);
+}
+
+
+static int spp_post_dev_data(struct hs20_osu_client *ctx,
+			     enum spp_post_dev_data_use use,
+			     const char *reason,
+			     const char *pps_fname, xml_node_t *pps)
+{
+	xml_node_t *payload;
+	xml_node_t *ret_node;
+
+	payload = build_spp_post_dev_data(ctx, NULL, NULL, reason);
+	if (payload == NULL)
+		return -1;
+
+	ret_node = soap_send_receive(ctx->http, payload);
+	if (!ret_node) {
+		const char *err = http_get_err(ctx->http);
+		if (err) {
+			wpa_printf(MSG_INFO, "HTTP error: %s", err);
+			write_result(ctx, "HTTP error: %s", err);
+		} else {
+			write_summary(ctx, "Failed to send SOAP message");
+		}
+		return -1;
+	}
+
+	if (hs20_spp_validate(ctx, ret_node, "sppPostDevDataResponse") < 0) {
+		wpa_printf(MSG_INFO, "SPP validation failed");
+		xml_node_free(ctx->xml, ret_node);
+		return -1;
+	}
+
+	process_spp_post_dev_data_response(ctx, use, ret_node,
+					   pps_fname, pps);
+	return 0;
+}
+
+
+void spp_sub_rem(struct hs20_osu_client *ctx, const char *address,
+		 const char *pps_fname, const char *ca_fname,
+		 const char *client_cert, const char *client_key,
+		 const char *cred_username, const char *cred_password,
+		 xml_node_t *pps)
+{
+	wpa_printf(MSG_INFO, "SPP subscription remediation");
+	write_summary(ctx, "SPP subscription remediation");
+
+	os_free(ctx->server_url);
+	ctx->server_url = os_strdup(address);
+
+	if (soap_init_client(ctx->http, address, ca_fname,
+			     cred_username, cred_password, client_cert,
+			     client_key) == 0) {
+		spp_post_dev_data(ctx, SPP_SUBSCRIPTION_REMEDIATION,
+				  "Subscription remediation", pps_fname, pps);
+	}
+}
+
+
+static void hs20_policy_update_complete(struct hs20_osu_client *ctx,
+					const char *pps_fname)
+{
+	wpa_printf(MSG_INFO, "Policy update completed");
+
+	/*
+	 * Update wpa_supplicant credentials and reconnect using updated
+	 * information.
+	 */
+	wpa_printf(MSG_INFO, "Updating wpa_supplicant credentials");
+	cmd_set_pps(ctx, pps_fname);
+
+	wpa_printf(MSG_INFO, "Requesting reconnection with updated configuration");
+	if (wpa_command(ctx->ifname, "INTERWORKING_SELECT auto") < 0)
+		wpa_printf(MSG_ERROR, "Failed to request wpa_supplicant to reconnect");
+}
+
+
+static int process_spp_exchange_complete(struct hs20_osu_client *ctx,
+					 xml_node_t *node)
+{
+	char *status, *session_id;
+
+	debug_dump_node(ctx, "sppExchangeComplete", node);
+
+	status = get_spp_attr_value(ctx->xml, node, "sppStatus");
+	if (status == NULL) {
+		wpa_printf(MSG_INFO, "No sppStatus attribute");
+		return -1;
+	}
+	write_summary(ctx, "Received sppExchangeComplete sppStatus='%s'",
+		      status);
+
+	session_id = get_spp_attr_value(ctx->xml, node, "sessionID");
+	if (session_id == NULL) {
+		wpa_printf(MSG_INFO, "No sessionID attribute");
+		xml_node_get_attr_value_free(ctx->xml, status);
+		return -1;
+	}
+
+	wpa_printf(MSG_INFO, "[hs20] sppStatus: '%s'  sessionID: '%s'",
+		   status, session_id);
+	xml_node_get_attr_value_free(ctx->xml, session_id);
+
+	if (strcasecmp(status, "Exchange complete, release TLS connection") ==
+	    0) {
+		xml_node_get_attr_value_free(ctx->xml, status);
+		return 0;
+	}
+
+	wpa_printf(MSG_INFO, "Unexpected sppStatus '%s'", status);
+	write_summary(ctx, "Unexpected sppStatus '%s'", status);
+	xml_node_get_attr_value_free(ctx->xml, status);
+	return -1;
+}
+
+
+static xml_node_t * build_spp_update_response(struct hs20_osu_client *ctx,
+					      const char *session_id,
+					      const char *spp_status,
+					      const char *error_code)
+{
+	xml_namespace_t *ns;
+	xml_node_t *spp_node, *node;
+
+	spp_node = xml_node_create_root(ctx->xml, SPP_NS_URI, "spp", &ns,
+					"sppUpdateResponse");
+	if (spp_node == NULL)
+		return NULL;
+
+	xml_node_add_attr(ctx->xml, spp_node, ns, "sppVersion", "1.0");
+	xml_node_add_attr(ctx->xml, spp_node, ns, "sessionID", session_id);
+	xml_node_add_attr(ctx->xml, spp_node, ns, "sppStatus", spp_status);
+
+	if (error_code) {
+		node = xml_node_create(ctx->xml, spp_node, ns, "sppError");
+		if (node)
+			xml_node_add_attr(ctx->xml, node, NULL, "errorCode",
+					  error_code);
+	}
+
+	return spp_node;
+}
+
+
+static int hs20_spp_update_response(struct hs20_osu_client *ctx,
+				    const char *session_id,
+				    const char *spp_status,
+				    const char *error_code)
+{
+	xml_node_t *node, *ret_node;
+	int ret;
+
+	write_summary(ctx, "Building sppUpdateResponse sppStatus='%s' error_code='%s'",
+		      spp_status, error_code);
+	node = build_spp_update_response(ctx, session_id, spp_status,
+					 error_code);
+	if (node == NULL)
+		return -1;
+	ret_node = soap_send_receive(ctx->http, node);
+	if (!ret_node) {
+		if (soap_reinit_client(ctx->http) < 0)
+			return -1;
+		wpa_printf(MSG_INFO, "Try to finish with re-opened connection");
+		node = build_spp_update_response(ctx, session_id, spp_status,
+						 error_code);
+		if (node == NULL)
+			return -1;
+		ret_node = soap_send_receive(ctx->http, node);
+		if (ret_node == NULL)
+			return -1;
+		wpa_printf(MSG_INFO, "Continue with new connection");
+	}
+
+	if (hs20_spp_validate(ctx, ret_node, "sppExchangeComplete") < 0) {
+		wpa_printf(MSG_INFO, "SPP validation failed");
+		xml_node_free(ctx->xml, ret_node);
+		return -1;
+	}
+
+	ret = process_spp_exchange_complete(ctx, ret_node);
+	xml_node_free(ctx->xml, ret_node);
+	return ret;
+}
+
+
+void spp_pol_upd(struct hs20_osu_client *ctx, const char *address,
+		 const char *pps_fname, const char *ca_fname,
+		 const char *client_cert, const char *client_key,
+		 const char *cred_username, const char *cred_password,
+		 xml_node_t *pps)
+{
+	wpa_printf(MSG_INFO, "SPP policy update");
+	write_summary(ctx, "SPP policy update");
+
+	os_free(ctx->server_url);
+	ctx->server_url = os_strdup(address);
+
+	if (soap_init_client(ctx->http, address, ca_fname, cred_username,
+			     cred_password, client_cert, client_key) == 0) {
+		spp_post_dev_data(ctx, SPP_POLICY_UPDATE, "Policy update",
+				  pps_fname, pps);
+	}
+}
+
+
+int cmd_prov(struct hs20_osu_client *ctx, const char *url,
+	     const char *ca_fname)
+{
+	unlink("Cert/est_cert.der");
+	unlink("Cert/est_cert.pem");
+
+	ctx->ca_fname = ca_fname;
+
+	if (url == NULL) {
+		wpa_printf(MSG_INFO, "Invalid prov command (missing URL)");
+		return -1;
+	}
+
+	wpa_printf(MSG_INFO, "Credential provisioning requested");
+
+	os_free(ctx->server_url);
+	ctx->server_url = os_strdup(url);
+
+	if (soap_init_client(ctx->http, url, ca_fname, NULL, NULL, NULL, NULL) <
+	    0)
+		return -1;
+	spp_post_dev_data(ctx, SPP_SUBSCRIPTION_REGISTRATION,
+			  "Subscription registration", NULL, NULL);
+
+	return ctx->pps_cred_set ? 0 : -1;
+}
+
+
+int cmd_sim_prov(struct hs20_osu_client *ctx, const char *url,
+	     const char *ca_fname)
+{
+	ctx->ca_fname = ca_fname;
+
+	if (url == NULL) {
+		wpa_printf(MSG_INFO, "Invalid prov command (missing URL)");
+		return -1;
+	}
+
+	wpa_printf(MSG_INFO, "SIM provisioning requested");
+
+	os_free(ctx->server_url);
+	ctx->server_url = os_strdup(url);
+
+	wpa_printf(MSG_INFO, "Wait for IP address before starting SIM provisioning");
+
+	if (wait_ip_addr(ctx->ifname, 15) < 0) {
+		wpa_printf(MSG_INFO, "Could not get IP address for WLAN - try connection anyway");
+	}
+
+	if (soap_init_client(ctx->http, url, ca_fname, NULL, NULL, NULL, NULL) <
+	    0)
+		return -1;
+	spp_post_dev_data(ctx, SPP_SUBSCRIPTION_REGISTRATION,
+			  "Subscription provisioning", NULL, NULL);
+
+	return ctx->pps_cred_set ? 0 : -1;
+}