Merged the hostap_2.6 updates, and the Leap of Faith work, from the hostap_update...
[mech_eap.git] / libeap / hs20 / client / spp_client.c
diff --git a/libeap/hs20/client/spp_client.c b/libeap/hs20/client/spp_client.c
new file mode 100644 (file)
index 0000000..c619541
--- /dev/null
@@ -0,0 +1,1004 @@
+/*
+ * 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"
+
+
+extern const char *spp_xsd_fname;
+
+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_fname, &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;
+
+       errno = 0;
+       fnode = node_from_file(ctx, fname);
+       if (!fnode) {
+               wpa_printf(MSG_ERROR,
+                          "Failed to create XML node from file: %s, possible error: %s",
+                          fname, strerror(errno));
+               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 - URL: %s ca_fname: %s",
+                  url, ctx->ca_fname ? ctx->ca_fname : "N/A");
+
+       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;
+}