Add IMEI column to subscriber table

Extend the database scheme, add imei to the hlr_subscriber struct and
create db_subscr_update_imei_by_imsi() and db_subscr_get_by_imei(). The
new functions are used in db_test, and in follow-up commits [1], [2].

Upgrade DB schema to version 2. SQLite can only insert new columns at
the end of the table, so this happens when upgrading the database. In
new databases, the column is placed after the IMEISV column (where it
makes more sense in my opinion). This should not have any effect, as
we never rely on the order of the columns in the tables.

Follow-up commit [1] will make use of this column to save the IMEI as
received from the MSC/VLR with the Check-IMEI Procedure. It was
decided to use Check-IMEI instead of the recent Automatic Device
Detection Procedure (which would send the IMEISV) in OS#3733, because
with Check-IMEI we don't need to rely on very recent releases of the
specification.

[1] change-id I09274ecbed64224f7ae305e09ede773931da2a57
    "Optionally store IMEI in subscriber table"
[2] change-id I1af7b573ca2a1cb22497052665012d9c1acf3b30
    "VTY: integrate IMEI"

Depends: Id2d2a3a93b033bafc74c62e15297034bf4aafe61 (libosmocore)
Related: OS#2541
Change-Id: If232c80bea35d5c6864b889ae92d477eeaa3f45d
This commit is contained in:
Oliver Smith 2019-01-09 12:03:51 +01:00 committed by Harald Welte
parent 7943e26938
commit 81db389fd4
6 changed files with 203 additions and 14 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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.

View File

@ -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 */

View File

@ -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