osmo-hlr/src/db_hlr.c

973 lines
28 KiB
C
Raw Normal View History

/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#define _POSIX_C_SOURCE 200809L /* for strptime(3) */
/* These are needed as well due to the above _POSIX_C_SOURCE definition: */
#define _DEFAULT_SOURCE /* for struct timezone */
#define _XOPEN_SOURCE /* for clockid_t */
#include <string.h>
#include <errno.h>
#include <inttypes.h>
#include <time.h>
#include <osmocom/core/utils.h>
1/2: refactor: add and use lu_fsm, osmo_gsup_req, osmo_ipa_name These are seemingly orthogonal changes in one patch, because they are in fact sufficiently intertwined that we are not willing to spend the time to separate them. They are also refactoring changes, unlikely to make sense on their own. ** lu_fsm: Attempting to make luop.c keep state about incoming GSUP requests made me find shortcomings in several places: - since it predates osmo_fsm, it is a state machine that does not strictly enforce the order of state transitions or the right sequence of incoming events. - several places OSMO_ASSERT() on data received from the network. - modifies the subscriber state before a LU is accepted. - dead code about canceling a subscriber in a previous VLR. That would be a good thing to actually do, which should also be trivial now that we record vlr_name and sgsn_name, but I decided to remove the dead code for now. To both step up the LU game *and* make it easier for me to integrate osmo_gsup_req handling, I decided to create a lu_fsm, drawing from my, by now, ample experience of writing osmo_fsms. ** osmo_gsup_req: Prepare for D-GSM, where osmo-hlr will do proxy routing for remote HLRs / communicate with remote MSCs via a proxy: a) It is important that a response that osmo-hlr generates and that is sent back to a requesting MSC contains all IEs that are needed to route it back to the requester. Particularly source_name must become destination_name in the response to be able to even reach the requesting MSC. Other fields are also necessary to match, which were so far taken care of in individual numerous code paths. b) For some operations, the response to a GSUP request is generated asynchronously (like Update Location Request -> Response, or taking the response from an EUSE, or the upcoming proxying to a remote HLR). To be able to feed a request message's information back into the response, we must thus keep the request data around. Since struct osmo_gsup_message references a lot of external data, usually with pointers directly into the received msgb, it is not so trivial to pass GSUP message data around asynchronously, on its own. osmo_gsup_req is the combined solution for both a and b: it keeps all data for a GSUP message by taking ownership of the incoming msgb, and it provides an explicit API "forcing" callers to respond with osmo_gsup_req_respond(), so that all code paths trivially are definitely responding with the correct IEs set to match the request's routing (by using osmo_gsup_make_response() recently added to libosmocore). Adjust all osmo-hlr code paths to use *only* osmo_gsup_req to respond to incoming requests received on the GSUP server (above LU code being one of them). In fact, the same should be done on the client side. Hence osmo_gsup_req is implemented in a server/client agnostic way, and is placed in libosmo-gsupclient. As soon as we see routing errors in complex GSUP setups, using osmo_gsup_req in the related GSUP client is likely to resolve those problems without much thinking required beyond making all code paths use it. libosmo-gsupclient is hence added to osmo-hlr binary's own library dependencies. It would have been added by the D-GSM proxy routing anyway, we are just doing it a little sooner. ** cni_peer_id.c / osmo_ipa_name: We so far handle an IPA unit name as pointer + size, or as just pointer with implicit talloc size. To ease working with GSUP peer identification data, I require: - a non-allocated storage of an IPA Name. It brings the drawback of being size limited, but our current implementation is anyway only able to handle MSC and SGSN names of 31 characters (see struct hlr_subscriber). - a single-argument handle for IPA Name, - easy to use utility functions like osmo_ipa_name_to_str(), osmo_ipa_name_cmp(), and copying by simple assignment, a = b. Hence this patch adds a osmo_ipa_name in cni_peer_id.h and cni_peer_id.c. Heavily used in LU and osmo_gsup_req. Depends: libosmocore Id9692880079ea0f219f52d81b1923a76fc640566 Change-Id: I3a8dff3d4a1cbe10d6ab08257a0138d6b2a082d9
2019-11-20 01:36:45 +00:00
#include <osmocom/core/timer.h>
#include <osmocom/crypt/auth.h>
#include <osmocom/gsm/gsm23003.h>
#include <sqlite3.h>
#include <osmocom/hlr/logging.h>
#include <osmocom/hlr/hlr.h>
#include <osmocom/hlr/db.h>
#include <osmocom/gsupclient/cni_peer_id.h>
#define LOGHLR(imsi, level, fmt, args ...) LOGP(DAUC, level, "IMSI='%s': " fmt, imsi, ## args)
/*! Add new subscriber record to the HLR database.
* \param[in,out] dbc database context.
* \param[in] imsi ASCII string of IMSI digits, is validated.
* \param[in] flags Bitmask of DB_SUBSCR_FLAG_*.
* \returns 0 on success, -EINVAL on invalid IMSI, -EIO on database error.
*/
int db_subscr_create(struct db_context *dbc, const char *imsi, uint8_t flags)
{
sqlite3_stmt *stmt;
int rc;
if (!osmo_imsi_str_valid(imsi)) {
LOGP(DAUC, LOGL_ERROR, "Cannot create subscriber: invalid IMSI: '%s'\n",
imsi);
return -EINVAL;
}
stmt = dbc->stmt[DB_STMT_SUBSCR_CREATE];
if (!db_bind_text(stmt, "$imsi", imsi))
return -EIO;
if (!db_bind_int(stmt, "$nam_cs", (flags & DB_SUBSCR_FLAG_NAM_CS) != 0))
return -EIO;
if (!db_bind_int(stmt, "$nam_ps", (flags & DB_SUBSCR_FLAG_NAM_PS) != 0))
return -EIO;
/* execute the statement */
rc = sqlite3_step(stmt);
db_remove_reset(stmt);
if (rc != SQLITE_DONE) {
LOGHLR(imsi, LOGL_ERROR, "Cannot create subscriber: SQL error: (%d) %s\n",
rc, sqlite3_errmsg(dbc->db));
return -EIO;
}
return 0;
}
/*! Completely delete a subscriber record from the HLR database.
* Also remove authentication data.
* Future todo: also drop from all other database tables, which aren't used yet
* at the time of writing this.
* \param[in,out] dbc database context.
* \param[in] subscr_id ID of the subscriber in the HLR db.
* \returns if the subscriber was found and removed, -EIO on database error,
* -ENOENT if no such subscriber data exists.
*/
int db_subscr_delete_by_id(struct db_context *dbc, int64_t subscr_id)
{
int rc;
struct sub_auth_data_str aud;
int ret = 0;
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_DEL_BY_ID];
if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
return -EIO;
/* execute the statement */
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
LOGP(DAUC, LOGL_ERROR,
"Cannot delete subscriber ID=%" PRId64 ": SQL error: (%d) %s\n",
subscr_id, rc, sqlite3_errmsg(dbc->db));
db_remove_reset(stmt);
return -EIO;
}
/* verify execution result */
rc = sqlite3_changes(dbc->db);
if (!rc) {
LOGP(DAUC, LOGL_ERROR, "Cannot delete: no such subscriber: ID=%" PRId64 "\n",
subscr_id);
ret = -ENOENT;
} else if (rc != 1) {
LOGP(DAUC, LOGL_ERROR, "Delete subscriber ID=%" PRId64
": SQL modified %d rows (expected 1)\n", subscr_id, rc);
ret = -EIO;
}
db_remove_reset(stmt);
/* make sure to remove authentication data for this subscriber id, for
* both 2G and 3G. */
aud = (struct sub_auth_data_str){
.type = OSMO_AUTH_TYPE_GSM,
.algo = OSMO_AUTH_ALG_NONE,
};
rc = db_subscr_update_aud_by_id(dbc, subscr_id, &aud);
if (ret == -ENOENT && !rc)
ret = 0;
aud = (struct sub_auth_data_str){
.type = OSMO_AUTH_TYPE_UMTS,
.algo = OSMO_AUTH_ALG_NONE,
};
rc = db_subscr_update_aud_by_id(dbc, subscr_id, &aud);
if (ret == -ENOENT && !rc)
ret = 0;
return ret;
}
/*! Set a subscriber's MSISDN in the HLR database.
* \param[in,out] dbc database context.
* \param[in] imsi ASCII string of IMSI digits
* \param[in] msisdn ASCII string of MSISDN digits, or NULL to remove the MSISDN.
* \returns 0 on success, -EINVAL in case of invalid MSISDN string, -EIO on
* database failure, -ENOENT if no such subscriber exists.
*/
int db_subscr_update_msisdn_by_imsi(struct db_context *dbc, const char *imsi,
const char *msisdn)
{
int rc;
int ret = 0;
if (msisdn && !osmo_msisdn_str_valid(msisdn)) {
LOGHLR(imsi, LOGL_ERROR,
"Cannot update subscriber: invalid MSISDN: '%s'\n",
msisdn);
return -EINVAL;
}
sqlite3_stmt *stmt = dbc->stmt[
msisdn ? DB_STMT_SET_MSISDN_BY_IMSI : DB_STMT_DELETE_MSISDN_BY_IMSI];
if (!db_bind_text(stmt, "$imsi", imsi))
return -EIO;
if (msisdn) {
if (!db_bind_text(stmt, "$msisdn", msisdn))
return -EIO;
}
/* execute the statement */
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
LOGHLR(imsi, LOGL_ERROR,
"Cannot update subscriber's MSISDN: SQL error: (%d) %s\n",
rc, sqlite3_errmsg(dbc->db));
ret = -EIO;
goto out;
}
/* verify execution result */
rc = sqlite3_changes(dbc->db);
if (!rc) {
LOGP(DAUC, LOGL_ERROR, "Cannot update MSISDN: no such subscriber: IMSI='%s'\n",
imsi);
ret = -ENOENT;
goto out;
} else if (rc != 1) {
LOGHLR(imsi, LOGL_ERROR, "Update MSISDN: SQL modified %d rows (expected 1)\n", rc);
ret = -EIO;
}
out:
db_remove_reset(stmt);
return ret;
}
/*! Insert or update 2G or 3G authentication tokens in the database.
* If aud->type is OSMO_AUTH_TYPE_GSM, the auc_2g table entry for the
* subscriber will be added or modified; if aud->algo is OSMO_AUTH_ALG_NONE,
* however, the auc_2g entry for the subscriber is deleted. If aud->type is
* OSMO_AUTH_TYPE_UMTS, the auc_3g table is updated; again, if aud->algo is
* OSMO_AUTH_ALG_NONE, the auc_3g entry is deleted.
* \param[in,out] dbc database context.
* \param[in] subscr_id DB ID of the subscriber.
* \param[in] aud Pointer to new auth data (in ASCII string form).
* \returns 0 on success, -EINVAL for invalid aud, -ENOENT for unknown
* subscr_id, -EIO for database errors.
*/
int db_subscr_update_aud_by_id(struct db_context *dbc, int64_t subscr_id,
const struct sub_auth_data_str *aud)
{
sqlite3_stmt *stmt_del;
sqlite3_stmt *stmt_ins;
sqlite3_stmt *stmt;
const char *label;
int rc;
int ret = 0;
switch (aud->type) {
case OSMO_AUTH_TYPE_GSM:
label = "auc_2g";
stmt_del = dbc->stmt[DB_STMT_AUC_2G_DELETE];
stmt_ins = dbc->stmt[DB_STMT_AUC_2G_INSERT];
switch (aud->algo) {
case OSMO_AUTH_ALG_NONE:
case OSMO_AUTH_ALG_COMP128v1:
case OSMO_AUTH_ALG_COMP128v2:
case OSMO_AUTH_ALG_COMP128v3:
case OSMO_AUTH_ALG_XOR:
break;
case OSMO_AUTH_ALG_MILENAGE:
LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
" auth algo not suited for 2G: %s\n",
osmo_auth_alg_name(aud->algo));
return -EINVAL;
default:
LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
" Unknown auth algo: %d\n", aud->algo);
return -EINVAL;
}
if (aud->algo == OSMO_AUTH_ALG_NONE)
break;
if (!osmo_is_hexstr(aud->u.gsm.ki, 32, 32, true)) {
LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
" Invalid KI: '%s'\n", aud->u.gsm.ki);
return -EINVAL;
}
break;
case OSMO_AUTH_TYPE_UMTS:
label = "auc_3g";
stmt_del = dbc->stmt[DB_STMT_AUC_3G_DELETE];
stmt_ins = dbc->stmt[DB_STMT_AUC_3G_INSERT];
switch (aud->algo) {
case OSMO_AUTH_ALG_NONE:
case OSMO_AUTH_ALG_MILENAGE:
case OSMO_AUTH_ALG_XOR:
break;
case OSMO_AUTH_ALG_COMP128v1:
case OSMO_AUTH_ALG_COMP128v2:
case OSMO_AUTH_ALG_COMP128v3:
LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
" auth algo not suited for 3G: %s\n",
osmo_auth_alg_name(aud->algo));
return -EINVAL;
default:
LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
" Unknown auth algo: %d\n", aud->algo);
return -EINVAL;
}
if (aud->algo == OSMO_AUTH_ALG_NONE)
break;
if (!osmo_is_hexstr(aud->u.umts.k, 32, 32, true)) {
LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
" Invalid K: '%s'\n", aud->u.umts.k);
return -EINVAL;
}
if (!osmo_is_hexstr(aud->u.umts.opc, 32, 32, true)) {
LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
" Invalid OP/OPC: '%s'\n", aud->u.umts.opc);
return -EINVAL;
}
if (aud->u.umts.ind_bitlen > OSMO_MILENAGE_IND_BITLEN_MAX) {
LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
" Invalid ind_bitlen: %d\n", aud->u.umts.ind_bitlen);
return -EINVAL;
}
break;
default:
LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
" unknown auth type: %d\n", aud->type);
return -EINVAL;
}
stmt = stmt_del;
if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
return -EIO;
/* execute the statement */
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
LOGP(DAUC, LOGL_ERROR,
"Cannot delete %s row: SQL error: (%d) %s\n",
label, rc, sqlite3_errmsg(dbc->db));
ret = -EIO;
goto out;
}
/* verify execution result */
rc = sqlite3_changes(dbc->db);
if (!rc)
/* Leave "no such entry" logging to the caller -- during
* db_subscr_delete_by_id(), we call this to make sure it is
* empty, and no entry is not an error then.*/
ret = -ENOENT;
else if (rc != 1) {
LOGP(DAUC, LOGL_ERROR, "Delete subscriber ID=%" PRId64
" from %s: SQL modified %d rows (expected 1)\n",
subscr_id, label, rc);
ret = -EIO;
}
db_remove_reset(stmt);
/* Error situation? Return now. */
if (ret && ret != -ENOENT)
return ret;
/* Just delete requested? */
if (aud->algo == OSMO_AUTH_ALG_NONE)
return ret;
/* Don't return -ENOENT if inserting new data. */
ret = 0;
/* Insert new row */
stmt = stmt_ins;
if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
return -EIO;
switch (aud->type) {
case OSMO_AUTH_TYPE_GSM:
if (!db_bind_int(stmt, "$algo_id_2g", aud->algo))
return -EIO;
if (!db_bind_text(stmt, "$ki", aud->u.gsm.ki))
return -EIO;
break;
case OSMO_AUTH_TYPE_UMTS:
if (!db_bind_int(stmt, "$algo_id_3g", aud->algo))
return -EIO;
if (!db_bind_text(stmt, "$k", aud->u.umts.k))
return -EIO;
if (!db_bind_text(stmt, "$op",
aud->u.umts.opc_is_op ? aud->u.umts.opc : NULL))
return -EIO;
if (!db_bind_text(stmt, "$opc",
aud->u.umts.opc_is_op ? NULL : aud->u.umts.opc))
return -EIO;
if (!db_bind_int(stmt, "$ind_bitlen", aud->u.umts.ind_bitlen))
return -EIO;
break;
default:
OSMO_ASSERT(false);
}
/* execute the statement */
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
LOGP(DAUC, LOGL_ERROR,
"Cannot insert %s row: SQL error: (%d) %s\n",
label, rc, sqlite3_errmsg(dbc->db));
ret = -EIO;
goto out;
}
out:
db_remove_reset(stmt);
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;
}
static void parse_last_lu_seen(time_t *dst, const char *last_lu_seen_str, const char *imsi, const char *label)
{
struct tm tm = {0};
time_t val;
if (!last_lu_seen_str || last_lu_seen_str[0] == '\0')
return;
if (strptime(last_lu_seen_str, DB_LAST_LU_SEEN_FMT, &tm) == NULL) {
LOGP(DAUC, LOGL_ERROR, "IMSI-%s: Last LU Seen %s: Cannot parse timestamp '%s'\n",
imsi, label, last_lu_seen_str);
return;
}
errno = 0;
val = mktime(&tm);
if (val == -1) {
LOGP(DAUC, LOGL_ERROR, "IMSI-%s: Last LU Seen %s: Cannot convert timestamp '%s' to time_t: %s\n",
imsi, label, last_lu_seen_str, strerror(errno));
val = 0;
}
*dst = val;
}
/* 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)
{
int rc;
int ret = 0;
/* execute the statement */
rc = sqlite3_step(stmt);
if (rc == SQLITE_DONE) {
ret = -ENOENT;
goto out;
}
if (rc != SQLITE_ROW) {
ret = -EIO;
goto out;
}
if (!subscr)
goto out;
*subscr = (struct hlr_subscriber){};
/* obtain the various columns */
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);
2016-05-05 16:24:15 +00:00
/* FIXME: These should all be BLOBs as they might contain NUL */
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);
parse_last_lu_seen(&subscr->last_lu_seen, (const char *)sqlite3_column_text(stmt, 14),
subscr->imsi, "CS");
parse_last_lu_seen(&subscr->last_lu_seen_ps, (const char *)sqlite3_column_text(stmt, 15),
subscr->imsi, "PS");
copy_sqlite3_text_to_ipa_name(&subscr->vlr_via_proxy, stmt, 16);
copy_sqlite3_text_to_ipa_name(&subscr->sgsn_via_proxy, stmt, 17);
out:
db_remove_reset(stmt);
switch (ret) {
case 0:
*err = NULL;
break;
case -ENOENT:
*err = "No such subscriber";
break;
default:
*err = sqlite3_errmsg(dbc->db);
break;
}
return ret;
}
/*! Check if a subscriber exists in the HLR database.
* \param[in, out] dbc database context.
* \param[in] imsi ASCII string of IMSI digits.
* \returns 0 if it exists, -ENOENT if it does not exist, -EIO on database error.
*/
int db_subscr_exists_by_imsi(struct db_context *dbc, const char *imsi) {
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_EXISTS_BY_IMSI];
const char *err;
int rc;
if (!db_bind_text(stmt, NULL, imsi))
return -EIO;
rc = sqlite3_step(stmt);
db_remove_reset(stmt);
if (rc == SQLITE_ROW)
return 0; /* exists */
if (rc == SQLITE_DONE)
return -ENOENT; /* does not exist */
err = sqlite3_errmsg(dbc->db);
LOGP(DAUC, LOGL_ERROR, "Failed to check if subscriber exists by IMSI='%s': %s\n", imsi, err);
return rc;
}
/*! Retrieve subscriber data from the HLR database.
* \param[in,out] dbc database context.
* \param[in] imsi ASCII string of IMSI 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_imsi(struct db_context *dbc, const char *imsi,
struct hlr_subscriber *subscr)
{
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_IMSI];
const char *err;
int rc;
if (!db_bind_text(stmt, NULL, imsi))
return -EIO;
rc = db_sel(dbc, stmt, subscr, &err);
if (rc && rc != -ENOENT)
LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: IMSI='%s': %s\n",
imsi, err);
return rc;
}
/*! Check if a subscriber exists in the HLR database.
* \param[in, out] dbc database context.
* \param[in] msisdn ASCII string of MSISDN digits.
* \returns 0 if it exists, -ENOENT if it does not exist, -EIO on database error.
*/
int db_subscr_exists_by_msisdn(struct db_context *dbc, const char *msisdn)
{
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_EXISTS_BY_MSISDN];
const char *err;
int rc;
if (!db_bind_text(stmt, NULL, msisdn))
return -EIO;
rc = sqlite3_step(stmt);
db_remove_reset(stmt);
if (rc == SQLITE_ROW)
return 0; /* exists */
if (rc == SQLITE_DONE)
return -ENOENT; /* does not exist */
err = sqlite3_errmsg(dbc->db);
LOGP(DAUC, LOGL_ERROR, "Failed to check if subscriber exists "
"by MSISDN='%s': %s\n", msisdn, err);
return rc;
}
/*! Retrieve subscriber data from the HLR database.
* \param[in,out] dbc database context.
* \param[in] msisdn ASCII string of MSISDN 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_msisdn(struct db_context *dbc, const char *msisdn,
struct hlr_subscriber *subscr)
{
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_MSISDN];
const char *err;
int rc;
if (!db_bind_text(stmt, NULL, msisdn))
return -EIO;
rc = db_sel(dbc, stmt, subscr, &err);
if (rc && rc != -ENOENT)
LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: MSISDN='%s': %s\n",
msisdn, err);
return rc;
}
/*! Retrieve subscriber data from the HLR database.
* \param[in,out] dbc database context.
* \param[in] filter_type ASCII string of identifier type to search.
* \param[in] filter ASCII string to search.
* \param[in] get_cb pointer to call back function for data.
* \param[in,out] data pointer to pass to callback function.
* \param[in,out] count counter for number of matched subscribers.
* \param[in,our] err
* \returns 0 on success, -ENOENT if no subscriber was found, -EIO on
* database error.
*/
int db_subscrs_get(struct db_context *dbc, const char *filter_type, const char *filter,
void (*get_cb)(struct hlr_subscriber *subscr, void *data), void *data,
int *count, const char **err)
{
sqlite3_stmt *stmt;
char search[256];
int rc;
struct hlr_subscriber subscr;
bool show_ls = false;
if (!filter_type) {
stmt = dbc->stmt[DB_STMT_SEL_ALL];
} else if (strcmp(filter_type, "imsi") == 0) {
stmt = dbc->stmt[DB_STMT_SEL_FILTER_IMSI];
} else if (strcmp(filter_type, "msisdn") == 0) {
stmt = dbc->stmt[DB_STMT_SEL_FILTER_MSISDN];
} else if (strcmp(filter_type, "cs") == 0) {
stmt = dbc->stmt[DB_STMT_SEL_FILTER_CS];
} else if (strcmp(filter_type, "ps") == 0) {
stmt = dbc->stmt[DB_STMT_SEL_FILTER_PS];
} else if (strcmp(filter_type, "last_lu_seen") == 0) {
show_ls = true;
stmt = dbc->stmt[DB_STMT_SEL_ALL_ORDER_LAST_SEEN];
} else {
return -EIO;
}
if (filter_type && filter && strcmp(filter_type, "last_lu_seen") != 0) {
if (strcmp(filter, "on") == 0) {
sprintf(search, "%s", "1");
} else if (strcmp(filter, "off") == 0) {
sprintf(search, "%s", "0");
} else {
sprintf(search, "%%%s%%", filter);
}
if (!db_bind_text(stmt, "$search", search)) {
*err = sqlite3_errmsg(dbc->db);
return -EIO;
}
}
rc = sqlite3_step(stmt);
if (rc == SQLITE_DONE) {
db_remove_reset(stmt);
*err = "No matching subscriber(s)";
return -ENOENT;
}
while (rc == SQLITE_ROW) {
subscr = (struct hlr_subscriber){
.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);
subscr.nam_cs = sqlite3_column_int(stmt, 9);
subscr.nam_ps = sqlite3_column_int(stmt, 10);
if (show_ls)
parse_last_lu_seen(&subscr.last_lu_seen, (const char *)sqlite3_column_text(stmt, 14),
subscr.imsi, "CS");
get_cb(&subscr, data);
rc = sqlite3_step(stmt);
(*count)++;
}
db_remove_reset(stmt);
if (rc != SQLITE_DONE) {
*err = sqlite3_errmsg(dbc->db);
LOGP(DAUC, LOGL_ERROR, "Cannot read subscribers from db:: %s\n", *err);
return rc;
}
*err = NULL;
return 0;
}
/*! Retrieve subscriber data from the HLR database.
* \param[in,out] dbc database context.
* \param[in] id ID of the subscriber in the HLR db.
* \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_id(struct db_context *dbc, int64_t id,
struct hlr_subscriber *subscr)
{
sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_ID];
const char *err;
int rc;
if (!db_bind_int64(stmt, NULL, id))
return -EIO;
rc = db_sel(dbc, stmt, subscr, &err);
if (rc && rc != -ENOENT)
LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: ID=%" PRId64 ": %s\n",
id, err);
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 && rc != -ENOENT)
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.
* \param[in] imsi ASCII string of IMSI digits.
* \param[in] nam_val True to enable CS/PS, false to disable.
* \param[in] is_ps when true, set nam_ps, else set nam_cs.
* \returns 0 on success, -ENOENT when the given IMSI does not exist, -EIO on
* database errors.
*/
int db_subscr_nam(struct db_context *dbc, const char *imsi, bool nam_val, bool is_ps)
{
sqlite3_stmt *stmt;
int rc;
int ret = 0;
stmt = dbc->stmt[is_ps ? DB_STMT_UPD_NAM_PS_BY_IMSI
: DB_STMT_UPD_NAM_CS_BY_IMSI];
if (!db_bind_text(stmt, "$imsi", imsi))
return -EIO;
if (!db_bind_int(stmt, "$val", nam_val ? 1 : 0))
return -EIO;
/* execute the statement */
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
LOGHLR(imsi, LOGL_ERROR, "%s %s: SQL error: %s\n",
nam_val ? "enable" : "disable",
is_ps ? "PS" : "CS",
sqlite3_errmsg(dbc->db));
ret = -EIO;
goto out;
}
/* verify execution result */
rc = sqlite3_changes(dbc->db);
if (!rc) {
LOGP(DAUC, LOGL_ERROR, "Cannot %s %s: no such subscriber: IMSI='%s'\n",
nam_val ? "enable" : "disable",
is_ps ? "PS" : "CS",
imsi);
ret = -ENOENT;
goto out;
} else if (rc != 1) {
LOGHLR(imsi, LOGL_ERROR, "%s %s: SQL modified %d rows (expected 1)\n",
nam_val ? "enable" : "disable",
is_ps ? "PS" : "CS",
rc);
ret = -EIO;
}
out:
db_remove_reset(stmt);
return ret;
}
/*! Record a Location Updating in the database.
* \param[in,out] dbc database context.
* \param[in] subscr_id ID of the subscriber in the HLR db.
* \param[in] vlr_or_sgsn_number ASCII string of identifier digits.
* \param[in] is_ps when true, set sgsn_number, else set vlr_number.
* \returns 0 on success, -ENOENT when the given subscriber does not exist,
* -EIO on database errors.
*/
int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
1/2: refactor: add and use lu_fsm, osmo_gsup_req, osmo_ipa_name These are seemingly orthogonal changes in one patch, because they are in fact sufficiently intertwined that we are not willing to spend the time to separate them. They are also refactoring changes, unlikely to make sense on their own. ** lu_fsm: Attempting to make luop.c keep state about incoming GSUP requests made me find shortcomings in several places: - since it predates osmo_fsm, it is a state machine that does not strictly enforce the order of state transitions or the right sequence of incoming events. - several places OSMO_ASSERT() on data received from the network. - modifies the subscriber state before a LU is accepted. - dead code about canceling a subscriber in a previous VLR. That would be a good thing to actually do, which should also be trivial now that we record vlr_name and sgsn_name, but I decided to remove the dead code for now. To both step up the LU game *and* make it easier for me to integrate osmo_gsup_req handling, I decided to create a lu_fsm, drawing from my, by now, ample experience of writing osmo_fsms. ** osmo_gsup_req: Prepare for D-GSM, where osmo-hlr will do proxy routing for remote HLRs / communicate with remote MSCs via a proxy: a) It is important that a response that osmo-hlr generates and that is sent back to a requesting MSC contains all IEs that are needed to route it back to the requester. Particularly source_name must become destination_name in the response to be able to even reach the requesting MSC. Other fields are also necessary to match, which were so far taken care of in individual numerous code paths. b) For some operations, the response to a GSUP request is generated asynchronously (like Update Location Request -> Response, or taking the response from an EUSE, or the upcoming proxying to a remote HLR). To be able to feed a request message's information back into the response, we must thus keep the request data around. Since struct osmo_gsup_message references a lot of external data, usually with pointers directly into the received msgb, it is not so trivial to pass GSUP message data around asynchronously, on its own. osmo_gsup_req is the combined solution for both a and b: it keeps all data for a GSUP message by taking ownership of the incoming msgb, and it provides an explicit API "forcing" callers to respond with osmo_gsup_req_respond(), so that all code paths trivially are definitely responding with the correct IEs set to match the request's routing (by using osmo_gsup_make_response() recently added to libosmocore). Adjust all osmo-hlr code paths to use *only* osmo_gsup_req to respond to incoming requests received on the GSUP server (above LU code being one of them). In fact, the same should be done on the client side. Hence osmo_gsup_req is implemented in a server/client agnostic way, and is placed in libosmo-gsupclient. As soon as we see routing errors in complex GSUP setups, using osmo_gsup_req in the related GSUP client is likely to resolve those problems without much thinking required beyond making all code paths use it. libosmo-gsupclient is hence added to osmo-hlr binary's own library dependencies. It would have been added by the D-GSM proxy routing anyway, we are just doing it a little sooner. ** cni_peer_id.c / osmo_ipa_name: We so far handle an IPA unit name as pointer + size, or as just pointer with implicit talloc size. To ease working with GSUP peer identification data, I require: - a non-allocated storage of an IPA Name. It brings the drawback of being size limited, but our current implementation is anyway only able to handle MSC and SGSN names of 31 characters (see struct hlr_subscriber). - a single-argument handle for IPA Name, - easy to use utility functions like osmo_ipa_name_to_str(), osmo_ipa_name_cmp(), and copying by simple assignment, a = b. Hence this patch adds a osmo_ipa_name in cni_peer_id.h and cni_peer_id.c. Heavily used in LU and osmo_gsup_req. Depends: libosmocore Id9692880079ea0f219f52d81b1923a76fc640566 Change-Id: I3a8dff3d4a1cbe10d6ab08257a0138d6b2a082d9
2019-11-20 01:36:45 +00:00
const struct osmo_ipa_name *vlr_name, bool is_ps,
const struct osmo_ipa_name *via_proxy)
{
sqlite3_stmt *stmt;
int rc, ret = 0;
struct timespec localtime;
stmt = dbc->stmt[is_ps ? DB_STMT_UPD_SGSN_BY_ID
: DB_STMT_UPD_VLR_BY_ID];
if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
return -EIO;
1/2: refactor: add and use lu_fsm, osmo_gsup_req, osmo_ipa_name These are seemingly orthogonal changes in one patch, because they are in fact sufficiently intertwined that we are not willing to spend the time to separate them. They are also refactoring changes, unlikely to make sense on their own. ** lu_fsm: Attempting to make luop.c keep state about incoming GSUP requests made me find shortcomings in several places: - since it predates osmo_fsm, it is a state machine that does not strictly enforce the order of state transitions or the right sequence of incoming events. - several places OSMO_ASSERT() on data received from the network. - modifies the subscriber state before a LU is accepted. - dead code about canceling a subscriber in a previous VLR. That would be a good thing to actually do, which should also be trivial now that we record vlr_name and sgsn_name, but I decided to remove the dead code for now. To both step up the LU game *and* make it easier for me to integrate osmo_gsup_req handling, I decided to create a lu_fsm, drawing from my, by now, ample experience of writing osmo_fsms. ** osmo_gsup_req: Prepare for D-GSM, where osmo-hlr will do proxy routing for remote HLRs / communicate with remote MSCs via a proxy: a) It is important that a response that osmo-hlr generates and that is sent back to a requesting MSC contains all IEs that are needed to route it back to the requester. Particularly source_name must become destination_name in the response to be able to even reach the requesting MSC. Other fields are also necessary to match, which were so far taken care of in individual numerous code paths. b) For some operations, the response to a GSUP request is generated asynchronously (like Update Location Request -> Response, or taking the response from an EUSE, or the upcoming proxying to a remote HLR). To be able to feed a request message's information back into the response, we must thus keep the request data around. Since struct osmo_gsup_message references a lot of external data, usually with pointers directly into the received msgb, it is not so trivial to pass GSUP message data around asynchronously, on its own. osmo_gsup_req is the combined solution for both a and b: it keeps all data for a GSUP message by taking ownership of the incoming msgb, and it provides an explicit API "forcing" callers to respond with osmo_gsup_req_respond(), so that all code paths trivially are definitely responding with the correct IEs set to match the request's routing (by using osmo_gsup_make_response() recently added to libosmocore). Adjust all osmo-hlr code paths to use *only* osmo_gsup_req to respond to incoming requests received on the GSUP server (above LU code being one of them). In fact, the same should be done on the client side. Hence osmo_gsup_req is implemented in a server/client agnostic way, and is placed in libosmo-gsupclient. As soon as we see routing errors in complex GSUP setups, using osmo_gsup_req in the related GSUP client is likely to resolve those problems without much thinking required beyond making all code paths use it. libosmo-gsupclient is hence added to osmo-hlr binary's own library dependencies. It would have been added by the D-GSM proxy routing anyway, we are just doing it a little sooner. ** cni_peer_id.c / osmo_ipa_name: We so far handle an IPA unit name as pointer + size, or as just pointer with implicit talloc size. To ease working with GSUP peer identification data, I require: - a non-allocated storage of an IPA Name. It brings the drawback of being size limited, but our current implementation is anyway only able to handle MSC and SGSN names of 31 characters (see struct hlr_subscriber). - a single-argument handle for IPA Name, - easy to use utility functions like osmo_ipa_name_to_str(), osmo_ipa_name_cmp(), and copying by simple assignment, a = b. Hence this patch adds a osmo_ipa_name in cni_peer_id.h and cni_peer_id.c. Heavily used in LU and osmo_gsup_req. Depends: libosmocore Id9692880079ea0f219f52d81b1923a76fc640566 Change-Id: I3a8dff3d4a1cbe10d6ab08257a0138d6b2a082d9
2019-11-20 01:36:45 +00:00
if (!db_bind_text(stmt, "$number", (char*)vlr_name->val))
return -EIO;
if (via_proxy && via_proxy->len) {
if (!db_bind_text(stmt, "$proxy", (char*)via_proxy->val))
return -EIO;
} else {
if (!db_bind_null(stmt, "$proxy"))
return -EIO;
}
/* execute the statement */
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
LOGP(DAUC, LOGL_ERROR, "Update %s number for subscriber ID=%" PRId64 ": SQL Error: %s\n",
is_ps? "SGSN" : "VLR", subscr_id, sqlite3_errmsg(dbc->db));
ret = -EIO;
goto out;
}
/* verify execution result */
rc = sqlite3_changes(dbc->db);
if (!rc) {
LOGP(DAUC, LOGL_ERROR, "Cannot update %s number for subscriber ID=%" PRId64
": no such subscriber\n",
is_ps? "SGSN" : "VLR", subscr_id);
ret = -ENOENT;
goto out;
} else if (rc != 1) {
LOGP(DAUC, LOGL_ERROR, "Update %s number for subscriber ID=%" PRId64
": SQL modified %d rows (expected 1)\n",
is_ps? "SGSN" : "VLR", subscr_id, rc);
ret = -EIO;
goto out;
}
db_remove_reset(stmt);
if (osmo_clock_gettime(CLOCK_REALTIME, &localtime) != 0) {
LOGP(DAUC, LOGL_ERROR, "Cannot get the current time: (%d) %s\n", errno, strerror(errno));
ret = -errno;
goto out;
}
stmt = dbc->stmt[is_ps? DB_STMT_SET_LAST_LU_SEEN_PS : DB_STMT_SET_LAST_LU_SEEN];
if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
return -EIO;
/* The timestamp will be converted to UTC by SQLite. */
if (!db_bind_int64(stmt, "$val", (int64_t)localtime.tv_sec)) {
ret = -EIO;
goto out;
}
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
LOGP(DAUC, LOGL_ERROR,
"Cannot update LU timestamp for subscriber ID=%" PRId64 ": SQL error: (%d) %s\n",
subscr_id, rc, sqlite3_errmsg(dbc->db));
ret = -EIO;
goto out;
}
/* verify execution result */
rc = sqlite3_changes(dbc->db);
if (!rc) {
LOGP(DAUC, LOGL_ERROR, "Cannot update LU timestamp for subscriber ID=%" PRId64
": no such subscriber\n", subscr_id);
ret = -ENOENT;
goto out;
} else if (rc != 1) {
LOGP(DAUC, LOGL_ERROR, "Update LU timestamp for subscriber ID=%" PRId64
": SQL modified %d rows (expected 1)\n", subscr_id, rc);
ret = -EIO;
}
out:
db_remove_reset(stmt);
return ret;
}
/*! Set the ms_purged_cs or ms_purged_ps values in the database.
* \param[in,out] dbc database context.
* \param[in] by_imsi ASCII string of IMSI digits.
* \param[in] purge_val true to purge, false to un-purge.
* \param[in] is_ps when true, set ms_purged_ps, else set ms_purged_cs.
* \returns 0 on success, -ENOENT when the given IMSI does not exist, -EIO on
* database errors.
*/
int db_subscr_purge(struct db_context *dbc, const char *by_imsi,
bool purge_val, bool is_ps)
{
sqlite3_stmt *stmt;
int rc, ret = 0;
stmt = dbc->stmt[is_ps ? DB_STMT_UPD_PURGE_PS_BY_IMSI
: DB_STMT_UPD_PURGE_CS_BY_IMSI];
if (!db_bind_text(stmt, "$imsi", by_imsi))
return -EIO;
if (!db_bind_int(stmt, "$val", purge_val ? 1 : 0))
return -EIO;
/* execute the statement */
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
LOGP(DAUC, LOGL_ERROR, "%s %s: SQL error: %s\n",
purge_val ? "purge" : "un-purge",
is_ps ? "PS" : "CS",
sqlite3_errmsg(dbc->db));
ret = -EIO;
goto out;
}
/* verify execution result */
rc = sqlite3_changes(dbc->db);
if (!rc) {
LOGP(DAUC, LOGL_ERROR, "Cannot %s %s: no such subscriber: IMSI='%s'\n",
purge_val ? "purge" : "un-purge",
is_ps ? "PS" : "CS",
by_imsi);
ret = -ENOENT;
goto out;
} else if (rc != 1) {
LOGHLR(by_imsi, LOGL_ERROR, "%s %s: SQL modified %d rows (expected 1)\n",
purge_val ? "purge" : "un-purge",
is_ps ? "PS" : "CS",
rc);
ret = -EIO;
}
out:
db_remove_reset(stmt);
return ret;
}