diff --git a/sql/hlr.sql b/sql/hlr.sql index 9ff98679..10838f21 100644 --- a/sql/hlr.sql +++ b/sql/hlr.sql @@ -5,8 +5,10 @@ CREATE TABLE subscriber ( imsi VARCHAR(15) UNIQUE NOT NULL, -- Chapter 2.1.2 msisdn VARCHAR(15) UNIQUE, - -- Chapter 2.2.3: Most recent / current IMEI + -- Chapter 2.2.3: Most recent / current IMEISV imeisv VARCHAR, + -- Chapter 2.1.9: Most recent / current IMEI + imei VARCHAR(14), -- Chapter 2.4.5 vlr_number VARCHAR(15), -- Chapter 2.4.6 @@ -74,4 +76,4 @@ CREATE UNIQUE INDEX idx_subscr_imsi ON subscriber (imsi); -- Set HLR database schema version number -- Note: This constant is currently duplicated in src/db.c and must be kept in sync! -PRAGMA user_version = 1; +PRAGMA user_version = 2; diff --git a/src/db.c b/src/db.c index f7cbacef..09e17766 100644 --- a/src/db.c +++ b/src/db.c @@ -28,12 +28,13 @@ #include "db_bootstrap.h" /* This constant is currently duplicated in sql/hlr.sql and must be kept in sync! */ -#define CURRENT_SCHEMA_VERSION 1 +#define CURRENT_SCHEMA_VERSION 2 #define SEL_COLUMNS \ "id," \ "imsi," \ "msisdn," \ + "imei," \ "vlr_number," \ "sgsn_number," \ "sgsn_address," \ @@ -50,8 +51,10 @@ static const char *stmt_sql[] = { [DB_STMT_SEL_BY_IMSI] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE imsi = ?", [DB_STMT_SEL_BY_MSISDN] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE msisdn = ?", [DB_STMT_SEL_BY_ID] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE id = ?", + [DB_STMT_SEL_BY_IMEI] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE imei = ?", [DB_STMT_UPD_VLR_BY_ID] = "UPDATE subscriber SET vlr_number = $number WHERE id = $subscriber_id", [DB_STMT_UPD_SGSN_BY_ID] = "UPDATE subscriber SET sgsn_number = $number WHERE id = $subscriber_id", + [DB_STMT_UPD_IMEI_BY_IMSI] = "UPDATE subscriber SET imei = $imei WHERE imsi = $imsi", [DB_STMT_AUC_BY_IMSI] = "SELECT id, algo_id_2g, ki, algo_id_3g, k, op, opc, sqn, ind_bitlen" " FROM subscriber" @@ -290,6 +293,40 @@ db_upgrade_v1(struct db_context *dbc) return rc; } +static int db_upgrade_v2(struct db_context *dbc) +{ + sqlite3_stmt *stmt; + int rc; + const char *update_stmt_sql = "ALTER TABLE subscriber ADD COLUMN imei VARCHAR(14) default NULL"; + const char *set_schema_version_sql = "PRAGMA user_version = 2"; + + rc = sqlite3_prepare_v2(dbc->db, update_stmt_sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", update_stmt_sql); + return rc; + } + rc = sqlite3_step(stmt); + db_remove_reset(stmt); + sqlite3_finalize(stmt); + if (rc != SQLITE_DONE) { + LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version %d\n", 1); + return rc; + } + + rc = sqlite3_prepare_v2(dbc->db, set_schema_version_sql, -1, &stmt, NULL); + if (rc != SQLITE_OK) { + LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", set_schema_version_sql); + return rc; + } + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) + LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version %d\n", 1); + + db_remove_reset(stmt); + sqlite3_finalize(stmt); + return rc; +} + static int db_get_user_version(struct db_context *dbc) { const char *user_version_sql = "PRAGMA user_version"; @@ -400,6 +437,15 @@ struct db_context *db_open(void *ctx, const char *fname, bool enable_sqlite_logg } version = 1; /* fall through */ + case 1: + rc = db_upgrade_v2(dbc); + if (rc != SQLITE_DONE) { + LOGP(DDB, LOGL_ERROR, "Failed to upgrade HLR DB schema to version 2: (rc=%d) %s\n", + rc, sqlite3_errmsg(dbc->db)); + goto out_free; + } + version = 2; + /* fall through */ /* case N: ... */ default: break; diff --git a/src/db.h b/src/db.h index ae592fb2..c438b8d5 100644 --- a/src/db.h +++ b/src/db.h @@ -9,8 +9,10 @@ enum stmt_idx { DB_STMT_SEL_BY_IMSI, DB_STMT_SEL_BY_MSISDN, DB_STMT_SEL_BY_ID, + DB_STMT_SEL_BY_IMEI, DB_STMT_UPD_VLR_BY_ID, DB_STMT_UPD_SGSN_BY_ID, + DB_STMT_UPD_IMEI_BY_IMSI, DB_STMT_AUC_BY_IMSI, DB_STMT_AUC_UPD_SQN, DB_STMT_UPD_PURGE_CS_BY_IMSI, @@ -71,6 +73,7 @@ struct hlr_subscriber { char imsi[GSM23003_IMSI_MAX_DIGITS+1]; char msisdn[GT_MAX_DIGITS+1]; /* imeisv? */ + char imei[GSM23003_IMEI_NUM_DIGITS+1]; char vlr_number[32]; char sgsn_number[32]; char sgsn_address[GT_MAX_DIGITS+1]; @@ -122,6 +125,7 @@ int db_subscr_update_msisdn_by_imsi(struct db_context *dbc, const char *imsi, const char *msisdn); int db_subscr_update_aud_by_id(struct db_context *dbc, int64_t subscr_id, const struct sub_auth_data_str *aud); +int db_subscr_update_imei_by_imsi(struct db_context *dbc, const char* imsi, const char *imei); int db_subscr_get_by_imsi(struct db_context *dbc, const char *imsi, struct hlr_subscriber *subscr); @@ -129,6 +133,7 @@ int db_subscr_get_by_msisdn(struct db_context *dbc, const char *msisdn, struct hlr_subscriber *subscr); int db_subscr_get_by_id(struct db_context *dbc, int64_t id, struct hlr_subscriber *subscr); +int db_subscr_get_by_imei(struct db_context *dbc, const char *imei, struct hlr_subscriber *subscr); int db_subscr_nam(struct db_context *dbc, const char *imsi, bool nam_val, bool is_ps); int db_subscr_lu(struct db_context *dbc, int64_t subscr_id, const char *vlr_or_sgsn_number, bool is_ps); diff --git a/src/db_hlr.c b/src/db_hlr.c index 25694770..3ba457c1 100644 --- a/src/db_hlr.c +++ b/src/db_hlr.c @@ -386,6 +386,53 @@ out: return ret; } +/*! Set a subscriber's IMEI in the HLR database. + * \param[in,out] dbc database context. + * \param[in] imsi ASCII string of IMSI digits + * \param[in] imei ASCII string of identifier digits, or NULL to remove the IMEI. + * \returns 0 on success, -ENOENT when the given subscriber does not exist, + * -EIO on database errors. + */ +int db_subscr_update_imei_by_imsi(struct db_context *dbc, const char* imsi, const char *imei) +{ + int rc, ret = 0; + sqlite3_stmt *stmt = dbc->stmt[DB_STMT_UPD_IMEI_BY_IMSI]; + + if (imei && !osmo_imei_str_valid(imei, false)) { + LOGP(DAUC, LOGL_ERROR, "Cannot update subscriber IMSI='%s': invalid IMEI: '%s'\n", imsi, imei); + return -EINVAL; + } + + if (!db_bind_text(stmt, "$imsi", imsi)) + return -EIO; + if (imei && !db_bind_text(stmt, "$imei", imei)) + return -EIO; + + /* execute the statement */ + rc = sqlite3_step(stmt); + if (rc != SQLITE_DONE) { + LOGP(DAUC, LOGL_ERROR, "Update IMEI for subscriber IMSI='%s': SQL Error: %s\n", imsi, + sqlite3_errmsg(dbc->db)); + ret = -EIO; + goto out; + } + + /* verify execution result */ + rc = sqlite3_changes(dbc->db); + if (!rc) { + LOGP(DAUC, LOGL_ERROR, "Cannot update IMEI for subscriber IMSI='%s': no such subscriber\n", imsi); + ret = -ENOENT; + } else if (rc != 1) { + LOGP(DAUC, LOGL_ERROR, "Update IMEI for subscriber IMSI='%s': SQL modified %d rows (expected 1)\n", + imsi, rc); + ret = -EIO; + } + +out: + db_remove_reset(stmt); + return ret; +} + /* Common code for db_subscr_get_by_*() functions. */ static int db_sel(struct db_context *dbc, sqlite3_stmt *stmt, struct hlr_subscriber *subscr, const char **err) @@ -415,18 +462,19 @@ static int db_sel(struct db_context *dbc, sqlite3_stmt *stmt, struct hlr_subscri subscr->id = sqlite3_column_int64(stmt, 0); copy_sqlite3_text_to_buf(subscr->imsi, stmt, 1); copy_sqlite3_text_to_buf(subscr->msisdn, stmt, 2); + copy_sqlite3_text_to_buf(subscr->imei, stmt, 3); /* FIXME: These should all be BLOBs as they might contain NUL */ - copy_sqlite3_text_to_buf(subscr->vlr_number, stmt, 3); - copy_sqlite3_text_to_buf(subscr->sgsn_number, stmt, 4); - copy_sqlite3_text_to_buf(subscr->sgsn_address, stmt, 5); - subscr->periodic_lu_timer = sqlite3_column_int(stmt, 6); - subscr->periodic_rau_tau_timer = sqlite3_column_int(stmt, 7); - subscr->nam_cs = sqlite3_column_int(stmt, 8); - subscr->nam_ps = sqlite3_column_int(stmt, 9); - subscr->lmsi = sqlite3_column_int(stmt, 10); - subscr->ms_purged_cs = sqlite3_column_int(stmt, 11); - subscr->ms_purged_ps = sqlite3_column_int(stmt, 12); - last_lu_seen_str = (const char *)sqlite3_column_text(stmt, 13); + copy_sqlite3_text_to_buf(subscr->vlr_number, stmt, 4); + copy_sqlite3_text_to_buf(subscr->sgsn_number, stmt, 5); + copy_sqlite3_text_to_buf(subscr->sgsn_address, stmt, 6); + subscr->periodic_lu_timer = sqlite3_column_int(stmt, 7); + subscr->periodic_rau_tau_timer = sqlite3_column_int(stmt, 8); + subscr->nam_cs = sqlite3_column_int(stmt, 9); + subscr->nam_ps = sqlite3_column_int(stmt, 10); + subscr->lmsi = sqlite3_column_int(stmt, 11); + subscr->ms_purged_cs = sqlite3_column_int(stmt, 12); + subscr->ms_purged_ps = sqlite3_column_int(stmt, 13); + last_lu_seen_str = (const char *)sqlite3_column_text(stmt, 14); if (last_lu_seen_str && last_lu_seen_str[0] != '\0') { if (strptime(last_lu_seen_str, DB_LAST_LU_SEEN_FMT, &tm) == NULL) { LOGP(DAUC, LOGL_ERROR, "Cannot parse last LU timestamp '%s' of subscriber with IMSI='%s': %s\n", @@ -530,6 +578,28 @@ int db_subscr_get_by_id(struct db_context *dbc, int64_t id, return rc; } +/*! Retrieve subscriber data from the HLR database. + * \param[in,out] dbc database context. + * \param[in] imei ASCII string of identifier digits + * \param[out] subscr place retrieved data in this struct. + * \returns 0 on success, -ENOENT if no such subscriber was found, -EIO on + * database error. + */ +int db_subscr_get_by_imei(struct db_context *dbc, const char *imei, struct hlr_subscriber *subscr) +{ + sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_IMEI]; + const char *err; + int rc; + + if (!db_bind_text(stmt, NULL, imei)) + return -EIO; + + rc = db_sel(dbc, stmt, subscr, &err); + if (rc) + LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: IMEI=%s: %s\n", imei, err); + return rc; +} + /*! You should use hlr_subscr_nam() instead; enable or disable PS or CS for a * subscriber without notifying GSUP clients. * \param[in,out] dbc database context. diff --git a/tests/db/db_test.c b/tests/db/db_test.c index c4ed6ed8..56905a9b 100644 --- a/tests/db/db_test.c +++ b/tests/db/db_test.c @@ -148,6 +148,7 @@ void dump_subscr(struct hlr_subscriber *subscr) Pd(id); Ps(imsi); Ps(msisdn); + Ps(imei); Ps(vlr_number); Ps(sgsn_number); Ps(sgsn_address); @@ -296,6 +297,23 @@ static void test_subscr_create_update_sel_delete() ASSERT_RC(db_subscr_update_msisdn_by_imsi(dbc, "foobar", "99"), -ENOENT); ASSERT_SEL(msisdn, "99", -ENOENT); + comment("Set valid / invalid IMEI"); + + ASSERT_RC(db_subscr_update_imei_by_imsi(dbc, imsi0, "12345678901234"), 0); + ASSERT_SEL(imei, "12345678901234", 0); + + ASSERT_RC(db_subscr_update_imei_by_imsi(dbc, imsi0, "123456789012345"), -EINVAL); /* too long */ + ASSERT_SEL(imei, "12345678901234", 0); + ASSERT_SEL(imei, "123456789012345", -ENOENT); + + comment("Set the same IMEI again"); + ASSERT_RC(db_subscr_update_imei_by_imsi(dbc, imsi0, "12345678901234"), 0); + ASSERT_SEL(imei, "12345678901234", 0); + + comment("Remove IMEI"); + ASSERT_RC(db_subscr_update_imei_by_imsi(dbc, imsi0, NULL), 0); + ASSERT_SEL(imei, "12345678901234", -ENOENT); + comment("Set / unset nam_cs and nam_ps"); /* nam_val, is_ps */ diff --git a/tests/db/db_test.err b/tests/db/db_test.err index 1d34045f..6ebdae26 100644 --- a/tests/db/db_test.err +++ b/tests/db/db_test.err @@ -227,6 +227,54 @@ db_subscr_get_by_msisdn(dbc, "99", &g_subscr) --> -ENOENT DAUC Cannot read subscriber from db: MSISDN='99': No such subscriber +--- Set valid / invalid IMEI + +db_subscr_update_imei_by_imsi(dbc, imsi0, "12345678901234") --> 0 + +db_subscr_get_by_imei(dbc, "12345678901234", &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .imei = '12345678901234', +} + +db_subscr_update_imei_by_imsi(dbc, imsi0, "123456789012345") --> -EINVAL +DAUC Cannot update subscriber IMSI='123456789000000': invalid IMEI: '123456789012345' + +db_subscr_get_by_imei(dbc, "12345678901234", &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .imei = '12345678901234', +} + +db_subscr_get_by_imei(dbc, "123456789012345", &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: IMEI=123456789012345: No such subscriber + + +--- Set the same IMEI again + +db_subscr_update_imei_by_imsi(dbc, imsi0, "12345678901234") --> 0 + +db_subscr_get_by_imei(dbc, "12345678901234", &g_subscr) --> 0 +struct hlr_subscriber { + .id = 1, + .imsi = '123456789000000', + .msisdn = '543210123456789', + .imei = '12345678901234', +} + + +--- Remove IMEI + +db_subscr_update_imei_by_imsi(dbc, imsi0, NULL) --> 0 + +db_subscr_get_by_imei(dbc, "12345678901234", &g_subscr) --> -ENOENT +DAUC Cannot read subscriber from db: IMEI=12345678901234: No such subscriber + + --- Set / unset nam_cs and nam_ps db_subscr_nam(dbc, imsi0, false, true) --> 0