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