/*
* Hotspot 2.0 SPP server
* Copyright (c) 2012-2013, Qualcomm Atheros, Inc.
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <errno.h>
#include <sqlite3.h>
#include "common.h"
#include "base64.h"
#include "md5_i.h"
#include "xml-utils.h"
#include "spp_server.h"
#define SPP_NS_URI "http://www.wi-fi.org/specifications/hotspot2dot0/v1.0/spp"
#define URN_OMA_DM_DEVINFO "urn:oma:mo:oma-dm-devinfo:1.0"
#define URN_OMA_DM_DEVDETAIL "urn:oma:mo:oma-dm-devdetail:1.0"
#define URN_OMA_DM_DMACC "urn:oma:mo:oma-dm-dmacc:1.0"
#define URN_HS20_PPS "urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0"
/* TODO: timeout to expire sessions */
enum hs20_session_operation {
NO_OPERATION,
UPDATE_PASSWORD,
CONTINUE_SUBSCRIPTION_REMEDIATION,
CONTINUE_POLICY_UPDATE,
USER_REMEDIATION,
SUBSCRIPTION_REGISTRATION,
POLICY_REMEDIATION,
POLICY_UPDATE,
FREE_REMEDIATION,
};
static char * db_get_session_val(struct hs20_svc *ctx, const char *user,
const char *realm, const char *session_id,
const char *field);
static char * db_get_osu_config_val(struct hs20_svc *ctx, const char *realm,
const char *field);
static xml_node_t * build_policy(struct hs20_svc *ctx, const char *user,
const char *realm, int use_dmacc);
static int db_add_session(struct hs20_svc *ctx,
const char *user, const char *realm,
const char *sessionid, const char *pw,
const char *redirect_uri,
enum hs20_session_operation operation)
{
char *sql;
int ret = 0;
sql = sqlite3_mprintf("INSERT INTO sessions(timestamp,id,user,realm,"
"operation,password,redirect_uri) "
"VALUES "
"(strftime('%%Y-%%m-%%d %%H:%%M:%%f','now'),"
"%Q,%Q,%Q,%d,%Q,%Q)",
sessionid, user ? user : "", realm ? realm : "",
operation, pw ? pw : "",
redirect_uri ? redirect_uri : "");
if (sql == NULL)
return -1;
debug_print(ctx, 1, "DB: %s", sql);
if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) {
debug_print(ctx, 1, "Failed to add session entry into sqlite "
"database: %s", sqlite3_errmsg(ctx->db));
ret = -1;
}
sqlite3_free(sql);
return ret;
}
static void db_update_session_password(struct hs20_svc *ctx, const char *user,
const char *realm, const char *sessionid,
const char *pw)
{
char *sql;
sql = sqlite3_mprintf("UPDATE sessions SET password=%Q WHERE id=%Q AND "
"user=%Q AND realm=%Q",
pw, sessionid, user, realm);
if (sql == NULL)
return;
debug_print(ctx, 1, "DB: %s", sql);
if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) {
debug_print(ctx, 1, "Failed to update session password: %s",
sqlite3_errmsg(ctx->db));
}
sqlite3_free(sql);
}
static void db_update_session_machine_managed(struct hs20_svc *ctx,
const char *user,
const char *realm,
const char *sessionid,
const int pw_mm)
{
char *sql;
sql = sqlite3_mprintf("UPDATE sessions SET machine_managed=%Q WHERE id=%Q AND user=%Q AND realm=%Q",
pw_mm ? "1" : "0", sessionid, user, realm);
if (sql == NULL)
return;
debug_print(ctx, 1, "DB: %s", sql);
if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) {
debug_print(ctx, 1,
"Failed to update session machine_managed: %s",
sqlite3_errmsg(ctx->db));
}
sqlite3_free(sql);
}
static void db_add_session_pps(struct hs20_svc *ctx, const char *user,
const char *realm, const char *sessionid,
xml_node_t *node)
{
char *str;
char *sql;
str = xml_node_to_str(ctx->xml, node);
if (str == NULL)
return;
sql = sqlite3_mprintf("UPDATE sessions SET pps=%Q WHERE id=%Q AND "
"user=%Q AND realm=%Q",
str, sessionid, user, realm);
free(str);
if (sql == NULL)
return;
debug_print(ctx, 1, "DB: %s", sql);
if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) {
debug_print(ctx, 1, "Failed to add session pps: %s",
sqlite3_errmsg(ctx->db));
}
sqlite3_free(sql);
}
static void db_add_session_devinfo(struct hs20_svc *ctx, const char *sessionid,
xml_node_t *node)
{
char *str;
char *sql;
str = xml_node_to_str(ctx->xml, node);
if (str == NULL)
return;
sql = sqlite3_mprintf("UPDATE sessions SET devinfo=%Q WHERE id=%Q",
str, sessionid);
free(str);
if (sql == NULL)
return;
debug_print(ctx, 1, "DB: %s", sql);
if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) {
debug_print(ctx, 1, "Failed to add session devinfo: %s",
sqlite3_errmsg(ctx->db));
}
sqlite3_free(sql);
}
static void db_add_session_devdetail(struct hs20_svc *ctx,
const char *sessionid,
xml_node_t *node)
{
char *str;
char *sql;
str = xml_node_to_str(ctx->xml, node);
if (str == NULL)
return;
sql = sqlite3_mprintf("UPDATE sessions SET devdetail=%Q WHERE id=%Q",
str, sessionid);
free(str);
if (sql == NULL)
return;
debug_print(ctx, 1, "DB: %s", sql);
if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) {
debug_print(ctx, 1, "Failed to add session devdetail: %s",
sqlite3_errmsg(ctx->db));
}
sqlite3_free(sql);
}
static void db_remove_session(struct hs20_svc *ctx,
const char *user, const char *realm,
const char *sessionid)
{
char *sql;
if (user == NULL || realm == NULL) {
sql = sqlite3_mprintf("DELETE FROM sessions WHERE "
"id=%Q", sessionid);
} else {
sql = sqlite3_mprintf("DELETE FROM sessions WHERE "
"user=%Q AND realm=%Q AND id=%Q",
user, realm, sessionid);
}
if (sql == NULL)
return;
debug_print(ctx, 1, "DB: %s", sql);
if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) {
debug_print(ctx, 1, "Failed to delete session entry from "
"sqlite database: %s", sqlite3_errmsg(ctx->db));
}
sqlite3_free(sql);
}
static void hs20_eventlog(struct hs20_svc *ctx,
const char *user, const char *realm,
const char *sessionid, const char *notes,
const char *dump)
{
char *sql;
char *user_buf = NULL, *realm_buf = NULL;
debug_print(ctx, 1, "eventlog: %s", notes);
if (user == NULL) {
user_buf = db_get_session_val(ctx, NULL, NULL, sessionid,
"user");
user = user_buf;
realm_buf = db_get_session_val(ctx, NULL, NULL, sessionid,
"realm");
realm = realm_buf;
}
sql = sqlite3_mprintf("INSERT INTO eventlog"
"(user,realm,sessionid,timestamp,notes,dump,addr)"
" VALUES (%Q,%Q,%Q,"
"strftime('%%Y-%%m-%%d %%H:%%M:%%f','now'),"
"%Q,%Q,%Q)",
user, realm, sessionid, notes,
dump ? dump : "", ctx->addr ? ctx->addr : "");
free(user_buf);
free(realm_buf);
if (sql == NULL)
return;
if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) {
debug_print(ctx, 1, "Failed to add eventlog entry into sqlite "
"database: %s", sqlite3_errmsg(ctx->db));
}
sqlite3_free(sql);
}
static void hs20_eventlog_node(struct hs20_svc *ctx,
const char *user, const char *realm,
const char *sessionid, const char *notes,
xml_node_t *node)
{
char *str;
if (node)
str = xml_node_to_str(ctx->xml, node);
else
str = NULL;
hs20_eventlog(ctx, user, realm, sessionid, notes, str);
free(str);
}
static void db_update_mo_str(struct hs20_svc *ctx, const char *user,
const char *realm, const char *name,
const char *str)
{
char *sql;
if (user == NULL || realm == NULL || name == NULL)
return;
sql = sqlite3_mprintf("UPDATE users SET %s=%Q "
"WHERE identity=%Q AND realm=%Q AND phase2=1",
name, str, user, realm);
if (sql == NULL)
return;
debug_print(ctx, 1, "DB: %s", sql);
if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) {
debug_print(ctx, 1, "Failed to update user MO entry in sqlite "
"database: %s", sqlite3_errmsg(ctx->db));
}
sqlite3_free(sql);
}
static void db_update_mo(struct hs20_svc *ctx, const char *user,
const char *realm, const char *name, xml_node_t *mo)
{
char *str;
str = xml_node_to_str(ctx->xml, mo);
if (str == NULL)
return;
db_update_mo_str(ctx, user, realm, name, str);
free(str);
}
static void add_text_node(struct hs20_svc *ctx, xml_node_t *parent,
const char *name, const char *value)
{
xml_node_create_text(ctx->xml, parent, NULL, name, value ? value : "");
}
static void add_text_node_conf(struct hs20_svc *ctx, const char *realm,
xml_node_t *parent, const char *name,
const char *field)
{
char *val;
val = db_get_osu_config_val(ctx, realm, field);
xml_node_create_text(ctx->xml, parent, NULL, name, val ? val : "");
os_free(val);
}
static int new_password(char *buf, int buflen)
{
int i;
if (buflen < 1)
return -1;
buf[buflen - 1] = '\0';
if (os_get_random((unsigned char *) buf, buflen - 1) < 0)
return -1;
for (i = 0; i < buflen - 1; i++) {
unsigned char val = buf[i];
val %= 2 * 26 + 10;
if (val < 26)
buf[i] = 'a' + val;
else if (val < 2 * 26)
buf[i] = 'A' + val - 26;
else
buf[i] = '0' + val - 2 * 26;
}
return 0;
}
struct get_db_field_data {
const char *field;
char *value;
};
static int get_db_field(void *ctx, int argc, char *argv[], char *col[])
{
struct get_db_field_data *data = ctx;
int i;
for (i = 0; i < argc; i++) {
if (os_strcmp(col[i], data->field) == 0 && argv[i]) {
os_free(data->value);
data->value = os_strdup(argv[i]);
break;
}
}
return 0;
}
static char * db_get_val(struct hs20_svc *ctx, const char *user,
const char *realm, const char *field, int dmacc)
{
char *cmd;
struct get_db_field_data data;
cmd = sqlite3_mprintf("SELECT %s FROM users WHERE "
"%s=%Q AND realm=%Q AND phase2=1",
field, dmacc ? "osu_user" : "identity",
user, realm);
if (cmd == NULL)
return NULL;
memset(&data, 0, sizeof(data));
data.field = field;
if (sqlite3_exec(ctx->db, cmd, get_db_field, &data, NULL) != SQLITE_OK)
{
debug_print(ctx, 1, "Could not find user '%s'", user);
sqlite3_free(cmd);
return NULL;
}
sqlite3_free(cmd);
debug_print(ctx, 1, "DB: user='%s' realm='%s' field='%s' dmacc=%d --> "
"value='%s'", user, realm, field, dmacc, data.value);
return data.value;
}
static int db_update_val(struct hs20_svc *ctx, const char *user,
const char *realm, const char *field,
const char *val, int dmacc)
{
char *cmd;
int ret;
cmd = sqlite3_mprintf("UPDATE users SET %s=%Q WHERE "
"%s=%Q AND realm=%Q AND phase2=1",
field, val, dmacc ? "osu_user" : "identity", user,
realm);
if (cmd == NULL)
return -1;
debug_print(ctx, 1, "DB: %s", cmd);
if (sqlite3_exec(ctx->db, cmd, NULL, NULL, NULL) != SQLITE_OK) {
debug_print(ctx, 1,
"Failed to update user in sqlite database: %s",
sqlite3_errmsg(ctx->db));
ret = -1;
} else {
debug_print(ctx, 1,
"DB: user='%s' realm='%s' field='%s' set to '%s'",
user, realm, field, val);
ret = 0;
}
sqlite3_free(cmd);
return ret;
}
static char * db_get_session_val(struct hs20_svc *ctx, const char *user,
const char *realm, const char *session_id,
const char *field)
{
char *cmd;
struct get_db_field_data data;
if (user == NULL || realm == NULL) {
cmd = sqlite3_mprintf("SELECT %s FROM sessions WHERE "
"id=%Q", field, session_id);
} else {
cmd = sqlite3_mprintf("SELECT %s FROM sessions WHERE "
"user=%Q AND realm=%Q AND id=%Q",
field, user, realm, session_id);
}
if (cmd == NULL)
return NULL;
debug_print(ctx, 1, "DB: %s", cmd);
memset(&data, 0, sizeof(data));
data.field = field;
if (sqlite3_exec(ctx->db, cmd, get_db_field, &data, NULL) != SQLITE_OK)
{
debug_print(ctx, 1, "DB: Could not find session %s: %s",
session_id, sqlite3_errmsg(ctx->db));
sqlite3_free(cmd);
return NULL;
}
sqlite3_free(cmd);
debug_print(ctx, 1, "DB: return '%s'", data.value);
return data.value;
}
static int update_password(struct hs20_svc *ctx, const char *user,
const char *realm, const char *pw, int dmacc)
{
char *cmd;
cmd = sqlite3_mprintf("UPDATE users SET password=%Q, "
"remediation='' "
"WHERE %s=%Q AND phase2=1",
pw, dmacc ? "osu_user" : "identity",
user);
if (cmd == NULL)
return -1;
debug_print(ctx, 1, "DB: %s", cmd);
if (sqlite3_exec(ctx->db, cmd, NULL, NULL, NULL) != SQLITE_OK) {
debug_print(ctx, 1, "Failed to update database for user '%s'",
user);
}
sqlite3_free(cmd);
return 0;
}
static int add_eap_ttls(struct hs20_svc *ctx, xml_node_t *parent)
{
xml_node_t *node;
node = xml_node_create(ctx->xml, parent, NULL, "EAPMethod");
if (node == NULL)
return -1;
add_text_node(ctx, node, "EAPType", "21");
add_text_node(ctx, node, "InnerMethod", "MS-CHAP-V2");
return 0;
}
static xml_node_t * build_username_password(struct hs20_svc *ctx,
xml_node_t *parent,
const char *user, const char *pw)
{
xml_node_t *node;
char *b64;
node = xml_node_create(ctx->xml, parent, NULL, "UsernamePassword");
if (node == NULL)
return NULL;
add_text_node(ctx, node, "Username", user);
b64 = (char *) base64_encode((unsigned char *) pw, strlen(pw), NULL);
if (b64 == NULL)
return NULL;
add_text_node(ctx, node, "Password", b64);
free(b64);
return node;
}
static int add_username_password(struct hs20_svc *ctx, xml_node_t *cred,
const char *user, const char *pw)
{
xml_node_t *node;
node = build_username_password(ctx, cred, user, pw);
if (node == NULL)
return -1;
add_text_node(ctx, node, "MachineManaged", "TRUE");
add_text_node(ctx, node, "SoftTokenApp", "");
add_eap_ttls(ctx, node);
return 0;
}
static void add_creation_date(struct hs20_svc *ctx, xml_node_t *cred)
{
char str[30];
time_t now;
struct tm tm;
time(&now);
gmtime_r(&now, &tm);
snprintf(str, sizeof(str), "%04u-%02u-%02uT%02u:%02u:%02uZ",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec);
xml_node_create_text(ctx->xml, cred, NULL, "CreationDate", str);
}
static xml_node_t * build_credential_pw(struct hs20_svc *ctx,
const char *user, const char *realm,
const char *pw)
{
xml_node_t *cred;
cred = xml_node_create_root(ctx->xml, NULL, NULL, NULL, "Credential");
if (cred == NULL) {
debug_print(ctx, 1, "Failed to create Credential node");
return NULL;
}
add_creation_date(ctx, cred);
if (add_username_password(ctx, cred, user, pw) < 0) {
xml_node_free(ctx->xml, cred);
return NULL;
}
add_text_node(ctx, cred, "Realm", realm);
return cred;
}
static xml_node_t * build_credential(struct hs20_svc *ctx,
const char *user, const char *realm,
char *new_pw, size_t new_pw_len)
{
if (new_password(new_pw, new_pw_len) < 0)
return NULL;
debug_print(ctx, 1, "Update password to '%s'", new_pw);
return build_credential_pw(ctx, user, realm, new_pw);
}
static xml_node_t * build_credential_cert(struct hs20_svc *ctx,
const char *user, const char *realm,
const char *cert_fingerprint)
{
xml_node_t *cred, *cert;
cred = xml_node_create_root(ctx->xml, NULL, NULL, NULL, "Credential");
if (cred == NULL) {
debug_print(ctx, 1, "Failed to create Credential node");
return NULL;
}
add_creation_date(ctx, cred);
cert = xml_node_create(ctx->xml, cred, NULL, "DigitalCertificate");
add_text_node(ctx, cert, "CertificateType", "x509v3");
add_text_node(ctx, cert, "CertSHA256Fingerprint", cert_fingerprint);
add_text_node(ctx, cred, "Realm", realm);
return cred;
}
static xml_node_t * build_post_dev_data_response(struct hs20_svc *ctx,
xml_namespace_t **ret_ns,
const char *session_id,
const char *status,
const char *error_code)
{
xml_node_t *spp_node = NULL;
xml_namespace_t *ns;
spp_node = xml_node_create_root(ctx->xml, SPP_NS_URI, "spp", &ns,
"sppPostDevDataResponse");
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, ns, "sessionID", session_id);
xml_node_add_attr(ctx->xml, spp_node, ns, "sppStatus", status);
if (error_code) {
xml_node_t *node;
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 add_update_node(struct hs20_svc *ctx, xml_node_t *spp_node,
xml_namespace_t *ns, const char *uri,
xml_node_t *upd_node)
{
xml_node_t *node, *tnds;
char *str;
tnds = mo_to_tnds(ctx->xml, upd_node, 0, NULL, NULL);
if (!tnds)
return -1;
str = xml_node_to_str(ctx->xml, tnds);
xml_node_free(ctx->xml, tnds);
if (str == NULL)
return -1;
node = xml_node_create_text(ctx->xml, spp_node, ns, "updateNode", str);
free(str);
xml_node_add_attr(ctx->xml, node, ns, "managementTreeURI", uri);
return 0;
}
static xml_node_t * build_sub_rem_resp(struct hs20_svc *ctx,
const char *user, const char *realm,
const char *session_id,
int machine_rem, int dmacc)
{
xml_namespace_t *ns;
xml_node_t *spp_node, *cred;
char buf[400];
char new_pw[33];
char *real_user = NULL;
char *status;
char *cert;
if (dmacc) {
real_user = db_get_val(ctx, user, realm, "identity", dmacc);
if (real_user == NULL) {
debug_print(ctx, 1, "Could not find user identity for "
"dmacc user '%s'", user);
return NULL;
}
}
cert = db_get_val(ctx, user, realm, "cert", dmacc);
if (cert && cert[0] == '\0')
cert = NULL;
if (cert) {
cred = build_credential_cert(ctx, real_user ? real_user : user,
realm, cert);
} else {
cred = build_credential(ctx, real_user ? real_user : user,
realm, new_pw, sizeof(new_pw));
}
free(real_user);
if (!cred) {
debug_print(ctx, 1, "Could not build credential");
return NULL;
}
status = "Remediation complete, request sppUpdateResponse";
spp_node = build_post_dev_data_response(ctx, &ns, session_id, status,
NULL);
if (spp_node == NULL) {
debug_print(ctx, 1, "Could not build sppPostDevDataResponse");
return NULL;
}
snprintf(buf, sizeof(buf),
"./Wi-Fi/%s/PerProviderSubscription/Credential1/Credential",
realm);
if (add_update_node(ctx, spp_node, ns, buf, cred) < 0) {
debug_print(ctx, 1, "Could not add update node");
xml_node_free(ctx->xml, spp_node);
return NULL;
}
hs20_eventlog_node(ctx, user, realm, session_id,
machine_rem ? "machine remediation" :
"user remediation", cred);
xml_node_free(ctx->xml, cred);
if (cert) {
debug_print(ctx, 1, "Certificate credential - no need for DB "
"password update on success notification");
} else {
debug_print(ctx, 1, "Request DB password update on success "
"notification");
db_add_session(ctx, user, realm, session_id, new_pw, NULL,
UPDATE_PASSWORD);
}
return spp_node;
}
static xml_node_t * machine_remediation(struct hs20_svc *ctx,
const char *user,
const char *realm,
const char *session_id, int dmacc)
{
return build_sub_rem_resp(ctx, user, realm, session_id, 1, dmacc);
}
static xml_node_t * policy_remediation(struct hs20_svc *ctx,
const char *user, const char *realm,
const char *session_id, int dmacc)
{
xml_namespace_t *ns;
xml_node_t *spp_node, *policy;
char buf[400];
const char *status;
hs20_eventlog(ctx, user, realm, session_id,
"requires policy remediation", NULL);
db_add_session(ctx, user, realm, session_id, NULL, NULL,
POLICY_REMEDIATION);
policy = build_policy(ctx, user, realm, dmacc);
if (!policy) {
return build_post_dev_data_response(
ctx, NULL, session_id,
"No update available at this time", NULL);
}
status = "Remediation complete, request sppUpdateResponse";
spp_node = build_post_dev_data_response(ctx, &ns, session_id, status,
NULL);
if (spp_node == NULL)
return NULL;
snprintf(buf, sizeof(buf),
"./Wi-Fi/%s/PerProviderSubscription/Credential1/Policy",
realm);
if (add_update_node(ctx, spp_node, ns, buf, policy) < 0) {
xml_node_free(ctx->xml, spp_node);
xml_node_free(ctx->xml, policy);
return NULL;
}
hs20_eventlog_node(ctx, user, realm, session_id,
"policy update (sub rem)", policy);
xml_node_free(ctx->xml, policy);
return spp_node;
}
static xml_node_t * browser_remediation(struct hs20_svc *ctx,
const char *session_id,
const char *redirect_uri,
const char *uri)
{
xml_namespace_t *ns;
xml_node_t *spp_node, *exec_node;
if (redirect_uri == NULL) {
debug_print(ctx, 1, "Missing redirectURI attribute for user "
"remediation");
return NULL;
}
debug_print(ctx, 1, "redirectURI %s", redirect_uri);
spp_node = build_post_dev_data_response(ctx, &ns, session_id, "OK",
NULL);
if (spp_node == NULL)
return NULL;
exec_node = xml_node_create(ctx->xml, spp_node, ns, "exec");
xml_node_create_text(ctx->xml, exec_node, ns, "launchBrowserToURI",
uri);
return spp_node;
}
static xml_node_t * user_remediation(struct hs20_svc *ctx, const char *user,
const char *realm, const char *session_id,
const char *redirect_uri)
{
char uri[300], *val;
hs20_eventlog(ctx, user, realm, session_id,
"requires user remediation", NULL);
val = db_get_osu_config_val(ctx, realm, "remediation_url");
if (val == NULL)
return NULL;
db_add_session(ctx, user, realm, session_id, NULL, redirect_uri,
USER_REMEDIATION);
snprintf(uri, sizeof(uri), "%s%s", val, session_id);
os_free(val);
return browser_remediation(ctx, session_id, redirect_uri, uri);
}
static xml_node_t * free_remediation(struct hs20_svc *ctx,
const char *user, const char *realm,
const char *session_id,
const char *redirect_uri)
{
char uri[300], *val;
hs20_eventlog(ctx, user, realm, session_id,
"requires free/public account remediation", NULL);
val = db_get_osu_config_val(ctx, realm, "free_remediation_url");
if (val == NULL)
return NULL;
db_add_session(ctx, user, realm, session_id, NULL, redirect_uri,
FREE_REMEDIATION);
snprintf(uri, sizeof(uri), "%s%s", val, session_id);
os_free(val);
return browser_remediation(ctx, session_id, redirect_uri, uri);
}
static xml_node_t * no_sub_rem(struct hs20_svc *ctx,
const char *user, const char *realm,
const char *session_id)
{
const char *status;
hs20_eventlog(ctx, user, realm, session_id,
"no subscription mediation available", NULL);
status = "No update available at this time";
return build_post_dev_data_response(ctx, NULL, session_id, status,
NULL);
}
static xml_node_t * hs20_subscription_remediation(struct hs20_svc *ctx,
const char *user,
const char *realm,
const char *session_id,
int dmacc,
const char *redirect_uri)
{
char *type, *identity;
xml_node_t *ret;
char *free_account;
identity = db_get_val(ctx, user, realm, "identity", dmacc);
if (identity == NULL || strlen(identity) == 0) {
hs20_eventlog(ctx, user, realm, session_id,
"user not found in database for remediation",
NULL);
os_free(identity);
return build_post_dev_data_response(ctx, NULL, session_id,
"Error occurred",
"Not found");
}
os_free(identity);
free_account = db_get_osu_config_val(ctx, realm, "free_account");
if (free_account && strcmp(free_account, user) == 0) {
free(free_account);
return no_sub_rem(ctx, user, realm, session_id);
}
free(free_account);
type = db_get_val(ctx, user, realm, "remediation", dmacc);
if (type && strcmp(type, "free") != 0) {
char *val;
int shared = 0;
val = db_get_val(ctx, user, realm, "shared", dmacc);
if (val)
shared = atoi(val);
free(val);
if (shared) {
free(type);
return no_sub_rem(ctx, user, realm, session_id);
}
}
if (type && strcmp(type, "user") == 0)
ret = user_remediation(ctx, user, realm, session_id,
redirect_uri);
else if (type && strcmp(type, "free") == 0)
ret = free_remediation(ctx, user, realm, session_id,
redirect_uri);
else if (type && strcmp(type, "policy") == 0)
ret = policy_remediation(ctx, user, realm, session_id, dmacc);
else
ret = machine_remediation(ctx, user, realm, session_id, dmacc);
free(type);
return ret;
}
static xml_node_t * build_policy(struct hs20_svc *ctx, const char *user,
const char *realm, int use_dmacc)
{
char *policy_id;
char fname[200];
xml_node_t *policy, *node;
policy_id = db_get_val(ctx, user, realm, "policy", use_dmacc);
if (policy_id == NULL || strlen(policy_id) == 0) {
free(policy_id);
policy_id = strdup("default");
if (policy_id == NULL)
return NULL;
}
snprintf(fname, sizeof(fname), "%s/spp/policy/%s.xml",
ctx->root_dir, policy_id);
free(policy_id);
debug_print(ctx, 1, "Use policy file %s", fname);
policy = node_from_file(ctx->xml, fname);
if (policy == NULL)
return NULL;
node = get_node_uri(ctx->xml, policy, "Policy/PolicyUpdate/URI");
if (node) {
char *url;
url = db_get_osu_config_val(ctx, realm, "policy_url");
if (url == NULL) {
xml_node_free(ctx->xml, policy);
return NULL;
}
xml_node_set_text(ctx->xml, node, url);
free(url);
}
node = get_node_uri(ctx->xml, policy, "Policy/PolicyUpdate");
if (node && use_dmacc) {
char *pw;
pw = db_get_val(ctx, user, realm, "osu_password", use_dmacc);
if (pw == NULL ||
build_username_password(ctx, node, user, pw) == NULL) {
debug_print(ctx, 1, "Failed to add Policy/PolicyUpdate/"
"UsernamePassword");
free(pw);
xml_node_free(ctx->xml, policy);
return NULL;
}
free(pw);
}
return policy;
}
static xml_node_t * hs20_policy_update(struct hs20_svc *ctx,
const char *user, const char *realm,
const char *session_id, int dmacc)
{
xml_namespace_t *ns;
xml_node_t *spp_node;
xml_node_t *policy;
char buf[400];
const char *status;
char *identity;
identity = db_get_val(ctx, user, realm, "identity", dmacc);
if (identity == NULL || strlen(identity) == 0) {
hs20_eventlog(ctx, user, realm, session_id,
"user not found in database for policy update",
NULL);
os_free(identity);
return build_post_dev_data_response(ctx, NULL, session_id,
"Error occurred",
"Not found");
}
os_free(identity);
policy = build_policy(ctx, user, realm, dmacc);
if (!policy) {
return build_post_dev_data_response(
ctx, NULL, session_id,
"No update available at this time", NULL);
}
db_add_session(ctx, user, realm, session_id, NULL, NULL, POLICY_UPDATE);
status = "Update complete, request sppUpdateResponse";
spp_node = build_post_dev_data_response(ctx, &ns, session_id, status,
NULL);
if (spp_node == NULL)
return NULL;
snprintf(buf, sizeof(buf),
"./Wi-Fi/%s/PerProviderSubscription/Credential1/Policy",
realm);
if (add_update_node(ctx, spp_node, ns, buf, policy) < 0) {
xml_node_free(ctx->xml, spp_node);
xml_node_free(ctx->xml, policy);
return NULL;
}
hs20_eventlog_node(ctx, user, realm, session_id, "policy update",
policy);
xml_node_free(ctx->xml, policy);
return spp_node;
}
static xml_node_t * spp_get_mo(struct hs20_svc *ctx, xml_node_t *node,
const char *urn, int *valid, char **ret_err)
{
xml_node_t *child, *tnds, *mo;
const char *name;
char *mo_urn;
char *str;
char fname[200];
*valid = -1;
if (ret_err)
*ret_err = NULL;
xml_node_for_each_child(ctx->xml, child, node) {
xml_node_for_each_check(ctx->xml, child);
name = xml_node_get_localname(ctx->xml, child);
if (strcmp(name, "moContainer") != 0)
continue;
mo_urn = xml_node_get_attr_value_ns(ctx->xml, child, SPP_NS_URI,
"moURN");
if (strcasecmp(urn, mo_urn) == 0) {
xml_node_get_attr_value_free(ctx->xml, mo_urn);
break;
}
xml_node_get_attr_value_free(ctx->xml, mo_urn);
}
if (child == NULL)
return NULL;
debug_print(ctx, 1, "moContainer text for %s", urn);
debug_dump_node(ctx, "moContainer", child);
str = xml_node_get_text(ctx->xml, child);
debug_print(ctx, 1, "moContainer payload: '%s'", str);
tnds = xml_node_from_buf(ctx->xml, str);
xml_node_get_text_free(ctx->xml, str);
if (tnds == NULL) {
debug_print(ctx, 1, "could not parse moContainer text");
return NULL;
}
snprintf(fname, sizeof(fname), "%s/spp/dm_ddf-v1_2.dtd", ctx->root_dir);
if (xml_validate_dtd(ctx->xml, tnds, fname, ret_err) == 0)
*valid = 1;
else if (ret_err && *ret_err &&
os_strcmp(*ret_err, "No declaration for attribute xmlns of element MgmtTree\n") == 0) {
free(*ret_err);
debug_print(ctx, 1, "Ignore OMA-DM DDF DTD validation error for MgmtTree namespace declaration with xmlns attribute");
*ret_err = NULL;
*valid = 1;
} else
*valid = 0;
mo = tnds_to_mo(ctx->xml, tnds);
xml_node_free(ctx->xml, tnds);
if (mo == NULL) {
debug_print(ctx, 1, "invalid moContainer for %s", urn);
}
return mo;
}
static xml_node_t * spp_exec_upload_mo(struct hs20_svc *ctx,
const char *session_id, const char *urn)
{
xml_namespace_t *ns;
xml_node_t *spp_node, *node, *exec_node;
spp_node = build_post_dev_data_response(ctx, &ns, session_id, "OK",
NULL);
if (spp_node == NULL)
return NULL;
exec_node = xml_node_create(ctx->xml, spp_node, ns, "exec");
node = xml_node_create(ctx->xml, exec_node, ns, "uploadMO");
xml_node_add_attr(ctx->xml, node, ns, "moURN", urn);
return spp_node;
}
static xml_node_t * hs20_subscription_registration(struct hs20_svc *ctx,
const char *realm,
const char *session_id,
const char *redirect_uri)
{
xml_namespace_t *ns;
xml_node_t *spp_node, *exec_node;
char uri[300], *val;
if (db_add_session(ctx, NULL, realm, session_id, NULL, redirect_uri,
SUBSCRIPTION_REGISTRATION) < 0)
return NULL;
val = db_get_osu_config_val(ctx, realm, "signup_url");
if (val == NULL)
return NULL;
spp_node = build_post_dev_data_response(ctx, &ns, session_id, "OK",
NULL);
if (spp_node == NULL)
return NULL;
exec_node = xml_node_create(ctx->xml, spp_node, ns, "exec");
snprintf(uri, sizeof(uri), "%s%s", val, session_id);
os_free(val);
xml_node_create_text(ctx->xml, exec_node, ns, "launchBrowserToURI",
uri);
return spp_node;
}
static xml_node_t * hs20_user_input_remediation(struct hs20_svc *ctx,
const char *user,
const char *realm, int dmacc,
const char *session_id)
{
return build_sub_rem_resp(ctx, user, realm, session_id, 0, dmacc);
}
static char * db_get_osu_config_val(struct hs20_svc *ctx, const char *realm,
const char *field)
{
char *cmd;
struct get_db_field_data data;
cmd = sqlite3_mprintf("SELECT value FROM osu_config WHERE realm=%Q AND "
"field=%Q", realm, field);
if (cmd == NULL)
return NULL;
debug_print(ctx, 1, "DB: %s", cmd);
memset(&data, 0, sizeof(data));
data.field = "value";
if (sqlite3_exec(ctx->db, cmd, get_db_field, &data, NULL) != SQLITE_OK)
{
debug_print(ctx, 1, "DB: Could not find osu_config %s: %s",
realm, sqlite3_errmsg(ctx->db));
sqlite3_free(cmd);
return NULL;
}
sqlite3_free(cmd);
debug_print(ctx, 1, "DB: return '%s'", data.value);
return data.value;
}
static xml_node_t * build_pps(struct hs20_svc *ctx,
const char *user, const char *realm,
const char *pw, const char *cert,
int machine_managed)
{
xml_node_t *pps, *c, *trust, *aaa, *aaa1, *upd, *homesp;
xml_node_t *cred, *eap, *userpw;
pps = xml_node_create_root(ctx->xml, NULL, NULL, NULL,
"PerProviderSubscription");
if (pps == NULL)
return NULL;
add_text_node(ctx, pps, "UpdateIdentifier", "1");
c = xml_node_create(ctx->xml, pps, NULL, "Credential1");
add_text_node(ctx, c, "CredentialPriority", "1");
aaa = xml_node_create(ctx->xml, c, NULL, "AAAServerTrustRoot");
aaa1 = xml_node_create(ctx->xml, aaa, NULL, "AAA1");
add_text_node_conf(ctx, realm, aaa1, "CertURL",
"aaa_trust_root_cert_url");
add_text_node_conf(ctx, realm, aaa1, "CertSHA256Fingerprint",
"aaa_trust_root_cert_fingerprint");
upd = xml_node_create(ctx->xml, c, NULL, "SubscriptionUpdate");
add_text_node(ctx, upd, "UpdateInterval", "4294967295");
add_text_node(ctx, upd, "UpdateMethod", "ClientInitiated");
add_text_node(ctx, upd, "Restriction", "HomeSP");
add_text_node_conf(ctx, realm, upd, "URI", "spp_http_auth_url");
trust = xml_node_create(ctx->xml, upd, NULL, "TrustRoot");
add_text_node_conf(ctx, realm, trust, "CertURL", "trust_root_cert_url");
add_text_node_conf(ctx, realm, trust, "CertSHA256Fingerprint",
"trust_root_cert_fingerprint");
homesp = xml_node_create(ctx->xml, c, NULL, "HomeSP");
add_text_node_conf(ctx, realm, homesp, "FriendlyName", "friendly_name");
add_text_node_conf(ctx, realm, homesp, "FQDN", "fqdn");
xml_node_create(ctx->xml, c, NULL, "SubscriptionParameters");
cred = xml_node_create(ctx->xml, c, NULL, "Credential");
add_creation_date(ctx, cred);
if (cert) {
xml_node_t *dc;
dc = xml_node_create(ctx->xml, cred, NULL,
"DigitalCertificate");
add_text_node(ctx, dc, "CertificateType", "x509v3");
add_text_node(ctx, dc, "CertSHA256Fingerprint", cert);
} else {
userpw = build_username_password(ctx, cred, user, pw);
add_text_node(ctx, userpw, "MachineManaged",
machine_managed ? "TRUE" : "FALSE");
eap = xml_node_create(ctx->xml, userpw, NULL, "EAPMethod");
add_text_node(ctx, eap, "EAPType", "21");
add_text_node(ctx, eap, "InnerMethod", "MS-CHAP-V2");
}
add_text_node(ctx, cred, "Realm", realm);
return pps;
}
static xml_node_t * spp_exec_get_certificate(struct hs20_svc *ctx,
const char *session_id,
const char *user,
const char *realm)
{
xml_namespace_t *ns;
xml_node_t *spp_node, *enroll, *exec_node;
char *val;
char password[11];
char *b64;
if (new_password(password, sizeof(password)) < 0)
return NULL;
spp_node = build_post_dev_data_response(ctx, &ns, session_id, "OK",
NULL);
if (spp_node == NULL)
return NULL;
exec_node = xml_node_create(ctx->xml, spp_node, ns, "exec");
enroll = xml_node_create(ctx->xml, exec_node, ns, "getCertificate");
xml_node_add_attr(ctx->xml, enroll, NULL, "enrollmentProtocol", "EST");
val = db_get_osu_config_val(ctx, realm, "est_url");
xml_node_create_text(ctx->xml, enroll, ns, "enrollmentServerURI",
val ? val : "");
os_free(val);
xml_node_create_text(ctx->xml, enroll, ns, "estUserID", user);
b64 = (char *) base64_encode((unsigned char *) password,
strlen(password), NULL);
if (b64 == NULL) {
xml_node_free(ctx->xml, spp_node);
return NULL;
}
xml_node_create_text(ctx->xml, enroll, ns, "estPassword", b64);
free(b64);
db_update_session_password(ctx, user, realm, session_id, password);
return spp_node;
}
static xml_node_t * hs20_user_input_registration(struct hs20_svc *ctx,
const char *session_id,
int enrollment_done)
{
xml_namespace_t *ns;
xml_node_t *spp_node, *node = NULL;
xml_node_t *pps, *tnds;
char buf[400];
char *str;
char *user, *realm, *pw, *type, *mm;
const char *status;
int cert = 0;
int machine_managed = 0;
char *fingerprint;
user = db_get_session_val(ctx, NULL, NULL, session_id, "user");
realm = db_get_session_val(ctx, NULL, NULL, session_id, "realm");
pw = db_get_session_val(ctx, NULL, NULL, session_id, "password");
if (!user || !realm || !pw) {
debug_print(ctx, 1, "Could not find session info from DB for "
"the new subscription");
free(user);
free(realm);
free(pw);
return NULL;
}
mm = db_get_session_val(ctx, NULL, NULL, session_id, "machine_managed");
if (mm && atoi(mm))
machine_managed = 1;
free(mm);
type = db_get_session_val(ctx, NULL, NULL, session_id, "type");
if (type && strcmp(type, "cert") == 0)
cert = 1;
free(type);
if (cert && !enrollment_done) {
xml_node_t *ret;
hs20_eventlog(ctx, user, realm, session_id,
"request client certificate enrollment", NULL);
ret = spp_exec_get_certificate(ctx, session_id, user, realm);
free(user);
free(realm);
free(pw);
return ret;
}
if (!cert && strlen(pw) == 0) {
machine_managed = 1;
free(pw);
pw = malloc(11);
if (pw == NULL || new_password(pw, 11) < 0) {
free(user);
free(realm);
free(pw);
return NULL;
}
}
status = "Provisioning complete, request sppUpdateResponse";
spp_node = build_post_dev_data_response(ctx, &ns, session_id, status,
NULL);
if (spp_node == NULL)
return NULL;
fingerprint = db_get_session_val(ctx, NULL, NULL, session_id, "cert");
pps = build_pps(ctx, user, realm, pw,
fingerprint ? fingerprint : NULL, machine_managed);
free(fingerprint);
if (!pps) {
xml_node_free(ctx->xml, spp_node);
free(user);
free(realm);
free(pw);
return NULL;
}
debug_print(ctx, 1, "Request DB subscription registration on success "
"notification");
if (machine_managed) {
db_update_session_password(ctx, user, realm, session_id, pw);
db_update_session_machine_managed(ctx, user, realm, session_id,
machine_managed);
}
db_add_session_pps(ctx, user, realm, session_id, pps);
hs20_eventlog_node(ctx, user, realm, session_id,
"new subscription", pps);
free(user);
free(pw);
tnds = mo_to_tnds(ctx->xml, pps, 0, URN_HS20_PPS, NULL);
xml_node_free(ctx->xml, pps);
if (!tnds) {
xml_node_free(ctx->xml, spp_node);
free(realm);
return NULL;
}
str = xml_node_to_str(ctx->xml, tnds);
xml_node_free(ctx->xml, tnds);
if (str == NULL) {
xml_node_free(ctx->xml, spp_node);
free(realm);
return NULL;
}
node = xml_node_create_text(ctx->xml, spp_node, ns, "addMO", str);
free(str);
snprintf(buf, sizeof(buf), "./Wi-Fi/%s/PerProviderSubscription", realm);
free(realm);
xml_node_add_attr(ctx->xml, node, ns, "managementTreeURI", buf);
xml_node_add_attr(ctx->xml, node, ns, "moURN", URN_HS20_PPS);
return spp_node;
}
static xml_node_t * hs20_user_input_free_remediation(struct hs20_svc *ctx,
const char *user,
const char *realm,
const char *session_id)
{
xml_namespace_t *ns;
xml_node_t *spp_node;
xml_node_t *cred;
char buf[400];
char *status;
char *free_account, *pw;
free_account = db_get_osu_config_val(ctx, realm, "free_account");
if (free_account == NULL)
return NULL;
pw = db_get_val(ctx, free_account, realm, "password", 0);
if (pw == NULL) {
free(free_account);
return NULL;
}
cred = build_credential_pw(ctx, free_account, realm, pw);
free(free_account);
free(pw);
if (!cred) {
xml_node_free(ctx->xml, cred);
return NULL;
}
status = "Remediation complete, request sppUpdateResponse";
spp_node = build_post_dev_data_response(ctx, &ns, session_id, status,
NULL);
if (spp_node == NULL)
return NULL;
snprintf(buf, sizeof(buf),
"./Wi-Fi/%s/PerProviderSubscription/Credential1/Credential",
realm);
if (add_update_node(ctx, spp_node, ns, buf, cred) < 0) {
xml_node_free(ctx->xml, spp_node);
return NULL;
}
hs20_eventlog_node(ctx, user, realm, session_id,
"free/public remediation", cred);
xml_node_free(ctx->xml, cred);
return spp_node;
}
static xml_node_t * hs20_user_input_complete(struct hs20_svc *ctx,
const char *user,
const char *realm, int dmacc,
const char *session_id)
{
char *val;
enum hs20_session_operation oper;
val = db_get_session_val(ctx, user, realm, session_id, "operation");
if (val == NULL) {
debug_print(ctx, 1, "No session %s found to continue",
session_id);
return NULL;
}
oper = atoi(val);
free(val);
if (oper == USER_REMEDIATION) {
return hs20_user_input_remediation(ctx, user, realm, dmacc,
session_id);
}
if (oper == FREE_REMEDIATION) {
return hs20_user_input_free_remediation(ctx, user, realm,
session_id);
}
if (oper == SUBSCRIPTION_REGISTRATION) {
return hs20_user_input_registration(ctx, session_id, 0);
}
debug_print(ctx, 1, "User session %s not in state for user input "
"completion", session_id);
return NULL;
}
static xml_node_t * hs20_cert_enroll_completed(struct hs20_svc *ctx,
const char *user,
const char *realm, int dmacc,
const char *session_id)
{
char *val;
enum hs20_session_operation oper;
val = db_get_session_val(ctx, user, realm, session_id, "operation");
if (val == NULL) {
debug_print(ctx, 1, "No session %s found to continue",
session_id);
return NULL;
}
oper = atoi(val);
free(val);
if (oper == SUBSCRIPTION_REGISTRATION)
return hs20_user_input_registration(ctx, session_id, 1);
debug_print(ctx, 1, "User session %s not in state for certificate "
"enrollment completion", session_id);
return NULL;
}
static xml_node_t * hs20_cert_enroll_failed(struct hs20_svc *ctx,
const char *user,
const char *realm, int dmacc,
const char *session_id)
{
char *val;
enum hs20_session_operation oper;
xml_node_t *spp_node, *node;
char *status;
xml_namespace_t *ns;
val = db_get_session_val(ctx, user, realm, session_id, "operation");
if (val == NULL) {
debug_print(ctx, 1, "No session %s found to continue",
session_id);
return NULL;
}
oper = atoi(val);
free(val);
if (oper != SUBSCRIPTION_REGISTRATION) {
debug_print(ctx, 1, "User session %s not in state for "
"enrollment failure", session_id);
return NULL;
}
status = "Error occurred";
spp_node = build_post_dev_data_response(ctx, &ns, session_id, status,
NULL);
if (spp_node == NULL)
return NULL;
node = xml_node_create(ctx->xml, spp_node, ns, "sppError");
xml_node_add_attr(ctx->xml, node, NULL, "errorCode",
"Credentials cannot be provisioned at this time");
db_remove_session(ctx, user, realm, session_id);
return spp_node;
}
static xml_node_t * hs20_spp_post_dev_data(struct hs20_svc *ctx,
xml_node_t *node,
const char *user,
const char *realm,
const char *session_id,
int dmacc)
{
const char *req_reason;
char *redirect_uri = NULL;
char *req_reason_buf = NULL;
char str[200];
xml_node_t *ret = NULL, *devinfo = NULL, *devdetail = NULL;
xml_node_t *mo;
char *version;
int valid;
char *supp, *pos;
char *err;
version = xml_node_get_attr_value_ns(ctx->xml, node, SPP_NS_URI,
"sppVersion");
if (version == NULL || strstr(version, "1.0") == NULL) {
ret = build_post_dev_data_response(
ctx, NULL, session_id, "Error occurred",
"SPP version not supported");
hs20_eventlog_node(ctx, user, realm, session_id,
"Unsupported sppVersion", ret);
xml_node_get_attr_value_free(ctx->xml, version);
return ret;
}
xml_node_get_attr_value_free(ctx->xml, version);
mo = get_node(ctx->xml, node, "supportedMOList");
if (mo == NULL) {
ret = build_post_dev_data_response(
ctx, NULL, session_id, "Error occurred",
"Other");
hs20_eventlog_node(ctx, user, realm, session_id,
"No supportedMOList element", ret);
return ret;
}
supp = xml_node_get_text(ctx->xml, mo);
for (pos = supp; pos && *pos; pos++)
*pos = tolower(*pos);
if (supp == NULL ||
strstr(supp, URN_OMA_DM_DEVINFO) == NULL ||
strstr(supp, URN_OMA_DM_DEVDETAIL) == NULL ||
strstr(supp, URN_HS20_PPS) == NULL) {
xml_node_get_text_free(ctx->xml, supp);
ret = build_post_dev_data_response(
ctx, NULL, session_id, "Error occurred",
"One or more mandatory MOs not supported");
hs20_eventlog_node(ctx, user, realm, session_id,
"Unsupported MOs", ret);
return ret;
}
xml_node_get_text_free(ctx->xml, supp);
req_reason_buf = xml_node_get_attr_value(ctx->xml, node,
"requestReason");
if (req_reason_buf == NULL) {
debug_print(ctx, 1, "No requestReason attribute");
return NULL;
}
req_reason = req_reason_buf;
redirect_uri = xml_node_get_attr_value(ctx->xml, node, "redirectURI");
debug_print(ctx, 1, "requestReason: %s sessionID: %s redirectURI: %s",
req_reason, session_id, redirect_uri);
snprintf(str, sizeof(str), "sppPostDevData: requestReason=%s",
req_reason);
hs20_eventlog(ctx, user, realm, session_id, str, NULL);
devinfo = spp_get_mo(ctx, node, URN_OMA_DM_DEVINFO, &valid, &err);
if (devinfo == NULL) {
ret = build_post_dev_data_response(ctx, NULL, session_id,
"Error occurred", "Other");
hs20_eventlog_node(ctx, user, realm, session_id,
"No DevInfo moContainer in sppPostDevData",
ret);
os_free(err);
goto out;
}
hs20_eventlog_node(ctx, user, realm, session_id,
"Received DevInfo MO", devinfo);
if (valid == 0) {
hs20_eventlog(ctx, user, realm, session_id,
"OMA-DM DDF DTD validation errors in DevInfo MO",
err);
ret = build_post_dev_data_response(ctx, NULL, session_id,
"Error occurred", "Other");
os_free(err);
goto out;
}
os_free(err);
if (user)
db_update_mo(ctx, user, realm, "devinfo", devinfo);
devdetail = spp_get_mo(ctx, node, URN_OMA_DM_DEVDETAIL, &valid, &err);
if (devdetail == NULL) {
ret = build_post_dev_data_response(ctx, NULL, session_id,
"Error occurred", "Other");
hs20_eventlog_node(ctx, user, realm, session_id,
"No DevDetail moContainer in sppPostDevData",
ret);
os_free(err);
goto out;
}
hs20_eventlog_node(ctx, user, realm, session_id,
"Received DevDetail MO", devdetail);
if (valid == 0) {
hs20_eventlog(ctx, user, realm, session_id,
"OMA-DM DDF DTD validation errors "
"in DevDetail MO", err);
ret = build_post_dev_data_response(ctx, NULL, session_id,
"Error occurred", "Other");
os_free(err);
goto out;
}
os_free(err);
if (user)
db_update_mo(ctx, user, realm, "devdetail", devdetail);
if (user)
mo = spp_get_mo(ctx, node, URN_HS20_PPS, &valid, &err);
else {
mo = NULL;
err = NULL;
}
if (user && mo) {
hs20_eventlog_node(ctx, user, realm, session_id,
"Received PPS MO", mo);
if (valid == 0) {
hs20_eventlog(ctx, user, realm, session_id,
"OMA-DM DDF DTD validation errors "
"in PPS MO", err);
xml_node_get_attr_value_free(ctx->xml, redirect_uri);
os_free(err);
return build_post_dev_data_response(
ctx, NULL, session_id,
"Error occurred", "Other");
}
db_update_mo(ctx, user, realm, "pps", mo);
db_update_val(ctx, user, realm, "fetch_pps", "0", dmacc);
xml_node_free(ctx->xml, mo);
}
os_free(err);
if (user && !mo) {
char *fetch;
int fetch_pps;
fetch = db_get_val(ctx, user, realm, "fetch_pps", dmacc);
fetch_pps = fetch ? atoi(fetch) : 0;
free(fetch);
if (fetch_pps) {
enum hs20_session_operation oper;
if (strcasecmp(req_reason, "Subscription remediation")
== 0)
oper = CONTINUE_SUBSCRIPTION_REMEDIATION;
else if (strcasecmp(req_reason, "Policy update") == 0)
oper = CONTINUE_POLICY_UPDATE;
else
oper = NO_OPERATION;
if (db_add_session(ctx, user, realm, session_id, NULL,
NULL, oper) < 0)
goto out;
ret = spp_exec_upload_mo(ctx, session_id,
URN_HS20_PPS);
hs20_eventlog_node(ctx, user, realm, session_id,
"request PPS MO upload",
ret);
goto out;
}
}
if (user && strcasecmp(req_reason, "MO upload") == 0) {
char *val = db_get_session_val(ctx, user, realm, session_id,
"operation");
enum hs20_session_operation oper;
if (!val) {
debug_print(ctx, 1, "No session %s found to continue",
session_id);
goto out;
}
oper = atoi(val);
free(val);
if (oper == CONTINUE_SUBSCRIPTION_REMEDIATION)
req_reason = "Subscription remediation";
else if (oper == CONTINUE_POLICY_UPDATE)
req_reason = "Policy update";
else {
debug_print(ctx, 1,
"No pending operation in session %s",
session_id);
goto out;
}
}
if (strcasecmp(req_reason, "Subscription registration") == 0) {
ret = hs20_subscription_registration(ctx, realm, session_id,
redirect_uri);
hs20_eventlog_node(ctx, user, realm, session_id,
"subscription registration response",
ret);
goto out;
}
if (user && strcasecmp(req_reason, "Subscription remediation") == 0) {
ret = hs20_subscription_remediation(ctx, user, realm,
session_id, dmacc,
redirect_uri);
hs20_eventlog_node(ctx, user, realm, session_id,
"subscription remediation response",
ret);
goto out;
}
if (user && strcasecmp(req_reason, "Policy update") == 0) {
ret = hs20_policy_update(ctx, user, realm, session_id, dmacc);
hs20_eventlog_node(ctx, user, realm, session_id,
"policy update response",
ret);
goto out;
}
if (strcasecmp(req_reason, "User input completed") == 0) {
db_add_session_devinfo(ctx, session_id, devinfo);
db_add_session_devdetail(ctx, session_id, devdetail);
ret = hs20_user_input_complete(ctx, user, realm, dmacc,
session_id);
hs20_eventlog_node(ctx, user, realm, session_id,
"user input completed response", ret);
goto out;
}
if (strcasecmp(req_reason, "Certificate enrollment completed") == 0) {
ret = hs20_cert_enroll_completed(ctx, user, realm, dmacc,
session_id);
hs20_eventlog_node(ctx, user, realm, session_id,
"certificate enrollment response", ret);
goto out;
}
if (strcasecmp(req_reason, "Certificate enrollment failed") == 0) {
ret = hs20_cert_enroll_failed(ctx, user, realm, dmacc,
session_id);
hs20_eventlog_node(ctx, user, realm, session_id,
"certificate enrollment failed response",
ret);
goto out;
}
debug_print(ctx, 1, "Unsupported requestReason '%s' user '%s'",
req_reason, user);
out:
xml_node_get_attr_value_free(ctx->xml, req_reason_buf);
xml_node_get_attr_value_free(ctx->xml, redirect_uri);
if (devinfo)
xml_node_free(ctx->xml, devinfo);
if (devdetail)
xml_node_free(ctx->xml, devdetail);
return ret;
}
static xml_node_t * build_spp_exchange_complete(struct hs20_svc *ctx,
const char *session_id,
const char *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,
"sppExchangeComplete");
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", status);
if (error_code) {
node = xml_node_create(ctx->xml, spp_node, ns, "sppError");
xml_node_add_attr(ctx->xml, node, NULL, "errorCode",
error_code);
}
return spp_node;
}
static int add_subscription(struct hs20_svc *ctx, const char *session_id)
{
char *user, *realm, *pw, *pw_mm, *pps, *str;
char *sql;
int ret = -1;
char *free_account;
int free_acc;
char *type;
int cert = 0;
char *cert_pem, *fingerprint;
user = db_get_session_val(ctx, NULL, NULL, session_id, "user");
realm = db_get_session_val(ctx, NULL, NULL, session_id, "realm");
pw = db_get_session_val(ctx, NULL, NULL, session_id, "password");
pw_mm = db_get_session_val(ctx, NULL, NULL, session_id,
"machine_managed");
pps = db_get_session_val(ctx, NULL, NULL, session_id, "pps");
cert_pem = db_get_session_val(ctx, NULL, NULL, session_id, "cert_pem");
fingerprint = db_get_session_val(ctx, NULL, NULL, session_id, "cert");
type = db_get_session_val(ctx, NULL, NULL, session_id, "type");
if (type && strcmp(type, "cert") == 0)
cert = 1;
free(type);
if (!user || !realm || !pw) {
debug_print(ctx, 1, "Could not find session info from DB for "
"the new subscription");
goto out;
}
free_account = db_get_osu_config_val(ctx, realm, "free_account");
free_acc = free_account && strcmp(free_account, user) == 0;
free(free_account);
debug_print(ctx, 1,
"New subscription: user='%s' realm='%s' free_acc=%d",
user, realm, free_acc);
debug_print(ctx, 1, "New subscription: pps='%s'", pps);
sql = sqlite3_mprintf("UPDATE eventlog SET user=%Q, realm=%Q WHERE "
"sessionid=%Q AND (user='' OR user IS NULL)",
user, realm, session_id);
if (sql) {
debug_print(ctx, 1, "DB: %s", sql);
if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) {
debug_print(ctx, 1, "Failed to update eventlog in "
"sqlite database: %s",
sqlite3_errmsg(ctx->db));
}
sqlite3_free(sql);
}
if (free_acc) {
hs20_eventlog(ctx, user, realm, session_id,
"completed shared free account registration",
NULL);
ret = 0;
goto out;
}
sql = sqlite3_mprintf("INSERT INTO users(identity,realm,phase2,"
"methods,cert,cert_pem,machine_managed) VALUES "
"(%Q,%Q,1,%Q,%Q,%Q,%d)",
user, realm, cert ? "TLS" : "TTLS-MSCHAPV2",
fingerprint ? fingerprint : "",
cert_pem ? cert_pem : "",
pw_mm && atoi(pw_mm) ? 1 : 0);
if (sql == NULL)
goto out;
debug_print(ctx, 1, "DB: %s", sql);
if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) {
debug_print(ctx, 1, "Failed to add user in sqlite database: %s",
sqlite3_errmsg(ctx->db));
sqlite3_free(sql);
goto out;
}
sqlite3_free(sql);
if (cert)
ret = 0;
else
ret = update_password(ctx, user, realm, pw, 0);
if (ret < 0) {
sql = sqlite3_mprintf("DELETE FROM users WHERE identity=%Q AND "
"realm=%Q AND phase2=1",
user, realm);
if (sql) {
debug_print(ctx, 1, "DB: %s", sql);
sqlite3_exec(ctx->db, sql, NULL, NULL, NULL);
sqlite3_free(sql);
}
}
if (pps)
db_update_mo_str(ctx, user, realm, "pps", pps);
str = db_get_session_val(ctx, NULL, NULL, session_id, "devinfo");
if (str) {
db_update_mo_str(ctx, user, realm, "devinfo", str);
free(str);
}
str = db_get_session_val(ctx, NULL, NULL, session_id, "devdetail");
if (str) {
db_update_mo_str(ctx, user, realm, "devdetail", str);
free(str);
}
if (ret == 0) {
hs20_eventlog(ctx, user, realm, session_id,
"completed subscription registration", NULL);
}
out:
free(user);
free(realm);
free(pw);
free(pw_mm);
free(pps);
free(cert_pem);
free(fingerprint);
return ret;
}
static xml_node_t * hs20_spp_update_response(struct hs20_svc *ctx,
xml_node_t *node,
const char *user,
const char *realm,
const char *session_id,
int dmacc)
{
char *status;
xml_node_t *ret;
char *val;
enum hs20_session_operation oper;
status = xml_node_get_attr_value_ns(ctx->xml, node, SPP_NS_URI,
"sppStatus");
if (status == NULL) {
debug_print(ctx, 1, "No sppStatus attribute");
return NULL;
}
debug_print(ctx, 1, "sppUpdateResponse: sppStatus: %s sessionID: %s",
status, session_id);
val = db_get_session_val(ctx, user, realm, session_id, "operation");
if (!val) {
debug_print(ctx, 1,
"No session active for user: %s sessionID: %s",
user, session_id);
oper = NO_OPERATION;
} else
oper = atoi(val);
if (strcasecmp(status, "OK") == 0) {
char *new_pw = NULL;
xml_node_get_attr_value_free(ctx->xml, status);
if (oper == USER_REMEDIATION) {
new_pw = db_get_session_val(ctx, user, realm,
session_id, "password");
if (new_pw == NULL || strlen(new_pw) == 0) {
free(new_pw);
ret = build_spp_exchange_complete(
ctx, session_id, "Error occurred",
"Other");
hs20_eventlog_node(ctx, user, realm,
session_id, "No password "
"had been assigned for "
"session", ret);
db_remove_session(ctx, user, realm, session_id);
return ret;
}
oper = UPDATE_PASSWORD;
}
if (oper == UPDATE_PASSWORD) {
if (!new_pw) {
new_pw = db_get_session_val(ctx, user, realm,
session_id,
"password");
if (!new_pw) {
db_remove_session(ctx, user, realm,
session_id);
return NULL;
}
}
debug_print(ctx, 1, "Update user '%s' password in DB",
user);
if (update_password(ctx, user, realm, new_pw, dmacc) <
0) {
debug_print(ctx, 1, "Failed to update user "
"'%s' password in DB", user);
ret = build_spp_exchange_complete(
ctx, session_id, "Error occurred",
"Other");
hs20_eventlog_node(ctx, user, realm,
session_id, "Failed to "
"update database", ret);
db_remove_session(ctx, user, realm, session_id);
return ret;
}
hs20_eventlog(ctx, user, realm,
session_id, "Updated user password "
"in database", NULL);
}
if (oper == SUBSCRIPTION_REGISTRATION) {
if (add_subscription(ctx, session_id) < 0) {
debug_print(ctx, 1, "Failed to add "
"subscription into DB");
ret = build_spp_exchange_complete(
ctx, session_id, "Error occurred",
"Other");
hs20_eventlog_node(ctx, user, realm,
session_id, "Failed to "
"update database", ret);
db_remove_session(ctx, user, realm, session_id);
return ret;
}
}
if (oper == POLICY_REMEDIATION || oper == POLICY_UPDATE) {
char *val;
val = db_get_val(ctx, user, realm, "remediation",
dmacc);
if (val && strcmp(val, "policy") == 0)
db_update_val(ctx, user, realm, "remediation",
"", dmacc);
free(val);
}
ret = build_spp_exchange_complete(
ctx, session_id,
"Exchange complete, release TLS connection", NULL);
hs20_eventlog_node(ctx, user, realm, session_id,
"Exchange completed", ret);
db_remove_session(ctx, user, realm, session_id);
return ret;
}
ret = build_spp_exchange_complete(ctx, session_id, "Error occurred",
"Other");
hs20_eventlog_node(ctx, user, realm, session_id, "Error occurred", ret);
db_remove_session(ctx, user, realm, session_id);
xml_node_get_attr_value_free(ctx->xml, status);
return ret;
}
#define SPP_SESSION_ID_LEN 16
static char * gen_spp_session_id(void)
{
FILE *f;
int i;
char *session;
session = os_malloc(SPP_SESSION_ID_LEN * 2 + 1);
if (session == NULL)
return NULL;
f = fopen("/dev/urandom", "r");
if (f == NULL) {
os_free(session);
return NULL;
}
for (i = 0; i < SPP_SESSION_ID_LEN; i++)
os_snprintf(session + i * 2, 3, "%02x", fgetc(f));
fclose(f);
return session;
}
xml_node_t * hs20_spp_server_process(struct hs20_svc *ctx, xml_node_t *node,
const char *auth_user,
const char *auth_realm, int dmacc)
{
xml_node_t *ret = NULL;
char *session_id;
const char *op_name;
char *xml_err;
char fname[200];
debug_dump_node(ctx, "received request", node);
if (!dmacc && auth_user && auth_realm) {
char *real;
real = db_get_val(ctx, auth_user, auth_realm, "identity", 0);
if (!real) {
real = db_get_val(ctx, auth_user, auth_realm,
"identity", 1);
if (real)
dmacc = 1;
}
os_free(real);
}
snprintf(fname, sizeof(fname), "%s/spp/spp.xsd", ctx->root_dir);
if (xml_validate(ctx->xml, node, fname, &xml_err) < 0) {
/*
* We may not be able to extract the sessionID from invalid
* input, but well, we can try.
*/
session_id = xml_node_get_attr_value_ns(ctx->xml, node,
SPP_NS_URI,
"sessionID");
debug_print(ctx, 1,
"SPP message failed validation, xsd file: %s xml-error: %s",
fname, xml_err);
hs20_eventlog_node(ctx, auth_user, auth_realm, session_id,
"SPP message failed validation", node);
hs20_eventlog(ctx, auth_user, auth_realm, session_id,
"Validation errors", xml_err);
os_free(xml_err);
xml_node_get_attr_value_free(ctx->xml, session_id);
/* TODO: what to return here? */
ret = xml_node_create_root(ctx->xml, NULL, NULL, NULL,
"SppValidationError");
return ret;
}
session_id = xml_node_get_attr_value_ns(ctx->xml, node, SPP_NS_URI,
"sessionID");
if (session_id) {
char *tmp;
debug_print(ctx, 1, "Received sessionID %s", session_id);
tmp = os_strdup(session_id);
xml_node_get_attr_value_free(ctx->xml, session_id);
if (tmp == NULL)
return NULL;
session_id = tmp;
} else {
session_id = gen_spp_session_id();
if (session_id == NULL) {
debug_print(ctx, 1, "Failed to generate sessionID");
return NULL;
}
debug_print(ctx, 1, "Generated sessionID %s", session_id);
}
op_name = xml_node_get_localname(ctx->xml, node);
if (op_name == NULL) {
debug_print(ctx, 1, "Could not get op_name");
return NULL;
}
if (strcmp(op_name, "sppPostDevData") == 0) {
hs20_eventlog_node(ctx, auth_user, auth_realm, session_id,
"sppPostDevData received and validated",
node);
ret = hs20_spp_post_dev_data(ctx, node, auth_user, auth_realm,
session_id, dmacc);
} else if (strcmp(op_name, "sppUpdateResponse") == 0) {
hs20_eventlog_node(ctx, auth_user, auth_realm, session_id,
"sppUpdateResponse received and validated",
node);
ret = hs20_spp_update_response(ctx, node, auth_user,
auth_realm, session_id, dmacc);
} else {
hs20_eventlog_node(ctx, auth_user, auth_realm, session_id,
"Unsupported SPP message received and "
"validated", node);
debug_print(ctx, 1, "Unsupported operation '%s'", op_name);
/* TODO: what to return here? */
ret = xml_node_create_root(ctx->xml, NULL, NULL, NULL,
"SppUnknownCommandError");
}
os_free(session_id);
if (ret == NULL) {
/* TODO: what to return here? */
ret = xml_node_create_root(ctx->xml, NULL, NULL, NULL,
"SppInternalError");
}
return ret;
}
int hs20_spp_server_init(struct hs20_svc *ctx)
{
char fname[200];
ctx->db = NULL;
snprintf(fname, sizeof(fname), "%s/AS/DB/eap_user.db", ctx->root_dir);
if (sqlite3_open(fname, &ctx->db)) {
printf("Failed to open sqlite database: %s\n",
sqlite3_errmsg(ctx->db));
sqlite3_close(ctx->db);
return -1;
}
return 0;
}
void hs20_spp_server_deinit(struct hs20_svc *ctx)
{
sqlite3_close(ctx->db);
ctx->db = NULL;
}