hostapd/hs20/client/oma_dm_client.c
Jouni Malinen 40bdceac88 HS 2.0R2: Configure OSU client trust root more consistently
Some of the code paths could have ended up ignoring CA file name from
command line due to overly complex way of setting ctx->ca_fname.
Configure this more consistently in osu_client.c as soon as the CA file
name has been determined.

Signed-off-by: Jouni Malinen <jouni@qca.qualcomm.com>
2014-03-18 00:39:39 +02:00

1370 lines
35 KiB
C

/*
* Hotspot 2.0 - OMA DM client
* Copyright (c) 2013-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 "common.h"
#include "wpa_helpers.h"
#include "xml-utils.h"
#include "http-utils.h"
#include "utils/browser.h"
#include "osu_client.h"
#define DM_SERVER_INITIATED_MGMT 1200
#define DM_CLIENT_INITIATED_MGMT 1201
#define DM_GENERIC_ALERT 1226
/* OMA-TS-SyncML-RepPro-V1_2_2 - 10. Response Status Codes */
#define DM_RESP_OK 200
#define DM_RESP_AUTH_ACCEPTED 212
#define DM_RESP_CHUNKED_ITEM_ACCEPTED 213
#define DM_RESP_NOT_EXECUTED 215
#define DM_RESP_ATOMIC_ROLL_BACK_OK 216
#define DM_RESP_NOT_MODIFIED 304
#define DM_RESP_BAD_REQUEST 400
#define DM_RESP_UNAUTHORIZED 401
#define DM_RESP_FORBIDDEN 403
#define DM_RESP_NOT_FOUND 404
#define DM_RESP_COMMAND_NOT_ALLOWED 405
#define DM_RESP_OPTIONAL_FEATURE_NOT_SUPPORTED 406
#define DM_RESP_MISSING_CREDENTIALS 407
#define DM_RESP_CONFLICT 409
#define DM_RESP_GONE 410
#define DM_RESP_INCOMPLETE_COMMAND 412
#define DM_RESP_REQ_ENTITY_TOO_LARGE 413
#define DM_RESP_URI_TOO_LONG 414
#define DM_RESP_UNSUPPORTED_MEDIA_TYPE_OR_FORMAT 415
#define DM_RESP_REQ_TOO_BIG 416
#define DM_RESP_ALREADY_EXISTS 418
#define DM_RESP_DEVICE_FULL 420
#define DM_RESP_SIZE_MISMATCH 424
#define DM_RESP_PERMISSION_DENIED 425
#define DM_RESP_COMMAND_FAILED 500
#define DM_RESP_COMMAND_NOT_IMPLEMENTED 501
#define DM_RESP_ATOMIC_ROLL_BACK_FAILED 516
#define DM_HS20_SUBSCRIPTION_CREATION \
"org.wi-fi.hotspot2dot0.SubscriptionCreation"
#define DM_HS20_SUBSCRIPTION_PROVISIONING \
"org.wi-fi.hotspot2dot0.SubscriptionProvisioning"
#define DM_HS20_SUBSCRIPTION_REMEDIATION \
"org.wi-fi.hotspot2dot0.SubscriptionRemediation"
#define DM_HS20_POLICY_UPDATE \
"org.wi-fi.hotspot2dot0.PolicyUpdate"
#define DM_URI_PPS "./Wi-Fi/org.wi-fi/PerProviderSubscription"
#define DM_URI_LAUNCH_BROWSER \
"./DevDetail/Ext/org.wi-fi/Wi-Fi/Ops/launchBrowserToURI"
static void add_item(struct hs20_osu_client *ctx, xml_node_t *parent,
const char *locuri, const char *data);
static const char * int2str(int val)
{
static char buf[20];
snprintf(buf, sizeof(buf), "%d", val);
return buf;
}
static char * oma_dm_get_target_locuri(struct hs20_osu_client *ctx,
xml_node_t *node)
{
xml_node_t *locuri;
char *uri, *ret = NULL;
locuri = get_node(ctx->xml, node, "Item/Target/LocURI");
if (locuri == NULL)
return NULL;
uri = xml_node_get_text(ctx->xml, locuri);
if (uri)
ret = os_strdup(uri);
xml_node_get_text_free(ctx->xml, uri);
return ret;
}
static void oma_dm_add_locuri(struct hs20_osu_client *ctx, xml_node_t *parent,
const char *element, const char *uri)
{
xml_node_t *node;
node = xml_node_create(ctx->xml, parent, NULL, element);
if (node == NULL)
return;
xml_node_create_text(ctx->xml, node, NULL, "LocURI", uri);
}
static xml_node_t * oma_dm_build_hdr(struct hs20_osu_client *ctx,
const char *url, int msgid)
{
xml_node_t *syncml, *synchdr;
xml_namespace_t *ns;
syncml = xml_node_create_root(ctx->xml, "SYNCML:SYNCML1.2", NULL, &ns,
"SyncML");
synchdr = xml_node_create(ctx->xml, syncml, NULL, "SyncHdr");
xml_node_create_text(ctx->xml, synchdr, NULL, "VerDTD", "1.2");
xml_node_create_text(ctx->xml, synchdr, NULL, "VerProto", "DM/1.2");
xml_node_create_text(ctx->xml, synchdr, NULL, "SessionID", "1");
xml_node_create_text(ctx->xml, synchdr, NULL, "MsgID", int2str(msgid));
oma_dm_add_locuri(ctx, synchdr, "Target", url);
oma_dm_add_locuri(ctx, synchdr, "Source", ctx->devid);
return syncml;
}
static void oma_dm_add_cmdid(struct hs20_osu_client *ctx, xml_node_t *parent,
int cmdid)
{
xml_node_create_text(ctx->xml, parent, NULL, "CmdID", int2str(cmdid));
}
static xml_node_t * add_alert(struct hs20_osu_client *ctx, xml_node_t *parent,
int cmdid, int data)
{
xml_node_t *node;
node = xml_node_create(ctx->xml, parent, NULL, "Alert");
if (node == NULL)
return NULL;
oma_dm_add_cmdid(ctx, node, cmdid);
xml_node_create_text(ctx->xml, node, NULL, "Data", int2str(data));
return node;
}
static xml_node_t * add_status(struct hs20_osu_client *ctx, xml_node_t *parent,
int msgref, int cmdref, int cmdid,
const char *cmd, int data, const char *targetref)
{
xml_node_t *node;
node = xml_node_create(ctx->xml, parent, NULL, "Status");
if (node == NULL)
return NULL;
oma_dm_add_cmdid(ctx, node, cmdid);
xml_node_create_text(ctx->xml, node, NULL, "MsgRef", int2str(msgref));
if (cmdref)
xml_node_create_text(ctx->xml, node, NULL, "CmdRef",
int2str(cmdref));
xml_node_create_text(ctx->xml, node, NULL, "Cmd", cmd);
xml_node_create_text(ctx->xml, node, NULL, "Data", int2str(data));
if (targetref) {
xml_node_create_text(ctx->xml, node, NULL, "TargetRef",
targetref);
}
return node;
}
static xml_node_t * add_results(struct hs20_osu_client *ctx, xml_node_t *parent,
int msgref, int cmdref, int cmdid,
const char *locuri, const char *data)
{
xml_node_t *node;
node = xml_node_create(ctx->xml, parent, NULL, "Results");
if (node == NULL)
return NULL;
oma_dm_add_cmdid(ctx, node, cmdid);
xml_node_create_text(ctx->xml, node, NULL, "MsgRef", int2str(msgref));
xml_node_create_text(ctx->xml, node, NULL, "CmdRef", int2str(cmdref));
add_item(ctx, node, locuri, data);
return node;
}
static char * mo_str(struct hs20_osu_client *ctx, const char *urn,
const char *fname)
{
xml_node_t *fnode, *tnds;
char *str;
fnode = node_from_file(ctx->xml, fname);
if (!fnode)
return NULL;
tnds = mo_to_tnds(ctx->xml, fnode, 0, urn, "syncml:dmddf1.2");
xml_node_free(ctx->xml, fnode);
if (!tnds)
return NULL;
str = xml_node_to_str(ctx->xml, tnds);
xml_node_free(ctx->xml, tnds);
if (str == NULL)
return NULL;
wpa_printf(MSG_INFO, "MgmtTree: %s", str);
return str;
}
static void add_item(struct hs20_osu_client *ctx, xml_node_t *parent,
const char *locuri, const char *data)
{
xml_node_t *item, *node;
item = xml_node_create(ctx->xml, parent, NULL, "Item");
oma_dm_add_locuri(ctx, item, "Source", locuri);
node = xml_node_create(ctx->xml, item, NULL, "Meta");
xml_node_create_text_ns(ctx->xml, node, "syncml:metinf", "Format",
"Chr");
xml_node_create_text_ns(ctx->xml, node, "syncml:metinf", "Type",
"text/plain");
xml_node_create_text(ctx->xml, item, NULL, "Data", data);
}
static void add_replace_devinfo(struct hs20_osu_client *ctx, xml_node_t *parent,
int cmdid)
{
xml_node_t *info, *child, *replace;
const char *name;
char locuri[200], *txt;
info = node_from_file(ctx->xml, "devinfo.xml");
if (info == NULL) {
wpa_printf(MSG_INFO, "Could not read devinfo.xml");
return;
}
replace = xml_node_create(ctx->xml, parent, NULL, "Replace");
if (replace == NULL) {
xml_node_free(ctx->xml, info);
return;
}
oma_dm_add_cmdid(ctx, replace, cmdid);
xml_node_for_each_child(ctx->xml, child, info) {
xml_node_for_each_check(ctx->xml, child);
name = xml_node_get_localname(ctx->xml, child);
os_snprintf(locuri, sizeof(locuri), "./DevInfo/%s", name);
txt = xml_node_get_text(ctx->xml, child);
if (txt) {
add_item(ctx, replace, locuri, txt);
xml_node_get_text_free(ctx->xml, txt);
}
}
xml_node_free(ctx->xml, info);
}
static void oma_dm_add_hs20_generic_alert(struct hs20_osu_client *ctx,
xml_node_t *syncbody,
int cmdid, const char *oper,
const char *data)
{
xml_node_t *node, *item;
char buf[200];
node = add_alert(ctx, syncbody, cmdid, DM_GENERIC_ALERT);
item = xml_node_create(ctx->xml, node, NULL, "Item");
oma_dm_add_locuri(ctx, item, "Source", DM_URI_PPS);
node = xml_node_create(ctx->xml, item, NULL, "Meta");
snprintf(buf, sizeof(buf), "Reversed-Domain-Name: %s", oper);
xml_node_create_text_ns(ctx->xml, node, "syncml:metinf", "Type", buf);
xml_node_create_text_ns(ctx->xml, node, "syncml:metinf", "Format",
"xml");
xml_node_create_text(ctx->xml, item, NULL, "Data", data);
}
static xml_node_t * build_oma_dm_1(struct hs20_osu_client *ctx,
const char *url, int msgid, const char *oper)
{
xml_node_t *syncml, *syncbody;
char *str;
int cmdid = 0;
syncml = oma_dm_build_hdr(ctx, url, msgid);
if (syncml == NULL)
return NULL;
syncbody = xml_node_create(ctx->xml, syncml, NULL, "SyncBody");
if (syncbody == NULL) {
xml_node_free(ctx->xml, syncml);
return NULL;
}
cmdid++;
add_alert(ctx, syncbody, cmdid, DM_CLIENT_INITIATED_MGMT);
str = mo_str(ctx, NULL, "devdetail.xml");
if (str == NULL) {
xml_node_free(ctx->xml, syncml);
return NULL;
}
cmdid++;
oma_dm_add_hs20_generic_alert(ctx, syncbody, cmdid, oper, str);
os_free(str);
cmdid++;
add_replace_devinfo(ctx, syncbody, cmdid);
xml_node_create(ctx->xml, syncbody, NULL, "Final");
return syncml;
}
static xml_node_t * build_oma_dm_1_sub_reg(struct hs20_osu_client *ctx,
const char *url, int msgid)
{
xml_node_t *syncml;
syncml = build_oma_dm_1(ctx, url, msgid, DM_HS20_SUBSCRIPTION_CREATION);
if (syncml)
debug_dump_node(ctx, "OMA-DM Package 1 (sub reg)", syncml);
return syncml;
}
static xml_node_t * build_oma_dm_1_sub_prov(struct hs20_osu_client *ctx,
const char *url, int msgid)
{
xml_node_t *syncml;
syncml = build_oma_dm_1(ctx, url, msgid,
DM_HS20_SUBSCRIPTION_PROVISIONING);
if (syncml)
debug_dump_node(ctx, "OMA-DM Package 1 (sub prov)", syncml);
return syncml;
}
static xml_node_t * build_oma_dm_1_pol_upd(struct hs20_osu_client *ctx,
const char *url, int msgid)
{
xml_node_t *syncml;
syncml = build_oma_dm_1(ctx, url, msgid, DM_HS20_POLICY_UPDATE);
if (syncml)
debug_dump_node(ctx, "OMA-DM Package 1 (pol upd)", syncml);
return syncml;
}
static xml_node_t * build_oma_dm_1_sub_rem(struct hs20_osu_client *ctx,
const char *url, int msgid)
{
xml_node_t *syncml;
syncml = build_oma_dm_1(ctx, url, msgid,
DM_HS20_SUBSCRIPTION_REMEDIATION);
if (syncml)
debug_dump_node(ctx, "OMA-DM Package 1 (sub rem)", syncml);
return syncml;
}
static int oma_dm_exec_browser(struct hs20_osu_client *ctx, xml_node_t *exec)
{
xml_node_t *node;
char *data;
int res;
node = get_node(ctx->xml, exec, "Item/Data");
if (node == NULL) {
wpa_printf(MSG_INFO, "No Data node found");
return DM_RESP_BAD_REQUEST;
}
data = xml_node_get_text(ctx->xml, node);
wpa_printf(MSG_INFO, "Data: %s", data);
wpa_printf(MSG_INFO, "Launch browser to URI '%s'", data);
write_summary(ctx, "Launch browser to URI '%s'", data);
res = hs20_web_browser(data);
xml_node_get_text_free(ctx->xml, data);
if (res > 0) {
wpa_printf(MSG_INFO, "User response in browser completed successfully");
write_summary(ctx, "User response in browser completed successfully");
return DM_RESP_OK;
} else {
wpa_printf(MSG_INFO, "Failed to receive user response");
write_summary(ctx, "Failed to receive user response");
return DM_RESP_COMMAND_FAILED;
}
}
static int oma_dm_exec_get_cert(struct hs20_osu_client *ctx, xml_node_t *exec)
{
xml_node_t *node, *getcert;
char *data;
const char *name;
int res;
wpa_printf(MSG_INFO, "Client certificate enrollment");
write_summary(ctx, "Client certificate enrollment");
node = get_node(ctx->xml, exec, "Item/Data");
if (node == NULL) {
wpa_printf(MSG_INFO, "No Data node found");
return DM_RESP_BAD_REQUEST;
}
data = xml_node_get_text(ctx->xml, node);
wpa_printf(MSG_INFO, "Data: %s", data);
getcert = xml_node_from_buf(ctx->xml, data);
xml_node_get_text_free(ctx->xml, data);
if (getcert == NULL) {
wpa_printf(MSG_INFO, "Could not parse Item/Data node contents");
return DM_RESP_BAD_REQUEST;
}
debug_dump_node(ctx, "OMA-DM getCertificate", getcert);
name = xml_node_get_localname(ctx->xml, getcert);
if (name == NULL || os_strcasecmp(name, "getCertificate") != 0) {
wpa_printf(MSG_INFO, "Unexpected getCertificate node name '%s'",
name);
return DM_RESP_BAD_REQUEST;
}
res = osu_get_certificate(ctx, getcert);
xml_node_free(ctx->xml, getcert);
return res == 0 ? DM_RESP_OK : DM_RESP_COMMAND_FAILED;
}
static int oma_dm_exec(struct hs20_osu_client *ctx, xml_node_t *exec)
{
char *locuri;
int ret;
locuri = oma_dm_get_target_locuri(ctx, exec);
if (locuri == NULL) {
wpa_printf(MSG_INFO, "No Target LocURI node found");
return DM_RESP_BAD_REQUEST;
}
wpa_printf(MSG_INFO, "Target LocURI: %s", locuri);
if (os_strcasecmp(locuri, "./DevDetail/Ext/org.wi-fi/Wi-Fi/Ops/"
"launchBrowserToURI") == 0) {
ret = oma_dm_exec_browser(ctx, exec);
} else if (os_strcasecmp(locuri, "./DevDetail/Ext/org.wi-fi/Wi-Fi/Ops/"
"getCertificate") == 0) {
ret = oma_dm_exec_get_cert(ctx, exec);
} else {
wpa_printf(MSG_INFO, "Unsupported exec Target LocURI");
ret = DM_RESP_NOT_FOUND;
}
os_free(locuri);
return ret;
}
static int oma_dm_run_add(struct hs20_osu_client *ctx, const char *locuri,
xml_node_t *add, xml_node_t *pps,
const char *pps_fname)
{
const char *pos;
size_t fqdn_len;
xml_node_t *node, *tnds, *unode, *pps_node;
char *data, *uri, *upos, *end;
int use_tnds = 0;
size_t uri_len;
wpa_printf(MSG_INFO, "Add command target LocURI: %s", locuri);
if (os_strncasecmp(locuri, "./Wi-Fi/", 8) != 0) {
wpa_printf(MSG_INFO, "Do not allow Add outside ./Wi-Fi");
return DM_RESP_PERMISSION_DENIED;
}
pos = locuri + 8;
if (ctx->fqdn == NULL)
return DM_RESP_COMMAND_FAILED;
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 Add outside ./Wi-Fi/%s",
ctx->fqdn);
return DM_RESP_PERMISSION_DENIED;
}
pos += fqdn_len + 1;
if (os_strncasecmp(pos, "PerProviderSubscription/", 24) != 0) {
wpa_printf(MSG_INFO,
"Do not allow Add outside ./Wi-Fi/%s/PerProviderSubscription",
ctx->fqdn);
return DM_RESP_PERMISSION_DENIED;
}
pos += 24;
wpa_printf(MSG_INFO, "Add command for PPS node %s", pos);
pps_node = get_node(ctx->xml, pps, pos);
if (pps_node) {
wpa_printf(MSG_INFO, "Specified PPS node exists already");
return DM_RESP_ALREADY_EXISTS;
}
uri = os_strdup(pos);
if (uri == NULL)
return DM_RESP_COMMAND_FAILED;
while (!pps_node) {
upos = os_strrchr(uri, '/');
if (!upos)
break;
upos[0] = '\0';
pps_node = get_node(ctx->xml, pps, uri);
wpa_printf(MSG_INFO, "Node %s %s", uri,
pps_node ? "exists" : "does not exist");
}
wpa_printf(MSG_INFO, "Parent URI: %s", uri);
if (!pps_node) {
/* Add at root of PPS MO */
pps_node = pps;
}
uri_len = os_strlen(uri);
os_strlcpy(uri, pos + uri_len, os_strlen(pos));
upos = uri;
while (*upos == '/')
upos++;
wpa_printf(MSG_INFO, "Nodes to add: %s", upos);
for (;;) {
end = os_strchr(upos, '/');
if (!end)
break;
*end = '\0';
wpa_printf(MSG_INFO, "Adding interim node %s", upos);
pps_node = xml_node_create(ctx->xml, pps_node, NULL, upos);
if (pps_node == NULL) {
os_free(uri);
return DM_RESP_COMMAND_FAILED;
}
upos = end + 1;
}
wpa_printf(MSG_INFO, "Adding node %s", upos);
node = get_node(ctx->xml, add, "Item/Meta/Type");
if (node) {
char *type;
type = xml_node_get_text(ctx->xml, node);
use_tnds = node &&
os_strstr(type, "application/vnd.syncml.dmtnds+xml");
}
node = get_node(ctx->xml, add, "Item/Data");
if (node == NULL) {
wpa_printf(MSG_INFO, "No Add/Item/Data found");
os_free(uri);
return DM_RESP_BAD_REQUEST;
}
data = xml_node_get_text(ctx->xml, node);
if (data == NULL) {
wpa_printf(MSG_INFO, "Could not get Add/Item/Data text");
os_free(uri);
return DM_RESP_BAD_REQUEST;
}
wpa_printf(MSG_DEBUG, "Add/Item/Data: %s", data);
if (use_tnds) {
tnds = xml_node_from_buf(ctx->xml, data);
xml_node_get_text_free(ctx->xml, data);
if (tnds == NULL) {
wpa_printf(MSG_INFO,
"Could not parse Add/Item/Data text");
os_free(uri);
return DM_RESP_BAD_REQUEST;
}
unode = tnds_to_mo(ctx->xml, tnds);
xml_node_free(ctx->xml, tnds);
if (unode == NULL) {
wpa_printf(MSG_INFO, "Could not parse TNDS text");
os_free(uri);
return DM_RESP_BAD_REQUEST;
}
debug_dump_node(ctx, "Parsed TNDS", unode);
xml_node_add_child(ctx->xml, pps_node, unode);
} else {
/* TODO: What to do here? */
os_free(uri);
return DM_RESP_BAD_REQUEST;
}
os_free(uri);
if (update_pps_file(ctx, pps_fname, pps) < 0)
return DM_RESP_COMMAND_FAILED;
ctx->pps_updated = 1;
return DM_RESP_OK;
}
static int oma_dm_add(struct hs20_osu_client *ctx, xml_node_t *add,
xml_node_t *pps, const char *pps_fname)
{
xml_node_t *node;
char *locuri;
char fname[300];
int ret;
node = get_node(ctx->xml, add, "Item/Target/LocURI");
if (node == NULL) {
wpa_printf(MSG_INFO, "No Target LocURI node found");
return DM_RESP_BAD_REQUEST;
}
locuri = xml_node_get_text(ctx->xml, node);
wpa_printf(MSG_INFO, "Target LocURI: %s", locuri);
if (os_strncasecmp(locuri, "./Wi-Fi/", 8) != 0) {
wpa_printf(MSG_INFO, "Unsupported Add Target LocURI");
xml_node_get_text_free(ctx->xml, locuri);
return DM_RESP_PERMISSION_DENIED;
}
node = get_node(ctx->xml, add, "Item/Data");
if (node == NULL) {
wpa_printf(MSG_INFO, "No Data node found");
xml_node_get_text_free(ctx->xml, locuri);
return DM_RESP_BAD_REQUEST;
}
if (pps_fname && os_file_exists(pps_fname)) {
ret = oma_dm_run_add(ctx, locuri, add, pps, pps_fname);
if (ret != DM_RESP_OK) {
xml_node_get_text_free(ctx->xml, locuri);
return ret;
}
ret = 0;
os_strlcpy(fname, pps_fname, sizeof(fname));
} else
ret = hs20_add_pps_mo(ctx, locuri, node, fname, sizeof(fname));
xml_node_get_text_free(ctx->xml, locuri);
if (ret < 0)
return ret == -2 ? DM_RESP_ALREADY_EXISTS :
DM_RESP_COMMAND_FAILED;
if (ctx->no_reconnect == 2) {
os_snprintf(ctx->pps_fname, sizeof(ctx->pps_fname), "%s",
fname);
ctx->pps_cred_set = 1;
return DM_RESP_OK;
}
wpa_printf(MSG_INFO, "Updating wpa_supplicant credentials");
cmd_set_pps(ctx, fname);
if (ctx->no_reconnect)
return DM_RESP_OK;
wpa_printf(MSG_INFO, "Requesting reconnection with updated configuration");
if (wpa_command(ctx->ifname, "INTERWORKING_SELECT auto") < 0)
wpa_printf(MSG_INFO, "Failed to request wpa_supplicant to reconnect");
return DM_RESP_OK;
}
static int oma_dm_replace(struct hs20_osu_client *ctx, xml_node_t *replace,
xml_node_t *pps, const char *pps_fname)
{
char *locuri, *pos;
size_t fqdn_len;
xml_node_t *node, *tnds, *unode, *pps_node, *parent;
char *data;
int use_tnds = 0;
locuri = oma_dm_get_target_locuri(ctx, replace);
if (locuri == NULL)
return DM_RESP_BAD_REQUEST;
wpa_printf(MSG_INFO, "Replace command target LocURI: %s", locuri);
if (os_strncasecmp(locuri, "./Wi-Fi/", 8) != 0) {
wpa_printf(MSG_INFO, "Do not allow Replace outside ./Wi-Fi");
os_free(locuri);
return DM_RESP_PERMISSION_DENIED;
}
pos = locuri + 8;
if (ctx->fqdn == NULL) {
os_free(locuri);
return DM_RESP_COMMAND_FAILED;
}
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 Replace outside ./Wi-Fi/%s",
ctx->fqdn);
os_free(locuri);
return DM_RESP_PERMISSION_DENIED;
}
pos += fqdn_len + 1;
if (os_strncasecmp(pos, "PerProviderSubscription/", 24) != 0) {
wpa_printf(MSG_INFO,
"Do not allow Replace outside ./Wi-Fi/%s/PerProviderSubscription",
ctx->fqdn);
os_free(locuri);
return DM_RESP_PERMISSION_DENIED;
}
pos += 24;
wpa_printf(MSG_INFO, "Replace command for PPS node %s", pos);
pps_node = get_node(ctx->xml, pps, pos);
if (pps_node == NULL) {
wpa_printf(MSG_INFO, "Specified PPS node not found");
os_free(locuri);
return DM_RESP_NOT_FOUND;
}
node = get_node(ctx->xml, replace, "Item/Meta/Type");
if (node) {
char *type;
type = xml_node_get_text(ctx->xml, node);
use_tnds = node &&
os_strstr(type, "application/vnd.syncml.dmtnds+xml");
}
node = get_node(ctx->xml, replace, "Item/Data");
if (node == NULL) {
wpa_printf(MSG_INFO, "No Replace/Item/Data found");
os_free(locuri);
return DM_RESP_BAD_REQUEST;
}
data = xml_node_get_text(ctx->xml, node);
if (data == NULL) {
wpa_printf(MSG_INFO, "Could not get Replace/Item/Data text");
os_free(locuri);
return DM_RESP_BAD_REQUEST;
}
wpa_printf(MSG_DEBUG, "Replace/Item/Data: %s", data);
if (use_tnds) {
tnds = xml_node_from_buf(ctx->xml, data);
xml_node_get_text_free(ctx->xml, data);
if (tnds == NULL) {
wpa_printf(MSG_INFO,
"Could not parse Replace/Item/Data text");
os_free(locuri);
return DM_RESP_BAD_REQUEST;
}
unode = tnds_to_mo(ctx->xml, tnds);
xml_node_free(ctx->xml, tnds);
if (unode == NULL) {
wpa_printf(MSG_INFO, "Could not parse TNDS text");
os_free(locuri);
return DM_RESP_BAD_REQUEST;
}
debug_dump_node(ctx, "Parsed TNDS", unode);
parent = xml_node_get_parent(ctx->xml, pps_node);
xml_node_detach(ctx->xml, pps_node);
xml_node_add_child(ctx->xml, parent, unode);
} else {
xml_node_set_text(ctx->xml, pps_node, data);
xml_node_get_text_free(ctx->xml, data);
}
os_free(locuri);
if (update_pps_file(ctx, pps_fname, pps) < 0)
return DM_RESP_COMMAND_FAILED;
ctx->pps_updated = 1;
return DM_RESP_OK;
}
static int oma_dm_get(struct hs20_osu_client *ctx, xml_node_t *get,
xml_node_t *pps, const char *pps_fname, char **value)
{
char *locuri, *pos;
size_t fqdn_len;
xml_node_t *pps_node;
const char *name;
*value = NULL;
locuri = oma_dm_get_target_locuri(ctx, get);
if (locuri == NULL)
return DM_RESP_BAD_REQUEST;
wpa_printf(MSG_INFO, "Get command target LocURI: %s", locuri);
if (os_strncasecmp(locuri, "./Wi-Fi/", 8) != 0) {
wpa_printf(MSG_INFO, "Do not allow Get outside ./Wi-Fi");
os_free(locuri);
return DM_RESP_PERMISSION_DENIED;
}
pos = locuri + 8;
if (ctx->fqdn == NULL)
return DM_RESP_COMMAND_FAILED;
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 Get outside ./Wi-Fi/%s",
ctx->fqdn);
os_free(locuri);
return DM_RESP_PERMISSION_DENIED;
}
pos += fqdn_len + 1;
if (os_strncasecmp(pos, "PerProviderSubscription/", 24) != 0) {
wpa_printf(MSG_INFO,
"Do not allow Get outside ./Wi-Fi/%s/PerProviderSubscription",
ctx->fqdn);
os_free(locuri);
return DM_RESP_PERMISSION_DENIED;
}
pos += 24;
wpa_printf(MSG_INFO, "Get command for PPS node %s", pos);
pps_node = get_node(ctx->xml, pps, pos);
if (pps_node == NULL) {
wpa_printf(MSG_INFO, "Specified PPS node not found");
os_free(locuri);
return DM_RESP_NOT_FOUND;
}
name = xml_node_get_localname(ctx->xml, pps_node);
wpa_printf(MSG_INFO, "Get command returned node with name '%s'", name);
if (os_strcasecmp(name, "Password") == 0) {
wpa_printf(MSG_INFO, "Do not allow Get for Password node");
os_free(locuri);
return DM_RESP_PERMISSION_DENIED;
}
/*
* TODO: No support for DMTNDS, so if interior node, reply with a
* list of children node names in Results element. The child list type is
* defined in [DMTND].
*/
*value = xml_node_get_text(ctx->xml, pps_node);
if (*value == NULL)
return DM_RESP_COMMAND_FAILED;
return DM_RESP_OK;
}
static int oma_dm_get_cmdid(struct hs20_osu_client *ctx, xml_node_t *node)
{
xml_node_t *cnode;
char *str;
int ret;
cnode = get_node(ctx->xml, node, "CmdID");
if (cnode == NULL)
return 0;
str = xml_node_get_text(ctx->xml, cnode);
if (str == NULL)
return 0;
ret = atoi(str);
xml_node_get_text_free(ctx->xml, str);
return ret;
}
static xml_node_t * oma_dm_send_recv(struct hs20_osu_client *ctx,
const char *url, xml_node_t *syncml,
const char *ext_hdr,
const char *username, const char *password,
const char *client_cert,
const char *client_key)
{
xml_node_t *resp;
char *str, *res;
char *resp_uri = NULL;
str = xml_node_to_str(ctx->xml, syncml);
xml_node_free(ctx->xml, syncml);
if (str == NULL)
return NULL;
wpa_printf(MSG_INFO, "Send OMA DM Package");
write_summary(ctx, "Send OMA DM Package");
os_free(ctx->server_url);
ctx->server_url = os_strdup(url);
res = http_post(ctx->http, url, str, "application/vnd.syncml.dm+xml",
ext_hdr, ctx->ca_fname, username, password,
client_cert, client_key, NULL);
os_free(str);
os_free(resp_uri);
resp_uri = NULL;
if (res == NULL) {
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 OMA DM Package");
}
return NULL;
}
wpa_printf(MSG_DEBUG, "Server response: %s", res);
wpa_printf(MSG_INFO, "Process OMA DM Package");
write_summary(ctx, "Process received OMA DM Package");
resp = xml_node_from_buf(ctx->xml, res);
os_free(res);
if (resp == NULL) {
wpa_printf(MSG_INFO, "Failed to parse OMA DM response");
return NULL;
}
debug_dump_node(ctx, "OMA DM Package", resp);
return resp;
}
static xml_node_t * oma_dm_process(struct hs20_osu_client *ctx, const char *url,
xml_node_t *resp, int msgid,
char **ret_resp_uri,
xml_node_t *pps, const char *pps_fname)
{
xml_node_t *syncml, *syncbody, *hdr, *body, *child;
const char *name;
char *resp_uri = NULL;
int server_msgid = 0;
int cmdid = 0;
int server_cmdid;
int resp_needed = 0;
char *tmp;
int final = 0;
char *locuri;
*ret_resp_uri = NULL;
name = xml_node_get_localname(ctx->xml, resp);
if (name == NULL || os_strcasecmp(name, "SyncML") != 0) {
wpa_printf(MSG_INFO, "SyncML node not found");
return NULL;
}
hdr = get_node(ctx->xml, resp, "SyncHdr");
body = get_node(ctx->xml, resp, "SyncBody");
if (hdr == NULL || body == NULL) {
wpa_printf(MSG_INFO, "Could not find SyncHdr or SyncBody");
return NULL;
}
xml_node_for_each_child(ctx->xml, child, hdr) {
xml_node_for_each_check(ctx->xml, child);
name = xml_node_get_localname(ctx->xml, child);
wpa_printf(MSG_INFO, "SyncHdr %s", name);
if (os_strcasecmp(name, "RespURI") == 0) {
tmp = xml_node_get_text(ctx->xml, child);
if (tmp)
resp_uri = os_strdup(tmp);
xml_node_get_text_free(ctx->xml, tmp);
} else if (os_strcasecmp(name, "MsgID") == 0) {
tmp = xml_node_get_text(ctx->xml, child);
if (tmp)
server_msgid = atoi(tmp);
xml_node_get_text_free(ctx->xml, tmp);
}
}
wpa_printf(MSG_INFO, "Server MsgID: %d", server_msgid);
if (resp_uri)
wpa_printf(MSG_INFO, "RespURI: %s", resp_uri);
syncml = oma_dm_build_hdr(ctx, resp_uri ? resp_uri : url, msgid);
if (syncml == NULL) {
os_free(resp_uri);
return NULL;
}
syncbody = xml_node_create(ctx->xml, syncml, NULL, "SyncBody");
cmdid++;
add_status(ctx, syncbody, server_msgid, 0, cmdid, "SyncHdr",
DM_RESP_AUTH_ACCEPTED, NULL);
xml_node_for_each_child(ctx->xml, child, body) {
xml_node_for_each_check(ctx->xml, child);
server_cmdid = oma_dm_get_cmdid(ctx, child);
name = xml_node_get_localname(ctx->xml, child);
wpa_printf(MSG_INFO, "SyncBody CmdID=%d - %s",
server_cmdid, name);
if (os_strcasecmp(name, "Exec") == 0) {
int res = oma_dm_exec(ctx, child);
cmdid++;
locuri = oma_dm_get_target_locuri(ctx, child);
if (locuri == NULL)
res = DM_RESP_BAD_REQUEST;
add_status(ctx, syncbody, server_msgid, server_cmdid,
cmdid, name, res, locuri);
os_free(locuri);
resp_needed = 1;
} else if (os_strcasecmp(name, "Add") == 0) {
int res = oma_dm_add(ctx, child, pps, pps_fname);
cmdid++;
locuri = oma_dm_get_target_locuri(ctx, child);
if (locuri == NULL)
res = DM_RESP_BAD_REQUEST;
add_status(ctx, syncbody, server_msgid, server_cmdid,
cmdid, name, res, locuri);
os_free(locuri);
resp_needed = 1;
} else if (os_strcasecmp(name, "Replace") == 0) {
int res;
res = oma_dm_replace(ctx, child, pps, pps_fname);
cmdid++;
locuri = oma_dm_get_target_locuri(ctx, child);
if (locuri == NULL)
res = DM_RESP_BAD_REQUEST;
add_status(ctx, syncbody, server_msgid, server_cmdid,
cmdid, name, res, locuri);
os_free(locuri);
resp_needed = 1;
} else if (os_strcasecmp(name, "Status") == 0) {
/* TODO: Verify success */
} else if (os_strcasecmp(name, "Get") == 0) {
int res;
char *value;
res = oma_dm_get(ctx, child, pps, pps_fname, &value);
cmdid++;
locuri = oma_dm_get_target_locuri(ctx, child);
if (locuri == NULL)
res = DM_RESP_BAD_REQUEST;
add_status(ctx, syncbody, server_msgid, server_cmdid,
cmdid, name, res, locuri);
if (res == DM_RESP_OK && value) {
cmdid++;
add_results(ctx, syncbody, server_msgid,
server_cmdid, cmdid, locuri, value);
}
os_free(locuri);
xml_node_get_text_free(ctx->xml, value);
resp_needed = 1;
#if 0 /* TODO: MUST support */
} else if (os_strcasecmp(name, "Delete") == 0) {
#endif
#if 0 /* TODO: MUST support */
} else if (os_strcasecmp(name, "Sequence") == 0) {
#endif
} else if (os_strcasecmp(name, "Final") == 0) {
final = 1;
break;
} else {
locuri = oma_dm_get_target_locuri(ctx, child);
add_status(ctx, syncbody, server_msgid, server_cmdid,
cmdid, name, DM_RESP_COMMAND_NOT_IMPLEMENTED,
locuri);
os_free(locuri);
resp_needed = 1;
}
}
if (!final) {
wpa_printf(MSG_INFO, "Final node not found");
xml_node_free(ctx->xml, syncml);
os_free(resp_uri);
return NULL;
}
if (!resp_needed) {
wpa_printf(MSG_INFO, "Exchange completed - no response needed");
xml_node_free(ctx->xml, syncml);
os_free(resp_uri);
return NULL;
}
xml_node_create(ctx->xml, syncbody, NULL, "Final");
debug_dump_node(ctx, "OMA-DM Package 3", syncml);
*ret_resp_uri = resp_uri;
return syncml;
}
int cmd_oma_dm_prov(struct hs20_osu_client *ctx, const char *url)
{
xml_node_t *syncml, *resp;
char *resp_uri = NULL;
int msgid = 0;
if (url == NULL) {
wpa_printf(MSG_INFO, "Invalid prov command (missing URL)");
return -1;
}
wpa_printf(MSG_INFO, "OMA-DM credential provisioning requested");
write_summary(ctx, "OMA-DM credential provisioning");
msgid++;
syncml = build_oma_dm_1_sub_reg(ctx, url, msgid);
if (syncml == NULL)
return -1;
while (syncml) {
resp = oma_dm_send_recv(ctx, resp_uri ? resp_uri : url,
syncml, NULL, NULL, NULL, NULL, NULL);
if (resp == NULL)
return -1;
msgid++;
syncml = oma_dm_process(ctx, url, resp, msgid, &resp_uri,
NULL, NULL);
xml_node_free(ctx->xml, resp);
}
os_free(resp_uri);
return ctx->pps_cred_set ? 0 : -1;
}
int cmd_oma_dm_sim_prov(struct hs20_osu_client *ctx, const char *url)
{
xml_node_t *syncml, *resp;
char *resp_uri = NULL;
int msgid = 0;
if (url == NULL) {
wpa_printf(MSG_INFO, "Invalid prov command (missing URL)");
return -1;
}
wpa_printf(MSG_INFO, "OMA-DM SIM provisioning requested");
ctx->no_reconnect = 2;
wpa_printf(MSG_INFO, "Wait for IP address before starting SIM provisioning");
write_summary(ctx, "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");
}
write_summary(ctx, "OMA-DM SIM provisioning");
msgid++;
syncml = build_oma_dm_1_sub_prov(ctx, url, msgid);
if (syncml == NULL)
return -1;
while (syncml) {
resp = oma_dm_send_recv(ctx, resp_uri ? resp_uri : url,
syncml, NULL, NULL, NULL, NULL, NULL);
if (resp == NULL)
return -1;
msgid++;
syncml = oma_dm_process(ctx, url, resp, msgid, &resp_uri,
NULL, NULL);
xml_node_free(ctx->xml, resp);
}
os_free(resp_uri);
if (ctx->pps_cred_set) {
wpa_printf(MSG_INFO, "Updating wpa_supplicant credentials");
cmd_set_pps(ctx, ctx->pps_fname);
wpa_printf(MSG_INFO, "Requesting reconnection with updated configuration");
write_summary(ctx, "Requesting reconnection with updated configuration");
if (wpa_command(ctx->ifname, "INTERWORKING_SELECT auto") < 0) {
wpa_printf(MSG_INFO, "Failed to request wpa_supplicant to reconnect");
write_summary(ctx, "Failed to request wpa_supplicant to reconnect");
return -1;
}
}
return ctx->pps_cred_set ? 0 : -1;
}
void oma_dm_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)
{
xml_node_t *syncml, *resp;
char *resp_uri = NULL;
int msgid = 0;
wpa_printf(MSG_INFO, "OMA-DM policy update");
write_summary(ctx, "OMA-DM policy update");
msgid++;
syncml = build_oma_dm_1_pol_upd(ctx, address, msgid);
if (syncml == NULL)
return;
while (syncml) {
resp = oma_dm_send_recv(ctx, resp_uri ? resp_uri : address,
syncml, NULL, cred_username,
cred_password, client_cert, client_key);
if (resp == NULL)
return;
msgid++;
syncml = oma_dm_process(ctx, address, resp, msgid, &resp_uri,
pps, pps_fname);
xml_node_free(ctx->xml, resp);
}
os_free(resp_uri);
if (ctx->pps_updated) {
wpa_printf(MSG_INFO, "Update wpa_supplicant credential based on updated PPS MO");
write_summary(ctx, "Update wpa_supplicant credential based on updated PPS MO and request connection");
cmd_set_pps(ctx, pps_fname);
if (wpa_command(ctx->ifname, "INTERWORKING_SELECT auto") < 0) {
wpa_printf(MSG_INFO,
"Failed to request wpa_supplicant to reconnect");
write_summary(ctx,
"Failed to request wpa_supplicant to reconnect");
}
}
}
void oma_dm_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)
{
xml_node_t *syncml, *resp;
char *resp_uri = NULL;
int msgid = 0;
wpa_printf(MSG_INFO, "OMA-DM subscription remediation");
write_summary(ctx, "OMA-DM subscription remediation");
msgid++;
syncml = build_oma_dm_1_sub_rem(ctx, address, msgid);
if (syncml == NULL)
return;
while (syncml) {
resp = oma_dm_send_recv(ctx, resp_uri ? resp_uri : address,
syncml, NULL, cred_username,
cred_password, client_cert, client_key);
if (resp == NULL)
return;
msgid++;
syncml = oma_dm_process(ctx, address, resp, msgid, &resp_uri,
pps, pps_fname);
xml_node_free(ctx->xml, resp);
}
os_free(resp_uri);
wpa_printf(MSG_INFO, "Update wpa_supplicant credential based on updated PPS MO and request reconnection");
write_summary(ctx, "Update wpa_supplicant credential based on updated PPS MO and request reconnection");
cmd_set_pps(ctx, pps_fname);
if (wpa_command(ctx->ifname, "INTERWORKING_SELECT auto") < 0) {
wpa_printf(MSG_INFO, "Failed to request wpa_supplicant to reconnect");
write_summary(ctx, "Failed to request wpa_supplicant to reconnect");
}
}
void cmd_oma_dm_add(struct hs20_osu_client *ctx, const char *pps_fname,
const char *add_fname)
{
xml_node_t *pps, *add;
int res;
ctx->fqdn = os_strdup("wi-fi.org");
pps = node_from_file(ctx->xml, pps_fname);
if (pps == NULL) {
wpa_printf(MSG_INFO, "PPS file %s could not be parsed",
pps_fname);
return;
}
add = node_from_file(ctx->xml, add_fname);
if (add == NULL) {
wpa_printf(MSG_INFO, "Add file %s could not be parsed",
add_fname);
xml_node_free(ctx->xml, pps);
return;
}
res = oma_dm_add(ctx, add, pps, pps_fname);
wpa_printf(MSG_INFO, "oma_dm_add --> %d", res);
xml_node_free(ctx->xml, pps);
xml_node_free(ctx->xml, add);
}
void cmd_oma_dm_replace(struct hs20_osu_client *ctx, const char *pps_fname,
const char *replace_fname)
{
xml_node_t *pps, *replace;
int res;
ctx->fqdn = os_strdup("wi-fi.org");
pps = node_from_file(ctx->xml, pps_fname);
if (pps == NULL) {
wpa_printf(MSG_INFO, "PPS file %s could not be parsed",
pps_fname);
return;
}
replace = node_from_file(ctx->xml, replace_fname);
if (replace == NULL) {
wpa_printf(MSG_INFO, "Replace file %s could not be parsed",
replace_fname);
xml_node_free(ctx->xml, pps);
return;
}
res = oma_dm_replace(ctx, replace, pps, pps_fname);
wpa_printf(MSG_INFO, "oma_dm_replace --> %d", res);
xml_node_free(ctx->xml, pps);
xml_node_free(ctx->xml, replace);
}