blob: 194518e5aba71c25a8ec7d95776f4d444eee726e [file] [log] [blame]
/*
* 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, 1);
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;
}
}
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;
}