6a6569b8bd
Add password and machine_managed flag to database in case of machine managed subscription to fix EAP-TTLS connection failure to production AP. In case of user managed subscription, the entered password is added to DB from the PHP script. However in machine managed subscription, machine generated password is added only in SOAP messages and PPS MO. So connection to production will fail as the generated password is not present in the database used by AAA server. Signed-off-by: Sreenath Sharma <sreenath.mailing.lists@gmail.com>
2290 lines
60 KiB
C
2290 lines
60 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)
|
|
{
|
|
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) {
|
|
if (devinfo)
|
|
db_add_session_devinfo(ctx, session_id, devinfo);
|
|
if (devdetail)
|
|
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");
|
|
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;
|
|
}
|