diff --git a/hostapd/config_file.c b/hostapd/config_file.c index 0452b7872..fd2f4e9dd 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -3817,6 +3817,9 @@ static int hostapd_config_fill(struct hostapd_config *conf, } else if (os_strcmp(buf, "hs20_t_c_server_url") == 0) { os_free(bss->t_c_server_url); bss->t_c_server_url = os_strdup(pos); + } else if (os_strcmp(buf, "hs20_sim_provisioning_url") == 0) { + os_free(bss->hs20_sim_provisioning_url); + bss->hs20_sim_provisioning_url = os_strdup(pos); #endif /* CONFIG_HS20 */ #ifdef CONFIG_MBO } else if (os_strcmp(buf, "mbo") == 0) { diff --git a/hs20/server/sql.txt b/hs20/server/sql.txt index 666ef1314..281a436de 100644 --- a/hs20/server/sql.txt +++ b/hs20/server/sql.txt @@ -93,3 +93,11 @@ CREATE TABLE cert_enroll( realm TEXT, serialnum TEXT ); + +CREATE TABLE sim_provisioning( + mobile_identifier_hash TEXT PRIMARY KEY, + imsi TEXT, + mac_addr TEXT, + eap_method TEXT, + timestamp TEXT +); diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c index cf96eb4fd..d2482c8b6 100644 --- a/src/ap/ap_config.c +++ b/src/ap/ap_config.c @@ -649,6 +649,7 @@ void hostapd_config_free_bss(struct hostapd_bss_config *conf) os_free(conf->hs20_operator_icon); } os_free(conf->subscr_remediation_url); + os_free(conf->hs20_sim_provisioning_url); os_free(conf->t_c_filename); os_free(conf->t_c_server_url); #endif /* CONFIG_HS20 */ diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index bd2b96791..555c3719c 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -597,6 +597,7 @@ struct hostapd_bss_config { unsigned int hs20_deauth_req_timeout; char *subscr_remediation_url; u8 subscr_remediation_method; + char *hs20_sim_provisioning_url; char *t_c_filename; u32 t_c_timestamp; char *t_c_server_url; diff --git a/src/ap/authsrv.c b/src/ap/authsrv.c index 95d004ed2..3702eb17e 100644 --- a/src/ap/authsrv.c +++ b/src/ap/authsrv.c @@ -136,6 +136,7 @@ static int hostapd_setup_radius_srv(struct hostapd_data *hapd) #ifdef CONFIG_HS20 srv.subscr_remediation_url = conf->subscr_remediation_url; srv.subscr_remediation_method = conf->subscr_remediation_method; + srv.hs20_sim_provisioning_url = conf->hs20_sim_provisioning_url; srv.t_c_server_url = conf->t_c_server_url; #endif /* CONFIG_HS20 */ srv.erp = conf->eap_server_erp; diff --git a/src/ap/eap_user_db.c b/src/ap/eap_user_db.c index 296d5c2dd..1c26fe3b6 100644 --- a/src/ap/eap_user_db.c +++ b/src/ap/eap_user_db.c @@ -174,6 +174,7 @@ eap_user_sqlite_get(struct hostapd_data *hapd, const u8 *identity, if (hapd->tmp_eap_user.identity == NULL) return NULL; os_memcpy(hapd->tmp_eap_user.identity, identity, identity_len); + hapd->tmp_eap_user.identity_len = identity_len; if (sqlite3_open(hapd->conf->eap_user_sqlite, &db)) { wpa_printf(MSG_INFO, "DB: Failed to open database %s: %s", diff --git a/src/radius/radius_server.c b/src/radius/radius_server.c index e3afc0d53..aa78cbae6 100644 --- a/src/radius/radius_server.c +++ b/src/radius/radius_server.c @@ -357,6 +357,7 @@ struct radius_server_data { char *subscr_remediation_url; u8 subscr_remediation_method; + char *hs20_sim_provisioning_url; char *t_c_server_url; @@ -380,6 +381,44 @@ static void radius_server_session_timeout(void *eloop_ctx, void *timeout_ctx); static void radius_server_session_remove_timeout(void *eloop_ctx, void *timeout_ctx); +#ifdef CONFIG_SQLITE +#ifdef CONFIG_HS20 + +static int db_table_exists(sqlite3 *db, const char *name) +{ + char cmd[128]; + + os_snprintf(cmd, sizeof(cmd), "SELECT 1 FROM %s;", name); + return sqlite3_exec(db, cmd, NULL, NULL, NULL) == SQLITE_OK; +} + + +static int db_table_create_sim_provisioning(sqlite3 *db) +{ + char *err = NULL; + const char *sql = + "CREATE TABLE sim_provisioning(" + " mobile_identifier_hash TEXT PRIMARY KEY," + " imsi TEXT," + " mac_addr TEXT," + " eap_method TEXT," + " timestamp TEXT" + ");"; + + RADIUS_DEBUG("Adding database table for SIM provisioning information"); + if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) { + RADIUS_ERROR("SQLite error: %s", err); + sqlite3_free(err); + return -1; + } + + return 0; +} + +#endif /* CONFIG_HS20 */ +#endif /* CONFIG_SQLITE */ + + void srv_log(struct radius_session *sess, const char *fmt, ...) PRINTF_FORMAT(2, 3); @@ -866,6 +905,117 @@ static void db_update_last_msk(struct radius_session *sess, const char *msk) } +#ifdef CONFIG_HS20 + +static int radius_server_is_sim_method(struct radius_session *sess) +{ + const char *name; + + name = eap_get_method(sess->eap); + return name && + (os_strcmp(name, "SIM") == 0 || + os_strcmp(name, "AKA") == 0 || + os_strcmp(name, "AKA'") == 0); +} + + +static int radius_server_hs20_missing_sim_pps(struct radius_msg *request) +{ + u8 *buf, *pos, *end, type, sublen; + size_t len; + + buf = NULL; + for (;;) { + if (radius_msg_get_attr_ptr(request, + RADIUS_ATTR_VENDOR_SPECIFIC, + &buf, &len, buf) < 0) + return 0; + if (len < 6) + continue; + pos = buf; + end = buf + len; + if (WPA_GET_BE32(pos) != RADIUS_VENDOR_ID_WFA) + continue; + pos += 4; + + type = *pos++; + sublen = *pos++; + if (sublen < 2) + continue; /* invalid length */ + sublen -= 2; /* skip header */ + if (pos + sublen > end) + continue; /* invalid WFA VSA */ + + if (type != RADIUS_VENDOR_ATTR_WFA_HS20_STA_VERSION) + continue; + + RADIUS_DUMP("HS2.0 mobile device version", pos, sublen); + if (sublen < 1 + 2) + continue; + if (pos[0] == 0) + continue; /* Release 1 STA does not support provisioning + + */ + /* UpdateIdentifier 0 indicates no PPS MO */ + return WPA_GET_BE16(pos + 1) == 0; + } +} + + +#define HS20_MOBILE_ID_HASH_LEN 16 + +static int radius_server_sim_provisioning_session(struct radius_session *sess, + const u8 *hash) +{ +#ifdef CONFIG_SQLITE + char *sql; + char addr_txt[ETH_ALEN * 3]; + char hash_txt[2 * HS20_MOBILE_ID_HASH_LEN + 1]; + struct os_time now; + int res; + const char *imsi, *eap_method; + + if (!sess->server->db || + (!db_table_exists(sess->server->db, "sim_provisioning") && + db_table_create_sim_provisioning(sess->server->db) < 0)) + return -1; + + imsi = eap_get_imsi(sess->eap); + if (!imsi) + return -1; + + eap_method = eap_get_method(sess->eap); + if (!eap_method) + return -1; + + os_snprintf(addr_txt, sizeof(addr_txt), MACSTR, + MAC2STR(sess->mac_addr)); + wpa_snprintf_hex(hash_txt, sizeof(hash_txt), hash, + HS20_MOBILE_ID_HASH_LEN); + + os_get_time(&now); + sql = sqlite3_mprintf("INSERT INTO sim_provisioning(mobile_identifier_hash,imsi,mac_addr,eap_method,timestamp) VALUES (%Q,%Q,%Q,%Q,%u)", + hash_txt, imsi, addr_txt, eap_method, now.sec); + if (!sql) + return -1; + + if (sqlite3_exec(sess->server->db, sql, NULL, NULL, NULL) != + SQLITE_OK) { + RADIUS_ERROR("Failed to add SIM provisioning entry into sqlite database: %s", + sqlite3_errmsg(sess->server->db)); + res = -1; + } else { + res = 0; + } + sqlite3_free(sql); + return res; +#endif /* CONFIG_SQLITE */ + return -1; +} + +#endif /* CONFIG_HS20 */ + + static struct radius_msg * radius_server_encapsulate_eap(struct radius_server_data *data, struct radius_client *client, @@ -979,6 +1129,48 @@ radius_server_encapsulate_eap(struct radius_server_data *data, buf, 0)) { RADIUS_DEBUG("Failed to add WFA-HS20-SubscrRem"); } + } else if (code == RADIUS_CODE_ACCESS_ACCEPT && + data->hs20_sim_provisioning_url && + radius_server_is_sim_method(sess) && + radius_server_hs20_missing_sim_pps(request)) { + u8 *buf, *pos, hash[HS20_MOBILE_ID_HASH_LEN]; + size_t prefix_len, url_len; + + RADIUS_DEBUG("Device needs HS 2.0 SIM provisioning"); + + if (os_get_random(hash, HS20_MOBILE_ID_HASH_LEN) < 0) { + radius_msg_free(msg); + return NULL; + } + RADIUS_DUMP("hotspot2dot0-mobile-identifier-hash", + hash, HS20_MOBILE_ID_HASH_LEN); + + if (radius_server_sim_provisioning_session(sess, hash) < 0) { + radius_msg_free(msg); + return NULL; + } + + prefix_len = os_strlen(data->hs20_sim_provisioning_url); + url_len = prefix_len + 2 * HS20_MOBILE_ID_HASH_LEN; + buf = os_malloc(1 + url_len + 1); + if (!buf) { + radius_msg_free(msg); + return NULL; + } + pos = buf; + *pos++ = data->subscr_remediation_method; + os_memcpy(pos, data->hs20_sim_provisioning_url, prefix_len); + pos += prefix_len; + wpa_snprintf_hex((char *) pos, 2 * HS20_MOBILE_ID_HASH_LEN + 1, + hash, HS20_MOBILE_ID_HASH_LEN); + RADIUS_DEBUG("HS 2.0 subscription remediation URL: %s", + (char *) &buf[1]); + if (!radius_msg_add_wfa( + msg, RADIUS_VENDOR_ATTR_WFA_HS20_SUBSCR_REMEDIATION, + buf, 1 + url_len)) { + RADIUS_DEBUG("Failed to add WFA-HS20-SubscrRem"); + } + os_free(buf); } if (code == RADIUS_CODE_ACCESS_ACCEPT && sess->t_c_filtering) { @@ -2173,6 +2365,9 @@ radius_server_init(struct radius_server_conf *conf) os_strdup(conf->subscr_remediation_url); } data->subscr_remediation_method = conf->subscr_remediation_method; + if (conf->hs20_sim_provisioning_url) + data->hs20_sim_provisioning_url = + os_strdup(conf->hs20_sim_provisioning_url); if (conf->t_c_server_url) data->t_c_server_url = os_strdup(conf->t_c_server_url); @@ -2293,6 +2488,7 @@ void radius_server_deinit(struct radius_server_data *data) os_free(data->dump_msk_file); #endif /* CONFIG_RADIUS_TEST */ os_free(data->subscr_remediation_url); + os_free(data->hs20_sim_provisioning_url); os_free(data->t_c_server_url); #ifdef CONFIG_SQLITE diff --git a/src/radius/radius_server.h b/src/radius/radius_server.h index 167bbf5b2..53728f9d7 100644 --- a/src/radius/radius_server.h +++ b/src/radius/radius_server.h @@ -233,6 +233,7 @@ struct radius_server_conf { char *subscr_remediation_url; u8 subscr_remediation_method; + char *hs20_sim_provisioning_url; char *t_c_server_url; };