aboutsummaryrefslogtreecommitdiff
path: root/hs20/client/spp_client.c
diff options
context:
space:
mode:
Diffstat (limited to 'hs20/client/spp_client.c')
-rw-r--r--hs20/client/spp_client.c995
1 files changed, 995 insertions, 0 deletions
diff --git a/hs20/client/spp_client.c b/hs20/client/spp_client.c
new file mode 100644
index 000000000000..302a05040df6
--- /dev/null
+++ b/hs20/client/spp_client.c
@@ -0,0 +1,995 @@
+/*
+ * Hotspot 2.0 SPP client
+ * Copyright (c) 2012-2014, 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 *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, ctx->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 *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, ctx->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)
+{
+ unlink("Cert/est_cert.der");
+ unlink("Cert/est_cert.pem");
+
+ 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, ctx->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)
+{
+ 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, ctx->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;
+}