/* * 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; }