X-Git-Url: http://www.project-moonshot.org/gitweb/?p=mech_eap.git;a=blobdiff_plain;f=libeap%2Fhs20%2Fserver%2Fspp_server.c;fp=libeap%2Fhs20%2Fserver%2Fspp_server.c;h=33e3fa10385a889cea7cdaf9d55031442d3c5f87;hp=0000000000000000000000000000000000000000;hb=f3746d009c6d7f50025af1f58a85e5fee9680be6;hpb=244f18d04aaf29e68495b5ffeb40ef5cca50942f diff --git a/libeap/hs20/server/spp_server.c b/libeap/hs20/server/spp_server.c new file mode 100644 index 0000000..33e3fa1 --- /dev/null +++ b/libeap/hs20/server/spp_server.c @@ -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 +#include +#include +#include +#include +#include +#include + +#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; +}