/* * Hotspot 2.0 SPP server * Copyright (c) 2012-2013, Qualcomm Atheros, Inc. * * This software may be distributed under the terms of the BSD license. * See README for more details. */ #include <stdlib.h> #include <stdio.h> #include <string.h> #include <ctype.h> #include <time.h> #include <errno.h> #include <sqlite3.h> #include "common.h" #include "base64.h" #include "md5_i.h" #include "xml-utils.h" #include "spp_server.h" #define SPP_NS_URI "http://www.wi-fi.org/specifications/hotspot2dot0/v1.0/spp" #define URN_OMA_DM_DEVINFO "urn:oma:mo:oma-dm-devinfo:1.0" #define URN_OMA_DM_DEVDETAIL "urn:oma:mo:oma-dm-devdetail:1.0" #define URN_OMA_DM_DMACC "urn:oma:mo:oma-dm-dmacc:1.0" #define URN_HS20_PPS "urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0" /* TODO: timeout to expire sessions */ enum hs20_session_operation { NO_OPERATION, UPDATE_PASSWORD, CONTINUE_SUBSCRIPTION_REMEDIATION, CONTINUE_POLICY_UPDATE, USER_REMEDIATION, SUBSCRIPTION_REGISTRATION, POLICY_REMEDIATION, POLICY_UPDATE, FREE_REMEDIATION, }; static char * db_get_session_val(struct hs20_svc *ctx, const char *user, const char *realm, const char *session_id, const char *field); static char * db_get_osu_config_val(struct hs20_svc *ctx, const char *realm, const char *field); static xml_node_t * build_policy(struct hs20_svc *ctx, const char *user, const char *realm, int use_dmacc); static int db_add_session(struct hs20_svc *ctx, const char *user, const char *realm, const char *sessionid, const char *pw, const char *redirect_uri, enum hs20_session_operation operation) { char *sql; int ret = 0; sql = sqlite3_mprintf("INSERT INTO sessions(timestamp,id,user,realm," "operation,password,redirect_uri) " "VALUES " "(strftime('%%Y-%%m-%%d %%H:%%M:%%f','now')," "%Q,%Q,%Q,%d,%Q,%Q)", sessionid, user ? user : "", realm ? realm : "", operation, pw ? pw : "", redirect_uri ? redirect_uri : ""); if (sql == NULL) return -1; debug_print(ctx, 1, "DB: %s", sql); if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { debug_print(ctx, 1, "Failed to add session entry into sqlite " "database: %s", sqlite3_errmsg(ctx->db)); ret = -1; } sqlite3_free(sql); return ret; } static void db_update_session_password(struct hs20_svc *ctx, const char *user, const char *realm, const char *sessionid, const char *pw) { char *sql; sql = sqlite3_mprintf("UPDATE sessions SET password=%Q WHERE id=%Q AND " "user=%Q AND realm=%Q", pw, sessionid, user, realm); if (sql == NULL) return; debug_print(ctx, 1, "DB: %s", sql); if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { debug_print(ctx, 1, "Failed to update session password: %s", sqlite3_errmsg(ctx->db)); } sqlite3_free(sql); } static void db_update_session_machine_managed(struct hs20_svc *ctx, const char *user, const char *realm, const char *sessionid, const int pw_mm) { char *sql; sql = sqlite3_mprintf("UPDATE sessions SET machine_managed=%Q WHERE id=%Q AND user=%Q AND realm=%Q", pw_mm ? "1" : "0", sessionid, user, realm); if (sql == NULL) return; debug_print(ctx, 1, "DB: %s", sql); if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { debug_print(ctx, 1, "Failed to update session machine_managed: %s", sqlite3_errmsg(ctx->db)); } sqlite3_free(sql); } static void db_add_session_pps(struct hs20_svc *ctx, const char *user, const char *realm, const char *sessionid, xml_node_t *node) { char *str; char *sql; str = xml_node_to_str(ctx->xml, node); if (str == NULL) return; sql = sqlite3_mprintf("UPDATE sessions SET pps=%Q WHERE id=%Q AND " "user=%Q AND realm=%Q", str, sessionid, user, realm); free(str); if (sql == NULL) return; debug_print(ctx, 1, "DB: %s", sql); if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { debug_print(ctx, 1, "Failed to add session pps: %s", sqlite3_errmsg(ctx->db)); } sqlite3_free(sql); } static void db_add_session_devinfo(struct hs20_svc *ctx, const char *sessionid, xml_node_t *node) { char *str; char *sql; str = xml_node_to_str(ctx->xml, node); if (str == NULL) return; sql = sqlite3_mprintf("UPDATE sessions SET devinfo=%Q WHERE id=%Q", str, sessionid); free(str); if (sql == NULL) return; debug_print(ctx, 1, "DB: %s", sql); if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { debug_print(ctx, 1, "Failed to add session devinfo: %s", sqlite3_errmsg(ctx->db)); } sqlite3_free(sql); } static void db_add_session_devdetail(struct hs20_svc *ctx, const char *sessionid, xml_node_t *node) { char *str; char *sql; str = xml_node_to_str(ctx->xml, node); if (str == NULL) return; sql = sqlite3_mprintf("UPDATE sessions SET devdetail=%Q WHERE id=%Q", str, sessionid); free(str); if (sql == NULL) return; debug_print(ctx, 1, "DB: %s", sql); if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { debug_print(ctx, 1, "Failed to add session devdetail: %s", sqlite3_errmsg(ctx->db)); } sqlite3_free(sql); } static void db_remove_session(struct hs20_svc *ctx, const char *user, const char *realm, const char *sessionid) { char *sql; if (user == NULL || realm == NULL) { sql = sqlite3_mprintf("DELETE FROM sessions WHERE " "id=%Q", sessionid); } else { sql = sqlite3_mprintf("DELETE FROM sessions WHERE " "user=%Q AND realm=%Q AND id=%Q", user, realm, sessionid); } if (sql == NULL) return; debug_print(ctx, 1, "DB: %s", sql); if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { debug_print(ctx, 1, "Failed to delete session entry from " "sqlite database: %s", sqlite3_errmsg(ctx->db)); } sqlite3_free(sql); } static void hs20_eventlog(struct hs20_svc *ctx, const char *user, const char *realm, const char *sessionid, const char *notes, const char *dump) { char *sql; char *user_buf = NULL, *realm_buf = NULL; debug_print(ctx, 1, "eventlog: %s", notes); if (user == NULL) { user_buf = db_get_session_val(ctx, NULL, NULL, sessionid, "user"); user = user_buf; realm_buf = db_get_session_val(ctx, NULL, NULL, sessionid, "realm"); realm = realm_buf; } sql = sqlite3_mprintf("INSERT INTO eventlog" "(user,realm,sessionid,timestamp,notes,dump,addr)" " VALUES (%Q,%Q,%Q," "strftime('%%Y-%%m-%%d %%H:%%M:%%f','now')," "%Q,%Q,%Q)", user, realm, sessionid, notes, dump ? dump : "", ctx->addr ? ctx->addr : ""); free(user_buf); free(realm_buf); if (sql == NULL) return; if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { debug_print(ctx, 1, "Failed to add eventlog entry into sqlite " "database: %s", sqlite3_errmsg(ctx->db)); } sqlite3_free(sql); } static void hs20_eventlog_node(struct hs20_svc *ctx, const char *user, const char *realm, const char *sessionid, const char *notes, xml_node_t *node) { char *str; if (node) str = xml_node_to_str(ctx->xml, node); else str = NULL; hs20_eventlog(ctx, user, realm, sessionid, notes, str); free(str); } static void db_update_mo_str(struct hs20_svc *ctx, const char *user, const char *realm, const char *name, const char *str) { char *sql; if (user == NULL || realm == NULL || name == NULL) return; sql = sqlite3_mprintf("UPDATE users SET %s=%Q " "WHERE identity=%Q AND realm=%Q AND phase2=1", name, str, user, realm); if (sql == NULL) return; debug_print(ctx, 1, "DB: %s", sql); if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { debug_print(ctx, 1, "Failed to update user MO entry in sqlite " "database: %s", sqlite3_errmsg(ctx->db)); } sqlite3_free(sql); } static void db_update_mo(struct hs20_svc *ctx, const char *user, const char *realm, const char *name, xml_node_t *mo) { char *str; str = xml_node_to_str(ctx->xml, mo); if (str == NULL) return; db_update_mo_str(ctx, user, realm, name, str); free(str); } static void add_text_node(struct hs20_svc *ctx, xml_node_t *parent, const char *name, const char *value) { xml_node_create_text(ctx->xml, parent, NULL, name, value ? value : ""); } static void add_text_node_conf(struct hs20_svc *ctx, const char *realm, xml_node_t *parent, const char *name, const char *field) { char *val; val = db_get_osu_config_val(ctx, realm, field); xml_node_create_text(ctx->xml, parent, NULL, name, val ? val : ""); os_free(val); } static int new_password(char *buf, int buflen) { int i; if (buflen < 1) return -1; buf[buflen - 1] = '\0'; if (os_get_random((unsigned char *) buf, buflen - 1) < 0) return -1; for (i = 0; i < buflen - 1; i++) { unsigned char val = buf[i]; val %= 2 * 26 + 10; if (val < 26) buf[i] = 'a' + val; else if (val < 2 * 26) buf[i] = 'A' + val - 26; else buf[i] = '0' + val - 2 * 26; } return 0; } struct get_db_field_data { const char *field; char *value; }; static int get_db_field(void *ctx, int argc, char *argv[], char *col[]) { struct get_db_field_data *data = ctx; int i; for (i = 0; i < argc; i++) { if (os_strcmp(col[i], data->field) == 0 && argv[i]) { os_free(data->value); data->value = os_strdup(argv[i]); break; } } return 0; } static char * db_get_val(struct hs20_svc *ctx, const char *user, const char *realm, const char *field, int dmacc) { char *cmd; struct get_db_field_data data; cmd = sqlite3_mprintf("SELECT %s FROM users WHERE " "%s=%Q AND realm=%Q AND phase2=1", field, dmacc ? "osu_user" : "identity", user, realm); if (cmd == NULL) return NULL; memset(&data, 0, sizeof(data)); data.field = field; if (sqlite3_exec(ctx->db, cmd, get_db_field, &data, NULL) != SQLITE_OK) { debug_print(ctx, 1, "Could not find user '%s'", user); sqlite3_free(cmd); return NULL; } sqlite3_free(cmd); debug_print(ctx, 1, "DB: user='%s' realm='%s' field='%s' dmacc=%d --> " "value='%s'", user, realm, field, dmacc, data.value); return data.value; } static int db_update_val(struct hs20_svc *ctx, const char *user, const char *realm, const char *field, const char *val, int dmacc) { char *cmd; int ret; cmd = sqlite3_mprintf("UPDATE users SET %s=%Q WHERE " "%s=%Q AND realm=%Q AND phase2=1", field, val, dmacc ? "osu_user" : "identity", user, realm); if (cmd == NULL) return -1; debug_print(ctx, 1, "DB: %s", cmd); if (sqlite3_exec(ctx->db, cmd, NULL, NULL, NULL) != SQLITE_OK) { debug_print(ctx, 1, "Failed to update user in sqlite database: %s", sqlite3_errmsg(ctx->db)); ret = -1; } else { debug_print(ctx, 1, "DB: user='%s' realm='%s' field='%s' set to '%s'", user, realm, field, val); ret = 0; } sqlite3_free(cmd); return ret; } static char * db_get_session_val(struct hs20_svc *ctx, const char *user, const char *realm, const char *session_id, const char *field) { char *cmd; struct get_db_field_data data; if (user == NULL || realm == NULL) { cmd = sqlite3_mprintf("SELECT %s FROM sessions WHERE " "id=%Q", field, session_id); } else { cmd = sqlite3_mprintf("SELECT %s FROM sessions WHERE " "user=%Q AND realm=%Q AND id=%Q", field, user, realm, session_id); } if (cmd == NULL) return NULL; debug_print(ctx, 1, "DB: %s", cmd); memset(&data, 0, sizeof(data)); data.field = field; if (sqlite3_exec(ctx->db, cmd, get_db_field, &data, NULL) != SQLITE_OK) { debug_print(ctx, 1, "DB: Could not find session %s: %s", session_id, sqlite3_errmsg(ctx->db)); sqlite3_free(cmd); return NULL; } sqlite3_free(cmd); debug_print(ctx, 1, "DB: return '%s'", data.value); return data.value; } static int update_password(struct hs20_svc *ctx, const char *user, const char *realm, const char *pw, int dmacc) { char *cmd; cmd = sqlite3_mprintf("UPDATE users SET password=%Q, " "remediation='' " "WHERE %s=%Q AND phase2=1", pw, dmacc ? "osu_user" : "identity", user); if (cmd == NULL) return -1; debug_print(ctx, 1, "DB: %s", cmd); if (sqlite3_exec(ctx->db, cmd, NULL, NULL, NULL) != SQLITE_OK) { debug_print(ctx, 1, "Failed to update database for user '%s'", user); } sqlite3_free(cmd); return 0; } static int add_eap_ttls(struct hs20_svc *ctx, xml_node_t *parent) { xml_node_t *node; node = xml_node_create(ctx->xml, parent, NULL, "EAPMethod"); if (node == NULL) return -1; add_text_node(ctx, node, "EAPType", "21"); add_text_node(ctx, node, "InnerMethod", "MS-CHAP-V2"); return 0; } static xml_node_t * build_username_password(struct hs20_svc *ctx, xml_node_t *parent, const char *user, const char *pw) { xml_node_t *node; char *b64; node = xml_node_create(ctx->xml, parent, NULL, "UsernamePassword"); if (node == NULL) return NULL; add_text_node(ctx, node, "Username", user); b64 = (char *) base64_encode((unsigned char *) pw, strlen(pw), NULL); if (b64 == NULL) return NULL; add_text_node(ctx, node, "Password", b64); free(b64); return node; } static int add_username_password(struct hs20_svc *ctx, xml_node_t *cred, const char *user, const char *pw) { xml_node_t *node; node = build_username_password(ctx, cred, user, pw); if (node == NULL) return -1; add_text_node(ctx, node, "MachineManaged", "TRUE"); add_text_node(ctx, node, "SoftTokenApp", ""); add_eap_ttls(ctx, node); return 0; } static void add_creation_date(struct hs20_svc *ctx, xml_node_t *cred) { char str[30]; time_t now; struct tm tm; time(&now); gmtime_r(&now, &tm); snprintf(str, sizeof(str), "%04u-%02u-%02uT%02u:%02u:%02uZ", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); xml_node_create_text(ctx->xml, cred, NULL, "CreationDate", str); } static xml_node_t * build_credential_pw(struct hs20_svc *ctx, const char *user, const char *realm, const char *pw) { xml_node_t *cred; cred = xml_node_create_root(ctx->xml, NULL, NULL, NULL, "Credential"); if (cred == NULL) { debug_print(ctx, 1, "Failed to create Credential node"); return NULL; } add_creation_date(ctx, cred); if (add_username_password(ctx, cred, user, pw) < 0) { xml_node_free(ctx->xml, cred); return NULL; } add_text_node(ctx, cred, "Realm", realm); return cred; } static xml_node_t * build_credential(struct hs20_svc *ctx, const char *user, const char *realm, char *new_pw, size_t new_pw_len) { if (new_password(new_pw, new_pw_len) < 0) return NULL; debug_print(ctx, 1, "Update password to '%s'", new_pw); return build_credential_pw(ctx, user, realm, new_pw); } static xml_node_t * build_credential_cert(struct hs20_svc *ctx, const char *user, const char *realm, const char *cert_fingerprint) { xml_node_t *cred, *cert; cred = xml_node_create_root(ctx->xml, NULL, NULL, NULL, "Credential"); if (cred == NULL) { debug_print(ctx, 1, "Failed to create Credential node"); return NULL; } add_creation_date(ctx, cred); cert = xml_node_create(ctx->xml, cred, NULL, "DigitalCertificate"); add_text_node(ctx, cert, "CertificateType", "x509v3"); add_text_node(ctx, cert, "CertSHA256Fingerprint", cert_fingerprint); add_text_node(ctx, cred, "Realm", realm); return cred; } static xml_node_t * build_post_dev_data_response(struct hs20_svc *ctx, xml_namespace_t **ret_ns, const char *session_id, const char *status, const char *error_code) { xml_node_t *spp_node = NULL; xml_namespace_t *ns; spp_node = xml_node_create_root(ctx->xml, SPP_NS_URI, "spp", &ns, "sppPostDevDataResponse"); if (spp_node == NULL) return NULL; if (ret_ns) *ret_ns = ns; xml_node_add_attr(ctx->xml, spp_node, ns, "sppVersion", "1.0"); xml_node_add_attr(ctx->xml, spp_node, ns, "sessionID", session_id); xml_node_add_attr(ctx->xml, spp_node, ns, "sppStatus", status); if (error_code) { xml_node_t *node; node = xml_node_create(ctx->xml, spp_node, ns, "sppError"); if (node) xml_node_add_attr(ctx->xml, node, NULL, "errorCode", error_code); } return spp_node; } static int add_update_node(struct hs20_svc *ctx, xml_node_t *spp_node, xml_namespace_t *ns, const char *uri, xml_node_t *upd_node) { xml_node_t *node, *tnds; char *str; tnds = mo_to_tnds(ctx->xml, upd_node, 0, NULL, NULL); if (!tnds) return -1; str = xml_node_to_str(ctx->xml, tnds); xml_node_free(ctx->xml, tnds); if (str == NULL) return -1; node = xml_node_create_text(ctx->xml, spp_node, ns, "updateNode", str); free(str); xml_node_add_attr(ctx->xml, node, ns, "managementTreeURI", uri); return 0; } static xml_node_t * build_sub_rem_resp(struct hs20_svc *ctx, const char *user, const char *realm, const char *session_id, int machine_rem, int dmacc) { xml_namespace_t *ns; xml_node_t *spp_node, *cred; char buf[400]; char new_pw[33]; char *real_user = NULL; char *status; char *cert; if (dmacc) { real_user = db_get_val(ctx, user, realm, "identity", dmacc); if (real_user == NULL) { debug_print(ctx, 1, "Could not find user identity for " "dmacc user '%s'", user); return NULL; } } cert = db_get_val(ctx, user, realm, "cert", dmacc); if (cert && cert[0] == '\0') cert = NULL; if (cert) { cred = build_credential_cert(ctx, real_user ? real_user : user, realm, cert); } else { cred = build_credential(ctx, real_user ? real_user : user, realm, new_pw, sizeof(new_pw)); } free(real_user); if (!cred) { debug_print(ctx, 1, "Could not build credential"); return NULL; } status = "Remediation complete, request sppUpdateResponse"; spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, NULL); if (spp_node == NULL) { debug_print(ctx, 1, "Could not build sppPostDevDataResponse"); return NULL; } snprintf(buf, sizeof(buf), "./Wi-Fi/%s/PerProviderSubscription/Credential1/Credential", realm); if (add_update_node(ctx, spp_node, ns, buf, cred) < 0) { debug_print(ctx, 1, "Could not add update node"); xml_node_free(ctx->xml, spp_node); return NULL; } hs20_eventlog_node(ctx, user, realm, session_id, machine_rem ? "machine remediation" : "user remediation", cred); xml_node_free(ctx->xml, cred); if (cert) { debug_print(ctx, 1, "Certificate credential - no need for DB " "password update on success notification"); } else { debug_print(ctx, 1, "Request DB password update on success " "notification"); db_add_session(ctx, user, realm, session_id, new_pw, NULL, UPDATE_PASSWORD); } return spp_node; } static xml_node_t * machine_remediation(struct hs20_svc *ctx, const char *user, const char *realm, const char *session_id, int dmacc) { return build_sub_rem_resp(ctx, user, realm, session_id, 1, dmacc); } static xml_node_t * policy_remediation(struct hs20_svc *ctx, const char *user, const char *realm, const char *session_id, int dmacc) { xml_namespace_t *ns; xml_node_t *spp_node, *policy; char buf[400]; const char *status; hs20_eventlog(ctx, user, realm, session_id, "requires policy remediation", NULL); db_add_session(ctx, user, realm, session_id, NULL, NULL, POLICY_REMEDIATION); policy = build_policy(ctx, user, realm, dmacc); if (!policy) { return build_post_dev_data_response( ctx, NULL, session_id, "No update available at this time", NULL); } status = "Remediation complete, request sppUpdateResponse"; spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, NULL); if (spp_node == NULL) return NULL; snprintf(buf, sizeof(buf), "./Wi-Fi/%s/PerProviderSubscription/Credential1/Policy", realm); if (add_update_node(ctx, spp_node, ns, buf, policy) < 0) { xml_node_free(ctx->xml, spp_node); xml_node_free(ctx->xml, policy); return NULL; } hs20_eventlog_node(ctx, user, realm, session_id, "policy update (sub rem)", policy); xml_node_free(ctx->xml, policy); return spp_node; } static xml_node_t * browser_remediation(struct hs20_svc *ctx, const char *session_id, const char *redirect_uri, const char *uri) { xml_namespace_t *ns; xml_node_t *spp_node, *exec_node; if (redirect_uri == NULL) { debug_print(ctx, 1, "Missing redirectURI attribute for user " "remediation"); return NULL; } debug_print(ctx, 1, "redirectURI %s", redirect_uri); spp_node = build_post_dev_data_response(ctx, &ns, session_id, "OK", NULL); if (spp_node == NULL) return NULL; exec_node = xml_node_create(ctx->xml, spp_node, ns, "exec"); xml_node_create_text(ctx->xml, exec_node, ns, "launchBrowserToURI", uri); return spp_node; } static xml_node_t * user_remediation(struct hs20_svc *ctx, const char *user, const char *realm, const char *session_id, const char *redirect_uri) { char uri[300], *val; hs20_eventlog(ctx, user, realm, session_id, "requires user remediation", NULL); val = db_get_osu_config_val(ctx, realm, "remediation_url"); if (val == NULL) return NULL; db_add_session(ctx, user, realm, session_id, NULL, redirect_uri, USER_REMEDIATION); snprintf(uri, sizeof(uri), "%s%s", val, session_id); os_free(val); return browser_remediation(ctx, session_id, redirect_uri, uri); } static xml_node_t * free_remediation(struct hs20_svc *ctx, const char *user, const char *realm, const char *session_id, const char *redirect_uri) { char uri[300], *val; hs20_eventlog(ctx, user, realm, session_id, "requires free/public account remediation", NULL); val = db_get_osu_config_val(ctx, realm, "free_remediation_url"); if (val == NULL) return NULL; db_add_session(ctx, user, realm, session_id, NULL, redirect_uri, FREE_REMEDIATION); snprintf(uri, sizeof(uri), "%s%s", val, session_id); os_free(val); return browser_remediation(ctx, session_id, redirect_uri, uri); } static xml_node_t * no_sub_rem(struct hs20_svc *ctx, const char *user, const char *realm, const char *session_id) { const char *status; hs20_eventlog(ctx, user, realm, session_id, "no subscription mediation available", NULL); status = "No update available at this time"; return build_post_dev_data_response(ctx, NULL, session_id, status, NULL); } static xml_node_t * hs20_subscription_remediation(struct hs20_svc *ctx, const char *user, const char *realm, const char *session_id, int dmacc, const char *redirect_uri) { char *type, *identity; xml_node_t *ret; char *free_account; identity = db_get_val(ctx, user, realm, "identity", dmacc); if (identity == NULL || strlen(identity) == 0) { hs20_eventlog(ctx, user, realm, session_id, "user not found in database for remediation", NULL); os_free(identity); return build_post_dev_data_response(ctx, NULL, session_id, "Error occurred", "Not found"); } os_free(identity); free_account = db_get_osu_config_val(ctx, realm, "free_account"); if (free_account && strcmp(free_account, user) == 0) { free(free_account); return no_sub_rem(ctx, user, realm, session_id); } free(free_account); type = db_get_val(ctx, user, realm, "remediation", dmacc); if (type && strcmp(type, "free") != 0) { char *val; int shared = 0; val = db_get_val(ctx, user, realm, "shared", dmacc); if (val) shared = atoi(val); free(val); if (shared) { free(type); return no_sub_rem(ctx, user, realm, session_id); } } if (type && strcmp(type, "user") == 0) ret = user_remediation(ctx, user, realm, session_id, redirect_uri); else if (type && strcmp(type, "free") == 0) ret = free_remediation(ctx, user, realm, session_id, redirect_uri); else if (type && strcmp(type, "policy") == 0) ret = policy_remediation(ctx, user, realm, session_id, dmacc); else ret = machine_remediation(ctx, user, realm, session_id, dmacc); free(type); return ret; } static xml_node_t * build_policy(struct hs20_svc *ctx, const char *user, const char *realm, int use_dmacc) { char *policy_id; char fname[200]; xml_node_t *policy, *node; policy_id = db_get_val(ctx, user, realm, "policy", use_dmacc); if (policy_id == NULL || strlen(policy_id) == 0) { free(policy_id); policy_id = strdup("default"); if (policy_id == NULL) return NULL; } snprintf(fname, sizeof(fname), "%s/spp/policy/%s.xml", ctx->root_dir, policy_id); free(policy_id); debug_print(ctx, 1, "Use policy file %s", fname); policy = node_from_file(ctx->xml, fname); if (policy == NULL) return NULL; node = get_node_uri(ctx->xml, policy, "Policy/PolicyUpdate/URI"); if (node) { char *url; url = db_get_osu_config_val(ctx, realm, "policy_url"); if (url == NULL) { xml_node_free(ctx->xml, policy); return NULL; } xml_node_set_text(ctx->xml, node, url); free(url); } node = get_node_uri(ctx->xml, policy, "Policy/PolicyUpdate"); if (node && use_dmacc) { char *pw; pw = db_get_val(ctx, user, realm, "osu_password", use_dmacc); if (pw == NULL || build_username_password(ctx, node, user, pw) == NULL) { debug_print(ctx, 1, "Failed to add Policy/PolicyUpdate/" "UsernamePassword"); free(pw); xml_node_free(ctx->xml, policy); return NULL; } free(pw); } return policy; } static xml_node_t * hs20_policy_update(struct hs20_svc *ctx, const char *user, const char *realm, const char *session_id, int dmacc) { xml_namespace_t *ns; xml_node_t *spp_node; xml_node_t *policy; char buf[400]; const char *status; char *identity; identity = db_get_val(ctx, user, realm, "identity", dmacc); if (identity == NULL || strlen(identity) == 0) { hs20_eventlog(ctx, user, realm, session_id, "user not found in database for policy update", NULL); os_free(identity); return build_post_dev_data_response(ctx, NULL, session_id, "Error occurred", "Not found"); } os_free(identity); policy = build_policy(ctx, user, realm, dmacc); if (!policy) { return build_post_dev_data_response( ctx, NULL, session_id, "No update available at this time", NULL); } db_add_session(ctx, user, realm, session_id, NULL, NULL, POLICY_UPDATE); status = "Update complete, request sppUpdateResponse"; spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, NULL); if (spp_node == NULL) return NULL; snprintf(buf, sizeof(buf), "./Wi-Fi/%s/PerProviderSubscription/Credential1/Policy", realm); if (add_update_node(ctx, spp_node, ns, buf, policy) < 0) { xml_node_free(ctx->xml, spp_node); xml_node_free(ctx->xml, policy); return NULL; } hs20_eventlog_node(ctx, user, realm, session_id, "policy update", policy); xml_node_free(ctx->xml, policy); return spp_node; } static xml_node_t * spp_get_mo(struct hs20_svc *ctx, xml_node_t *node, const char *urn, int *valid, char **ret_err) { xml_node_t *child, *tnds, *mo; const char *name; char *mo_urn; char *str; char fname[200]; *valid = -1; if (ret_err) *ret_err = NULL; xml_node_for_each_child(ctx->xml, child, node) { xml_node_for_each_check(ctx->xml, child); name = xml_node_get_localname(ctx->xml, child); if (strcmp(name, "moContainer") != 0) continue; mo_urn = xml_node_get_attr_value_ns(ctx->xml, child, SPP_NS_URI, "moURN"); if (strcasecmp(urn, mo_urn) == 0) { xml_node_get_attr_value_free(ctx->xml, mo_urn); break; } xml_node_get_attr_value_free(ctx->xml, mo_urn); } if (child == NULL) return NULL; debug_print(ctx, 1, "moContainer text for %s", urn); debug_dump_node(ctx, "moContainer", child); str = xml_node_get_text(ctx->xml, child); debug_print(ctx, 1, "moContainer payload: '%s'", str); tnds = xml_node_from_buf(ctx->xml, str); xml_node_get_text_free(ctx->xml, str); if (tnds == NULL) { debug_print(ctx, 1, "could not parse moContainer text"); return NULL; } snprintf(fname, sizeof(fname), "%s/spp/dm_ddf-v1_2.dtd", ctx->root_dir); if (xml_validate_dtd(ctx->xml, tnds, fname, ret_err) == 0) *valid = 1; else if (ret_err && *ret_err && os_strcmp(*ret_err, "No declaration for attribute xmlns of element MgmtTree\n") == 0) { free(*ret_err); debug_print(ctx, 1, "Ignore OMA-DM DDF DTD validation error for MgmtTree namespace declaration with xmlns attribute"); *ret_err = NULL; *valid = 1; } else *valid = 0; mo = tnds_to_mo(ctx->xml, tnds); xml_node_free(ctx->xml, tnds); if (mo == NULL) { debug_print(ctx, 1, "invalid moContainer for %s", urn); } return mo; } static xml_node_t * spp_exec_upload_mo(struct hs20_svc *ctx, const char *session_id, const char *urn) { xml_namespace_t *ns; xml_node_t *spp_node, *node, *exec_node; spp_node = build_post_dev_data_response(ctx, &ns, session_id, "OK", NULL); if (spp_node == NULL) return NULL; exec_node = xml_node_create(ctx->xml, spp_node, ns, "exec"); node = xml_node_create(ctx->xml, exec_node, ns, "uploadMO"); xml_node_add_attr(ctx->xml, node, ns, "moURN", urn); return spp_node; } static xml_node_t * hs20_subscription_registration(struct hs20_svc *ctx, const char *realm, const char *session_id, const char *redirect_uri) { xml_namespace_t *ns; xml_node_t *spp_node, *exec_node; char uri[300], *val; if (db_add_session(ctx, NULL, realm, session_id, NULL, redirect_uri, SUBSCRIPTION_REGISTRATION) < 0) return NULL; val = db_get_osu_config_val(ctx, realm, "signup_url"); if (val == NULL) return NULL; spp_node = build_post_dev_data_response(ctx, &ns, session_id, "OK", NULL); if (spp_node == NULL) return NULL; exec_node = xml_node_create(ctx->xml, spp_node, ns, "exec"); snprintf(uri, sizeof(uri), "%s%s", val, session_id); os_free(val); xml_node_create_text(ctx->xml, exec_node, ns, "launchBrowserToURI", uri); return spp_node; } static xml_node_t * hs20_user_input_remediation(struct hs20_svc *ctx, const char *user, const char *realm, int dmacc, const char *session_id) { return build_sub_rem_resp(ctx, user, realm, session_id, 0, dmacc); } static char * db_get_osu_config_val(struct hs20_svc *ctx, const char *realm, const char *field) { char *cmd; struct get_db_field_data data; cmd = sqlite3_mprintf("SELECT value FROM osu_config WHERE realm=%Q AND " "field=%Q", realm, field); if (cmd == NULL) return NULL; debug_print(ctx, 1, "DB: %s", cmd); memset(&data, 0, sizeof(data)); data.field = "value"; if (sqlite3_exec(ctx->db, cmd, get_db_field, &data, NULL) != SQLITE_OK) { debug_print(ctx, 1, "DB: Could not find osu_config %s: %s", realm, sqlite3_errmsg(ctx->db)); sqlite3_free(cmd); return NULL; } sqlite3_free(cmd); debug_print(ctx, 1, "DB: return '%s'", data.value); return data.value; } static xml_node_t * build_pps(struct hs20_svc *ctx, const char *user, const char *realm, const char *pw, const char *cert, int machine_managed) { xml_node_t *pps, *c, *trust, *aaa, *aaa1, *upd, *homesp; xml_node_t *cred, *eap, *userpw; pps = xml_node_create_root(ctx->xml, NULL, NULL, NULL, "PerProviderSubscription"); if (pps == NULL) return NULL; add_text_node(ctx, pps, "UpdateIdentifier", "1"); c = xml_node_create(ctx->xml, pps, NULL, "Credential1"); add_text_node(ctx, c, "CredentialPriority", "1"); aaa = xml_node_create(ctx->xml, c, NULL, "AAAServerTrustRoot"); aaa1 = xml_node_create(ctx->xml, aaa, NULL, "AAA1"); add_text_node_conf(ctx, realm, aaa1, "CertURL", "aaa_trust_root_cert_url"); add_text_node_conf(ctx, realm, aaa1, "CertSHA256Fingerprint", "aaa_trust_root_cert_fingerprint"); upd = xml_node_create(ctx->xml, c, NULL, "SubscriptionUpdate"); add_text_node(ctx, upd, "UpdateInterval", "4294967295"); add_text_node(ctx, upd, "UpdateMethod", "ClientInitiated"); add_text_node(ctx, upd, "Restriction", "HomeSP"); add_text_node_conf(ctx, realm, upd, "URI", "spp_http_auth_url"); trust = xml_node_create(ctx->xml, upd, NULL, "TrustRoot"); add_text_node_conf(ctx, realm, trust, "CertURL", "trust_root_cert_url"); add_text_node_conf(ctx, realm, trust, "CertSHA256Fingerprint", "trust_root_cert_fingerprint"); homesp = xml_node_create(ctx->xml, c, NULL, "HomeSP"); add_text_node_conf(ctx, realm, homesp, "FriendlyName", "friendly_name"); add_text_node_conf(ctx, realm, homesp, "FQDN", "fqdn"); xml_node_create(ctx->xml, c, NULL, "SubscriptionParameters"); cred = xml_node_create(ctx->xml, c, NULL, "Credential"); add_creation_date(ctx, cred); if (cert) { xml_node_t *dc; dc = xml_node_create(ctx->xml, cred, NULL, "DigitalCertificate"); add_text_node(ctx, dc, "CertificateType", "x509v3"); add_text_node(ctx, dc, "CertSHA256Fingerprint", cert); } else { userpw = build_username_password(ctx, cred, user, pw); add_text_node(ctx, userpw, "MachineManaged", machine_managed ? "TRUE" : "FALSE"); eap = xml_node_create(ctx->xml, userpw, NULL, "EAPMethod"); add_text_node(ctx, eap, "EAPType", "21"); add_text_node(ctx, eap, "InnerMethod", "MS-CHAP-V2"); } add_text_node(ctx, cred, "Realm", realm); return pps; } static xml_node_t * spp_exec_get_certificate(struct hs20_svc *ctx, const char *session_id, const char *user, const char *realm) { xml_namespace_t *ns; xml_node_t *spp_node, *enroll, *exec_node; char *val; char password[11]; char *b64; if (new_password(password, sizeof(password)) < 0) return NULL; spp_node = build_post_dev_data_response(ctx, &ns, session_id, "OK", NULL); if (spp_node == NULL) return NULL; exec_node = xml_node_create(ctx->xml, spp_node, ns, "exec"); enroll = xml_node_create(ctx->xml, exec_node, ns, "getCertificate"); xml_node_add_attr(ctx->xml, enroll, NULL, "enrollmentProtocol", "EST"); val = db_get_osu_config_val(ctx, realm, "est_url"); xml_node_create_text(ctx->xml, enroll, ns, "enrollmentServerURI", val ? val : ""); os_free(val); xml_node_create_text(ctx->xml, enroll, ns, "estUserID", user); b64 = (char *) base64_encode((unsigned char *) password, strlen(password), NULL); if (b64 == NULL) { xml_node_free(ctx->xml, spp_node); return NULL; } xml_node_create_text(ctx->xml, enroll, ns, "estPassword", b64); free(b64); db_update_session_password(ctx, user, realm, session_id, password); return spp_node; } static xml_node_t * hs20_user_input_registration(struct hs20_svc *ctx, const char *session_id, int enrollment_done) { xml_namespace_t *ns; xml_node_t *spp_node, *node = NULL; xml_node_t *pps, *tnds; char buf[400]; char *str; char *user, *realm, *pw, *type, *mm; const char *status; int cert = 0; int machine_managed = 0; char *fingerprint; user = db_get_session_val(ctx, NULL, NULL, session_id, "user"); realm = db_get_session_val(ctx, NULL, NULL, session_id, "realm"); pw = db_get_session_val(ctx, NULL, NULL, session_id, "password"); if (!user || !realm || !pw) { debug_print(ctx, 1, "Could not find session info from DB for " "the new subscription"); free(user); free(realm); free(pw); return NULL; } mm = db_get_session_val(ctx, NULL, NULL, session_id, "machine_managed"); if (mm && atoi(mm)) machine_managed = 1; free(mm); type = db_get_session_val(ctx, NULL, NULL, session_id, "type"); if (type && strcmp(type, "cert") == 0) cert = 1; free(type); if (cert && !enrollment_done) { xml_node_t *ret; hs20_eventlog(ctx, user, realm, session_id, "request client certificate enrollment", NULL); ret = spp_exec_get_certificate(ctx, session_id, user, realm); free(user); free(realm); free(pw); return ret; } if (!cert && strlen(pw) == 0) { machine_managed = 1; free(pw); pw = malloc(11); if (pw == NULL || new_password(pw, 11) < 0) { free(user); free(realm); free(pw); return NULL; } } status = "Provisioning complete, request sppUpdateResponse"; spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, NULL); if (spp_node == NULL) return NULL; fingerprint = db_get_session_val(ctx, NULL, NULL, session_id, "cert"); pps = build_pps(ctx, user, realm, pw, fingerprint ? fingerprint : NULL, machine_managed); free(fingerprint); if (!pps) { xml_node_free(ctx->xml, spp_node); free(user); free(realm); free(pw); return NULL; } debug_print(ctx, 1, "Request DB subscription registration on success " "notification"); if (machine_managed) { db_update_session_password(ctx, user, realm, session_id, pw); db_update_session_machine_managed(ctx, user, realm, session_id, machine_managed); } db_add_session_pps(ctx, user, realm, session_id, pps); hs20_eventlog_node(ctx, user, realm, session_id, "new subscription", pps); free(user); free(pw); tnds = mo_to_tnds(ctx->xml, pps, 0, URN_HS20_PPS, NULL); xml_node_free(ctx->xml, pps); if (!tnds) { xml_node_free(ctx->xml, spp_node); free(realm); return NULL; } str = xml_node_to_str(ctx->xml, tnds); xml_node_free(ctx->xml, tnds); if (str == NULL) { xml_node_free(ctx->xml, spp_node); free(realm); return NULL; } node = xml_node_create_text(ctx->xml, spp_node, ns, "addMO", str); free(str); snprintf(buf, sizeof(buf), "./Wi-Fi/%s/PerProviderSubscription", realm); free(realm); xml_node_add_attr(ctx->xml, node, ns, "managementTreeURI", buf); xml_node_add_attr(ctx->xml, node, ns, "moURN", URN_HS20_PPS); return spp_node; } static xml_node_t * hs20_user_input_free_remediation(struct hs20_svc *ctx, const char *user, const char *realm, const char *session_id) { xml_namespace_t *ns; xml_node_t *spp_node; xml_node_t *cred; char buf[400]; char *status; char *free_account, *pw; free_account = db_get_osu_config_val(ctx, realm, "free_account"); if (free_account == NULL) return NULL; pw = db_get_val(ctx, free_account, realm, "password", 0); if (pw == NULL) { free(free_account); return NULL; } cred = build_credential_pw(ctx, free_account, realm, pw); free(free_account); free(pw); if (!cred) { xml_node_free(ctx->xml, cred); return NULL; } status = "Remediation complete, request sppUpdateResponse"; spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, NULL); if (spp_node == NULL) return NULL; snprintf(buf, sizeof(buf), "./Wi-Fi/%s/PerProviderSubscription/Credential1/Credential", realm); if (add_update_node(ctx, spp_node, ns, buf, cred) < 0) { xml_node_free(ctx->xml, spp_node); return NULL; } hs20_eventlog_node(ctx, user, realm, session_id, "free/public remediation", cred); xml_node_free(ctx->xml, cred); return spp_node; } static xml_node_t * hs20_user_input_complete(struct hs20_svc *ctx, const char *user, const char *realm, int dmacc, const char *session_id) { char *val; enum hs20_session_operation oper; val = db_get_session_val(ctx, user, realm, session_id, "operation"); if (val == NULL) { debug_print(ctx, 1, "No session %s found to continue", session_id); return NULL; } oper = atoi(val); free(val); if (oper == USER_REMEDIATION) { return hs20_user_input_remediation(ctx, user, realm, dmacc, session_id); } if (oper == FREE_REMEDIATION) { return hs20_user_input_free_remediation(ctx, user, realm, session_id); } if (oper == SUBSCRIPTION_REGISTRATION) { return hs20_user_input_registration(ctx, session_id, 0); } debug_print(ctx, 1, "User session %s not in state for user input " "completion", session_id); return NULL; } static xml_node_t * hs20_cert_enroll_completed(struct hs20_svc *ctx, const char *user, const char *realm, int dmacc, const char *session_id) { char *val; enum hs20_session_operation oper; val = db_get_session_val(ctx, user, realm, session_id, "operation"); if (val == NULL) { debug_print(ctx, 1, "No session %s found to continue", session_id); return NULL; } oper = atoi(val); free(val); if (oper == SUBSCRIPTION_REGISTRATION) return hs20_user_input_registration(ctx, session_id, 1); debug_print(ctx, 1, "User session %s not in state for certificate " "enrollment completion", session_id); return NULL; } static xml_node_t * hs20_cert_enroll_failed(struct hs20_svc *ctx, const char *user, const char *realm, int dmacc, const char *session_id) { char *val; enum hs20_session_operation oper; xml_node_t *spp_node, *node; char *status; xml_namespace_t *ns; val = db_get_session_val(ctx, user, realm, session_id, "operation"); if (val == NULL) { debug_print(ctx, 1, "No session %s found to continue", session_id); return NULL; } oper = atoi(val); free(val); if (oper != SUBSCRIPTION_REGISTRATION) { debug_print(ctx, 1, "User session %s not in state for " "enrollment failure", session_id); return NULL; } status = "Error occurred"; spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, NULL); if (spp_node == NULL) return NULL; node = xml_node_create(ctx->xml, spp_node, ns, "sppError"); xml_node_add_attr(ctx->xml, node, NULL, "errorCode", "Credentials cannot be provisioned at this time"); db_remove_session(ctx, user, realm, session_id); return spp_node; } static xml_node_t * hs20_spp_post_dev_data(struct hs20_svc *ctx, xml_node_t *node, const char *user, const char *realm, const char *session_id, int dmacc) { const char *req_reason; char *redirect_uri = NULL; char *req_reason_buf = NULL; char str[200]; xml_node_t *ret = NULL, *devinfo = NULL, *devdetail = NULL; xml_node_t *mo; char *version; int valid; char *supp, *pos; char *err; version = xml_node_get_attr_value_ns(ctx->xml, node, SPP_NS_URI, "sppVersion"); if (version == NULL || strstr(version, "1.0") == NULL) { ret = build_post_dev_data_response( ctx, NULL, session_id, "Error occurred", "SPP version not supported"); hs20_eventlog_node(ctx, user, realm, session_id, "Unsupported sppVersion", ret); xml_node_get_attr_value_free(ctx->xml, version); return ret; } xml_node_get_attr_value_free(ctx->xml, version); mo = get_node(ctx->xml, node, "supportedMOList"); if (mo == NULL) { ret = build_post_dev_data_response( ctx, NULL, session_id, "Error occurred", "Other"); hs20_eventlog_node(ctx, user, realm, session_id, "No supportedMOList element", ret); return ret; } supp = xml_node_get_text(ctx->xml, mo); for (pos = supp; pos && *pos; pos++) *pos = tolower(*pos); if (supp == NULL || strstr(supp, URN_OMA_DM_DEVINFO) == NULL || strstr(supp, URN_OMA_DM_DEVDETAIL) == NULL || strstr(supp, URN_HS20_PPS) == NULL) { xml_node_get_text_free(ctx->xml, supp); ret = build_post_dev_data_response( ctx, NULL, session_id, "Error occurred", "One or more mandatory MOs not supported"); hs20_eventlog_node(ctx, user, realm, session_id, "Unsupported MOs", ret); return ret; } xml_node_get_text_free(ctx->xml, supp); req_reason_buf = xml_node_get_attr_value(ctx->xml, node, "requestReason"); if (req_reason_buf == NULL) { debug_print(ctx, 1, "No requestReason attribute"); return NULL; } req_reason = req_reason_buf; redirect_uri = xml_node_get_attr_value(ctx->xml, node, "redirectURI"); debug_print(ctx, 1, "requestReason: %s sessionID: %s redirectURI: %s", req_reason, session_id, redirect_uri); snprintf(str, sizeof(str), "sppPostDevData: requestReason=%s", req_reason); hs20_eventlog(ctx, user, realm, session_id, str, NULL); devinfo = spp_get_mo(ctx, node, URN_OMA_DM_DEVINFO, &valid, &err); if (devinfo == NULL) { ret = build_post_dev_data_response(ctx, NULL, session_id, "Error occurred", "Other"); hs20_eventlog_node(ctx, user, realm, session_id, "No DevInfo moContainer in sppPostDevData", ret); os_free(err); goto out; } hs20_eventlog_node(ctx, user, realm, session_id, "Received DevInfo MO", devinfo); if (valid == 0) { hs20_eventlog(ctx, user, realm, session_id, "OMA-DM DDF DTD validation errors in DevInfo MO", err); ret = build_post_dev_data_response(ctx, NULL, session_id, "Error occurred", "Other"); os_free(err); goto out; } os_free(err); if (user) db_update_mo(ctx, user, realm, "devinfo", devinfo); devdetail = spp_get_mo(ctx, node, URN_OMA_DM_DEVDETAIL, &valid, &err); if (devdetail == NULL) { ret = build_post_dev_data_response(ctx, NULL, session_id, "Error occurred", "Other"); hs20_eventlog_node(ctx, user, realm, session_id, "No DevDetail moContainer in sppPostDevData", ret); os_free(err); goto out; } hs20_eventlog_node(ctx, user, realm, session_id, "Received DevDetail MO", devdetail); if (valid == 0) { hs20_eventlog(ctx, user, realm, session_id, "OMA-DM DDF DTD validation errors " "in DevDetail MO", err); ret = build_post_dev_data_response(ctx, NULL, session_id, "Error occurred", "Other"); os_free(err); goto out; } os_free(err); if (user) db_update_mo(ctx, user, realm, "devdetail", devdetail); if (user) mo = spp_get_mo(ctx, node, URN_HS20_PPS, &valid, &err); else { mo = NULL; err = NULL; } if (user && mo) { hs20_eventlog_node(ctx, user, realm, session_id, "Received PPS MO", mo); if (valid == 0) { hs20_eventlog(ctx, user, realm, session_id, "OMA-DM DDF DTD validation errors " "in PPS MO", err); xml_node_get_attr_value_free(ctx->xml, redirect_uri); os_free(err); return build_post_dev_data_response( ctx, NULL, session_id, "Error occurred", "Other"); } db_update_mo(ctx, user, realm, "pps", mo); db_update_val(ctx, user, realm, "fetch_pps", "0", dmacc); xml_node_free(ctx->xml, mo); } os_free(err); if (user && !mo) { char *fetch; int fetch_pps; fetch = db_get_val(ctx, user, realm, "fetch_pps", dmacc); fetch_pps = fetch ? atoi(fetch) : 0; free(fetch); if (fetch_pps) { enum hs20_session_operation oper; if (strcasecmp(req_reason, "Subscription remediation") == 0) oper = CONTINUE_SUBSCRIPTION_REMEDIATION; else if (strcasecmp(req_reason, "Policy update") == 0) oper = CONTINUE_POLICY_UPDATE; else oper = NO_OPERATION; if (db_add_session(ctx, user, realm, session_id, NULL, NULL, oper) < 0) goto out; ret = spp_exec_upload_mo(ctx, session_id, URN_HS20_PPS); hs20_eventlog_node(ctx, user, realm, session_id, "request PPS MO upload", ret); goto out; } } if (user && strcasecmp(req_reason, "MO upload") == 0) { char *val = db_get_session_val(ctx, user, realm, session_id, "operation"); enum hs20_session_operation oper; if (!val) { debug_print(ctx, 1, "No session %s found to continue", session_id); goto out; } oper = atoi(val); free(val); if (oper == CONTINUE_SUBSCRIPTION_REMEDIATION) req_reason = "Subscription remediation"; else if (oper == CONTINUE_POLICY_UPDATE) req_reason = "Policy update"; else { debug_print(ctx, 1, "No pending operation in session %s", session_id); goto out; } } if (strcasecmp(req_reason, "Subscription registration") == 0) { ret = hs20_subscription_registration(ctx, realm, session_id, redirect_uri); hs20_eventlog_node(ctx, user, realm, session_id, "subscription registration response", ret); goto out; } if (user && strcasecmp(req_reason, "Subscription remediation") == 0) { ret = hs20_subscription_remediation(ctx, user, realm, session_id, dmacc, redirect_uri); hs20_eventlog_node(ctx, user, realm, session_id, "subscription remediation response", ret); goto out; } if (user && strcasecmp(req_reason, "Policy update") == 0) { ret = hs20_policy_update(ctx, user, realm, session_id, dmacc); hs20_eventlog_node(ctx, user, realm, session_id, "policy update response", ret); goto out; } if (strcasecmp(req_reason, "User input completed") == 0) { db_add_session_devinfo(ctx, session_id, devinfo); db_add_session_devdetail(ctx, session_id, devdetail); ret = hs20_user_input_complete(ctx, user, realm, dmacc, session_id); hs20_eventlog_node(ctx, user, realm, session_id, "user input completed response", ret); goto out; } if (strcasecmp(req_reason, "Certificate enrollment completed") == 0) { ret = hs20_cert_enroll_completed(ctx, user, realm, dmacc, session_id); hs20_eventlog_node(ctx, user, realm, session_id, "certificate enrollment response", ret); goto out; } if (strcasecmp(req_reason, "Certificate enrollment failed") == 0) { ret = hs20_cert_enroll_failed(ctx, user, realm, dmacc, session_id); hs20_eventlog_node(ctx, user, realm, session_id, "certificate enrollment failed response", ret); goto out; } debug_print(ctx, 1, "Unsupported requestReason '%s' user '%s'", req_reason, user); out: xml_node_get_attr_value_free(ctx->xml, req_reason_buf); xml_node_get_attr_value_free(ctx->xml, redirect_uri); if (devinfo) xml_node_free(ctx->xml, devinfo); if (devdetail) xml_node_free(ctx->xml, devdetail); return ret; } static xml_node_t * build_spp_exchange_complete(struct hs20_svc *ctx, const char *session_id, const char *status, const char *error_code) { xml_namespace_t *ns; xml_node_t *spp_node, *node; spp_node = xml_node_create_root(ctx->xml, SPP_NS_URI, "spp", &ns, "sppExchangeComplete"); xml_node_add_attr(ctx->xml, spp_node, ns, "sppVersion", "1.0"); xml_node_add_attr(ctx->xml, spp_node, ns, "sessionID", session_id); xml_node_add_attr(ctx->xml, spp_node, ns, "sppStatus", status); if (error_code) { node = xml_node_create(ctx->xml, spp_node, ns, "sppError"); xml_node_add_attr(ctx->xml, node, NULL, "errorCode", error_code); } return spp_node; } static int add_subscription(struct hs20_svc *ctx, const char *session_id) { char *user, *realm, *pw, *pw_mm, *pps, *str; char *sql; int ret = -1; char *free_account; int free_acc; char *type; int cert = 0; char *cert_pem, *fingerprint; user = db_get_session_val(ctx, NULL, NULL, session_id, "user"); realm = db_get_session_val(ctx, NULL, NULL, session_id, "realm"); pw = db_get_session_val(ctx, NULL, NULL, session_id, "password"); pw_mm = db_get_session_val(ctx, NULL, NULL, session_id, "machine_managed"); pps = db_get_session_val(ctx, NULL, NULL, session_id, "pps"); cert_pem = db_get_session_val(ctx, NULL, NULL, session_id, "cert_pem"); fingerprint = db_get_session_val(ctx, NULL, NULL, session_id, "cert"); type = db_get_session_val(ctx, NULL, NULL, session_id, "type"); if (type && strcmp(type, "cert") == 0) cert = 1; free(type); if (!user || !realm || !pw) { debug_print(ctx, 1, "Could not find session info from DB for " "the new subscription"); goto out; } free_account = db_get_osu_config_val(ctx, realm, "free_account"); free_acc = free_account && strcmp(free_account, user) == 0; free(free_account); debug_print(ctx, 1, "New subscription: user='%s' realm='%s' free_acc=%d", user, realm, free_acc); debug_print(ctx, 1, "New subscription: pps='%s'", pps); sql = sqlite3_mprintf("UPDATE eventlog SET user=%Q, realm=%Q WHERE " "sessionid=%Q AND (user='' OR user IS NULL)", user, realm, session_id); if (sql) { debug_print(ctx, 1, "DB: %s", sql); if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { debug_print(ctx, 1, "Failed to update eventlog in " "sqlite database: %s", sqlite3_errmsg(ctx->db)); } sqlite3_free(sql); } if (free_acc) { hs20_eventlog(ctx, user, realm, session_id, "completed shared free account registration", NULL); ret = 0; goto out; } 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; }