![Jouni Malinen](/assets/img/avatar_default.png)
This makes it easier to track whether a policy update has been successfully completed. Signed-off-by: Jouni Malinen <jouni@codeaurora.org>
2436 lines
64 KiB
C
2436 lines
64 KiB
C
/*
|
|
* 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,
|
|
const u8 *mac_addr)
|
|
{
|
|
char *sql;
|
|
int ret = 0;
|
|
char addr[20];
|
|
|
|
if (mac_addr)
|
|
snprintf(addr, sizeof(addr), MACSTR, MAC2STR(mac_addr));
|
|
else
|
|
addr[0] = '\0';
|
|
sql = sqlite3_mprintf("INSERT INTO sessions(timestamp,id,user,realm,"
|
|
"operation,password,redirect_uri,mac_addr,test) "
|
|
"VALUES "
|
|
"(strftime('%%Y-%%m-%%d %%H:%%M:%%f','now'),"
|
|
"%Q,%Q,%Q,%d,%Q,%Q,%Q,%Q)",
|
|
sessionid, user ? user : "", realm ? realm : "",
|
|
operation, pw ? pw : "",
|
|
redirect_uri ? redirect_uri : "",
|
|
addr, ctx->test);
|
|
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 void add_text_node_conf_corrupt(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);
|
|
if (val) {
|
|
size_t len;
|
|
|
|
len = os_strlen(val);
|
|
if (len > 0) {
|
|
if (val[len - 1] == '0')
|
|
val[len - 1] = '1';
|
|
else
|
|
val[len - 1] = '0';
|
|
}
|
|
}
|
|
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,
|
|
int machine_managed)
|
|
{
|
|
xml_node_t *node;
|
|
|
|
node = build_username_password(ctx, cred, user, pw);
|
|
if (node == NULL)
|
|
return -1;
|
|
|
|
add_text_node(ctx, node, "MachineManaged",
|
|
machine_managed ? "TRUE" : "FALSE");
|
|
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, int machine_managed)
|
|
{
|
|
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, machine_managed) < 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, 1);
|
|
}
|
|
|
|
|
|
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 {
|
|
char *pw;
|
|
|
|
pw = db_get_session_val(ctx, user, realm, session_id,
|
|
"password");
|
|
if (pw && pw[0]) {
|
|
debug_print(ctx, 1, "New password from the user: '%s'",
|
|
pw);
|
|
snprintf(new_pw, sizeof(new_pw), "%s", pw);
|
|
free(pw);
|
|
cred = build_credential_pw(ctx,
|
|
real_user ? real_user : user,
|
|
realm, new_pw, 0);
|
|
} 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/Cred01/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, NULL);
|
|
}
|
|
|
|
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, NULL);
|
|
|
|
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/Cred01/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, NULL);
|
|
|
|
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, NULL);
|
|
|
|
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 if (type && strcmp(type, "machine") == 0)
|
|
ret = machine_remediation(ctx, user, realm, session_id, dmacc);
|
|
else
|
|
ret = no_sub_rem(ctx, user, realm, session_id);
|
|
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,
|
|
NULL);
|
|
|
|
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/Cred01/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,
|
|
const u8 *mac_addr)
|
|
{
|
|
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, mac_addr) < 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, const char *test)
|
|
{
|
|
xml_node_t *pps, *c, *trust, *aaa, *aaa1, *upd, *homesp, *p;
|
|
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, "Cred01");
|
|
|
|
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");
|
|
if (test && os_strcmp(test, "corrupt_aaa_hash") == 0) {
|
|
debug_print(ctx, 1,
|
|
"TEST: Corrupt PPS/Cred*/AAAServerTrustRoot/Root*/CertSHA256FingerPrint");
|
|
add_text_node_conf_corrupt(ctx, realm, aaa1,
|
|
"CertSHA256Fingerprint",
|
|
"aaa_trust_root_cert_fingerprint");
|
|
} else {
|
|
add_text_node_conf(ctx, realm, aaa1, "CertSHA256Fingerprint",
|
|
"aaa_trust_root_cert_fingerprint");
|
|
}
|
|
|
|
if (test && os_strcmp(test, "corrupt_polupd_hash") == 0) {
|
|
debug_print(ctx, 1,
|
|
"TEST: Corrupt PPS/Cred*/Policy/PolicyUpdate/Trustroot/CertSHA256FingerPrint");
|
|
p = xml_node_create(ctx->xml, c, NULL, "Policy");
|
|
upd = xml_node_create(ctx->xml, p, NULL, "PolicyUpdate");
|
|
add_text_node(ctx, upd, "UpdateInterval", "30");
|
|
add_text_node(ctx, upd, "UpdateMethod", "SPP-ClientInitiated");
|
|
add_text_node(ctx, upd, "Restriction", "Unrestricted");
|
|
add_text_node_conf(ctx, realm, upd, "URI", "policy_url");
|
|
trust = xml_node_create(ctx->xml, upd, NULL, "TrustRoot");
|
|
add_text_node_conf(ctx, realm, trust, "CertURL",
|
|
"policy_trust_root_cert_url");
|
|
add_text_node_conf_corrupt(ctx, realm, trust,
|
|
"CertSHA256Fingerprint",
|
|
"policy_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", "SPP-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");
|
|
if (test && os_strcmp(test, "corrupt_subrem_hash") == 0) {
|
|
debug_print(ctx, 1,
|
|
"TEST: Corrupt PPS/Cred*/SubscriptionUpdate/Trustroot/CertSHA256FingerPrint");
|
|
add_text_node_conf_corrupt(ctx, realm, trust,
|
|
"CertSHA256Fingerprint",
|
|
"trust_root_cert_fingerprint");
|
|
} else {
|
|
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, *test;
|
|
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");
|
|
test = db_get_session_val(ctx, NULL, NULL, session_id, "test");
|
|
if (test)
|
|
debug_print(ctx, 1, "TEST: Requested special behavior: %s",
|
|
test);
|
|
pps = build_pps(ctx, user, realm, pw,
|
|
fingerprint ? fingerprint : NULL, machine_managed,
|
|
test);
|
|
free(fingerprint);
|
|
free(test);
|
|
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, 1);
|
|
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/Cred01/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, *macaddr;
|
|
char *version;
|
|
int valid;
|
|
char *supp, *pos;
|
|
char *err;
|
|
u8 wifi_mac_addr[ETH_ALEN];
|
|
|
|
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);
|
|
|
|
os_memset(wifi_mac_addr, 0, ETH_ALEN);
|
|
macaddr = get_node(ctx->xml, devdetail,
|
|
"Ext/org.wi-fi/Wi-Fi/Wi-FiMACAddress");
|
|
if (macaddr) {
|
|
char *addr, buf[50];
|
|
|
|
addr = xml_node_get_text(ctx->xml, macaddr);
|
|
if (addr && hwaddr_compact_aton(addr, wifi_mac_addr) == 0) {
|
|
snprintf(buf, sizeof(buf), "DevDetail MAC address: "
|
|
MACSTR, MAC2STR(wifi_mac_addr));
|
|
hs20_eventlog(ctx, user, realm, session_id, buf, NULL);
|
|
xml_node_get_text_free(ctx->xml, addr);
|
|
} else {
|
|
hs20_eventlog(ctx, user, realm, session_id,
|
|
"Could not extract MAC address from DevDetail",
|
|
NULL);
|
|
}
|
|
} else {
|
|
hs20_eventlog(ctx, user, realm, session_id,
|
|
"No MAC address in DevDetail", NULL);
|
|
}
|
|
|
|
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, NULL) < 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,
|
|
wifi_mac_addr);
|
|
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;
|
|
}
|
|
|
|
str = db_get_session_val(ctx, NULL, NULL, session_id, "mac_addr");
|
|
|
|
sql = sqlite3_mprintf("INSERT INTO users(identity,realm,phase2,methods,cert,cert_pem,machine_managed,mac_addr) VALUES (%Q,%Q,1,%Q,%Q,%Q,%d,%Q)",
|
|
user, realm, cert ? "TLS" : "TTLS-MSCHAPV2",
|
|
fingerprint ? fingerprint : "",
|
|
cert_pem ? cert_pem : "",
|
|
pw_mm && atoi(pw_mm) ? 1 : 0,
|
|
str ? str : "");
|
|
free(str);
|
|
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 (cert && user) {
|
|
const char *serialnum;
|
|
|
|
str = db_get_session_val(ctx, NULL, NULL, session_id,
|
|
"mac_addr");
|
|
|
|
if (os_strncmp(user, "cert-", 5) == 0)
|
|
serialnum = user + 5;
|
|
else
|
|
serialnum = "";
|
|
sql = sqlite3_mprintf("INSERT OR REPLACE INTO cert_enroll (mac_addr,user,realm,serialnum) VALUES(%Q,%Q,%Q,%Q)",
|
|
str ? str : "", user, realm ? realm : "",
|
|
serialnum);
|
|
free(str);
|
|
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 add cert_enroll entry into sqlite database: %s",
|
|
sqlite3_errmsg(ctx->db));
|
|
}
|
|
sqlite3_free(sql);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
if (oper == POLICY_UPDATE)
|
|
db_update_val(ctx, user, realm, "polupd_done", "1",
|
|
dmacc);
|
|
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;
|
|
}
|