996 lines
26 KiB
C
996 lines
26 KiB
C
/*
|
|
* 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;
|
|
}
|