Merged the hostap_2.6 updates, and the Leap of Faith work, from the hostap_update...
[mech_eap.git] / libeap / hs20 / server / spp_server.c
diff --git a/libeap/hs20/server/spp_server.c b/libeap/hs20/server/spp_server.c
new file mode 100644 (file)
index 0000000..33e3fa1
--- /dev/null
@@ -0,0 +1,2292 @@
+/*
+ * 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, 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;
+}