Cumulative patch from commit 39a5800f7c2a9de743c673a78929ac46a099b1a4

39a5800 wpa_supplicant: Allow disabling LDPC
7230040 Interworking: Read IMSI if not read at supplicant start
62f736d Interworking: Init scard when a credential requires SIM access
729897a Interworking: Fix incorrect compile PCSC flag
21611ea edit: Increase buffer size to 4096 bytes
0b2c59e OSU server: Add example scripts for Hotspot 2.0 PKI
0f27c20 HS 2.0R2: Add example OSU SPP server implementation
1e03c6c XML: Remove forgotten, unused definition of debug_print_func
5cfc87b Make hs20_wan_metrics parser error print more helpful
4be20bf Fix validation of anqp_3gpp_cell_net configuration parameter
23587e3 Remove duplicated vht_capab parser entry
18a8e55 Notify STA of disconnection based on ACL change
8943cc9 RADIUS server: Add support for MAC ACL
dc87541 Clean up debug print for PSK file search
bbbacbf DFS: Print CAC info in ctrl_iface STATUS command
ace0fbd P2P: Fix segfault when PBC overlap is detected
cf15b15 Add writing of network block ocsp parameter
5c9da16 nl80211: Set all BSS interfaces down when tearing down AP in MBSS mode
f1c4dbf wpa_supplicant: Remove pending sme-connect radio work
4f560cd wpa_supplicant: Override HT A-MPDU size if VHT A-MPDU was overridden
3ae8b7b hostapd: Add vendor command support
782e2f7 P2P: Do not initiate scan on P2P Device when enabled
74a1319 Fix issue with incorrect secondary_channel in HT40/HT80
96ecea5 Pass TDLS peer capability information in tdls_mgmt
78cd7e6 Sync with wireless-testing.git include/uapi/linux/nl80211.h
b36935b nl80211: Fix EAPOL frames not being delivered
6997f8b nl80211: Set interface address even if using old interface
9b4d9c8 nl80211: Print if_indices list in debug log
762c41a eloop: Add assert() on negative fd when using select() code path
978c673 Add a note on using 'iw list' to determine multi-BSS support

Change-Id: I89af7f8d92ed706c8909ed3cc9c49d6e1277a2b0
Signed-off-by: Dmitry Shmidt <dimitrysh@google.com>
diff --git a/hs20/server/spp_server.c b/hs20/server/spp_server.c
new file mode 100644
index 0000000..4d77d0e
--- /dev/null
+++ b/hs20/server/spp_server.c
@@ -0,0 +1,2263 @@
+/*
+ * 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_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");
+	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;
+}