blob: aa2e3652606f6a95e508db949d1448375889616a [file] [log] [blame]
Harald Welte626f5eb2023-05-30 18:28:15 +02001/* (C) 2015-2023 by Harald Welte <laforge@gnumonks.org>
Harald Weltee687be52016-05-03 18:49:27 +02002 *
3 * All Rights Reserved
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
Stefan Sperling5c14c9c2018-12-07 12:30:21 +010020#define _POSIX_C_SOURCE 200809L /* for strptime(3) */
21/* These are needed as well due to the above _POSIX_C_SOURCE definition: */
22#define _DEFAULT_SOURCE /* for struct timezone */
23#define _XOPEN_SOURCE /* for clockid_t */
24
Harald Weltee687be52016-05-03 18:49:27 +020025#include <string.h>
Max00b37152017-02-20 11:09:27 +010026#include <errno.h>
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020027#include <inttypes.h>
Stefan Sperling638ba8c2018-12-04 15:07:29 +010028#include <time.h>
Harald Weltee687be52016-05-03 18:49:27 +020029
30#include <osmocom/core/utils.h>
Neels Hofmeyrad868e22019-11-20 02:36:45 +010031#include <osmocom/core/timer.h>
Harald Weltee687be52016-05-03 18:49:27 +020032#include <osmocom/crypt/auth.h>
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020033#include <osmocom/gsm/gsm23003.h>
Harald Weltee687be52016-05-03 18:49:27 +020034
35#include <sqlite3.h>
36
Neels Hofmeyr2f758032019-11-20 00:37:07 +010037#include <osmocom/hlr/logging.h>
38#include <osmocom/hlr/hlr.h>
39#include <osmocom/hlr/db.h>
Neels Hofmeyrc79bcde2019-12-04 01:04:32 +010040#include <osmocom/gsupclient/cni_peer_id.h>
Harald Weltee687be52016-05-03 18:49:27 +020041
Neels Hofmeyr40aa61c2017-10-09 17:56:04 +020042#define LOGHLR(imsi, level, fmt, args ...) LOGP(DAUC, level, "IMSI='%s': " fmt, imsi, ## args)
Harald Weltee687be52016-05-03 18:49:27 +020043
Neels Hofmeyr16140f72017-10-25 19:17:18 +020044/*! Add new subscriber record to the HLR database.
45 * \param[in,out] dbc database context.
46 * \param[in] imsi ASCII string of IMSI digits, is validated.
Oliver Smithcd2af5e2019-03-06 13:17:39 +010047 * \param[in] flags Bitmask of DB_SUBSCR_FLAG_*.
Pau Espin Pedrold456fce2022-06-17 17:56:56 +020048 * \returns 0 on success, -EINVAL on invalid IMSI, -EEXIST if subscriber with
49 * provided imsi already exists, -EIO on other database errors.
Neels Hofmeyr16140f72017-10-25 19:17:18 +020050 */
Oliver Smithcd2af5e2019-03-06 13:17:39 +010051int db_subscr_create(struct db_context *dbc, const char *imsi, uint8_t flags)
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020052{
53 sqlite3_stmt *stmt;
54 int rc;
55
56 if (!osmo_imsi_str_valid(imsi)) {
57 LOGP(DAUC, LOGL_ERROR, "Cannot create subscriber: invalid IMSI: '%s'\n",
58 imsi);
59 return -EINVAL;
60 }
61
62 stmt = dbc->stmt[DB_STMT_SUBSCR_CREATE];
63
64 if (!db_bind_text(stmt, "$imsi", imsi))
65 return -EIO;
Oliver Smithcd2af5e2019-03-06 13:17:39 +010066 if (!db_bind_int(stmt, "$nam_cs", (flags & DB_SUBSCR_FLAG_NAM_CS) != 0))
67 return -EIO;
68 if (!db_bind_int(stmt, "$nam_ps", (flags & DB_SUBSCR_FLAG_NAM_PS) != 0))
69 return -EIO;
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020070
71 /* execute the statement */
72 rc = sqlite3_step(stmt);
73 db_remove_reset(stmt);
74 if (rc != SQLITE_DONE) {
75 LOGHLR(imsi, LOGL_ERROR, "Cannot create subscriber: SQL error: (%d) %s\n",
76 rc, sqlite3_errmsg(dbc->db));
Pau Espin Pedrold456fce2022-06-17 17:56:56 +020077 if (rc == SQLITE_CONSTRAINT_UNIQUE)
78 return -EEXIST;
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020079 return -EIO;
80 }
81
82 return 0;
83}
84
Neels Hofmeyr16140f72017-10-25 19:17:18 +020085/*! Completely delete a subscriber record from the HLR database.
86 * Also remove authentication data.
87 * Future todo: also drop from all other database tables, which aren't used yet
88 * at the time of writing this.
89 * \param[in,out] dbc database context.
90 * \param[in] subscr_id ID of the subscriber in the HLR db.
91 * \returns if the subscriber was found and removed, -EIO on database error,
92 * -ENOENT if no such subscriber data exists.
93 */
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020094int db_subscr_delete_by_id(struct db_context *dbc, int64_t subscr_id)
95{
96 int rc;
Neels Hofmeyr1332a172017-10-10 02:25:00 +020097 struct sub_auth_data_str aud;
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020098 int ret = 0;
99
100 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_DEL_BY_ID];
101
102 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
103 return -EIO;
104
105 /* execute the statement */
106 rc = sqlite3_step(stmt);
107 if (rc != SQLITE_DONE) {
108 LOGP(DAUC, LOGL_ERROR,
Stefan Sperling705b61b2018-12-07 12:44:50 +0100109 "Cannot delete subscriber ID=%" PRId64 ": SQL error: (%d) %s\n",
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200110 subscr_id, rc, sqlite3_errmsg(dbc->db));
111 db_remove_reset(stmt);
112 return -EIO;
113 }
114
115 /* verify execution result */
116 rc = sqlite3_changes(dbc->db);
117 if (!rc) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100118 LOGP(DAUC, LOGL_ERROR, "Cannot delete: no such subscriber: ID=%" PRId64 "\n",
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200119 subscr_id);
120 ret = -ENOENT;
121 } else if (rc != 1) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100122 LOGP(DAUC, LOGL_ERROR, "Delete subscriber ID=%" PRId64
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200123 ": SQL modified %d rows (expected 1)\n", subscr_id, rc);
124 ret = -EIO;
125 }
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200126 db_remove_reset(stmt);
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200127
128 /* make sure to remove authentication data for this subscriber id, for
129 * both 2G and 3G. */
130
131 aud = (struct sub_auth_data_str){
132 .type = OSMO_AUTH_TYPE_GSM,
133 .algo = OSMO_AUTH_ALG_NONE,
134 };
135 rc = db_subscr_update_aud_by_id(dbc, subscr_id, &aud);
136 if (ret == -ENOENT && !rc)
137 ret = 0;
138
139 aud = (struct sub_auth_data_str){
140 .type = OSMO_AUTH_TYPE_UMTS,
141 .algo = OSMO_AUTH_ALG_NONE,
142 };
143 rc = db_subscr_update_aud_by_id(dbc, subscr_id, &aud);
144 if (ret == -ENOENT && !rc)
145 ret = 0;
146
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200147 return ret;
148}
149
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200150/*! Set a subscriber's MSISDN in the HLR database.
151 * \param[in,out] dbc database context.
Oliver Smith2dc7d962019-01-15 14:14:51 +0100152 * \param[in] imsi ASCII string of IMSI digits
153 * \param[in] msisdn ASCII string of MSISDN digits, or NULL to remove the MSISDN.
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200154 * \returns 0 on success, -EINVAL in case of invalid MSISDN string, -EIO on
155 * database failure, -ENOENT if no such subscriber exists.
156 */
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200157int db_subscr_update_msisdn_by_imsi(struct db_context *dbc, const char *imsi,
158 const char *msisdn)
159{
160 int rc;
161 int ret = 0;
162
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100163 if (msisdn && !osmo_msisdn_str_valid(msisdn)) {
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200164 LOGHLR(imsi, LOGL_ERROR,
165 "Cannot update subscriber: invalid MSISDN: '%s'\n",
166 msisdn);
167 return -EINVAL;
168 }
169
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100170 sqlite3_stmt *stmt = dbc->stmt[
171 msisdn ? DB_STMT_SET_MSISDN_BY_IMSI : DB_STMT_DELETE_MSISDN_BY_IMSI];
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200172
173 if (!db_bind_text(stmt, "$imsi", imsi))
174 return -EIO;
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100175 if (msisdn) {
176 if (!db_bind_text(stmt, "$msisdn", msisdn))
177 return -EIO;
178 }
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200179
180 /* execute the statement */
181 rc = sqlite3_step(stmt);
182 if (rc != SQLITE_DONE) {
183 LOGHLR(imsi, LOGL_ERROR,
184 "Cannot update subscriber's MSISDN: SQL error: (%d) %s\n",
185 rc, sqlite3_errmsg(dbc->db));
186 ret = -EIO;
187 goto out;
188 }
189
190 /* verify execution result */
191 rc = sqlite3_changes(dbc->db);
192 if (!rc) {
193 LOGP(DAUC, LOGL_ERROR, "Cannot update MSISDN: no such subscriber: IMSI='%s'\n",
194 imsi);
195 ret = -ENOENT;
196 goto out;
197 } else if (rc != 1) {
198 LOGHLR(imsi, LOGL_ERROR, "Update MSISDN: SQL modified %d rows (expected 1)\n", rc);
199 ret = -EIO;
200 }
201
202out:
203 db_remove_reset(stmt);
204 return ret;
205
206}
207
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200208/*! Insert or update 2G or 3G authentication tokens in the database.
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200209 * If aud->type is OSMO_AUTH_TYPE_GSM, the auc_2g table entry for the
210 * subscriber will be added or modified; if aud->algo is OSMO_AUTH_ALG_NONE,
211 * however, the auc_2g entry for the subscriber is deleted. If aud->type is
212 * OSMO_AUTH_TYPE_UMTS, the auc_3g table is updated; again, if aud->algo is
213 * OSMO_AUTH_ALG_NONE, the auc_3g entry is deleted.
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200214 * \param[in,out] dbc database context.
215 * \param[in] subscr_id DB ID of the subscriber.
216 * \param[in] aud Pointer to new auth data (in ASCII string form).
217 * \returns 0 on success, -EINVAL for invalid aud, -ENOENT for unknown
218 * subscr_id, -EIO for database errors.
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200219 */
220int db_subscr_update_aud_by_id(struct db_context *dbc, int64_t subscr_id,
221 const struct sub_auth_data_str *aud)
222{
223 sqlite3_stmt *stmt_del;
224 sqlite3_stmt *stmt_ins;
225 sqlite3_stmt *stmt;
226 const char *label;
227 int rc;
228 int ret = 0;
229
230 switch (aud->type) {
231 case OSMO_AUTH_TYPE_GSM:
232 label = "auc_2g";
233 stmt_del = dbc->stmt[DB_STMT_AUC_2G_DELETE];
234 stmt_ins = dbc->stmt[DB_STMT_AUC_2G_INSERT];
235
236 switch (aud->algo) {
237 case OSMO_AUTH_ALG_NONE:
238 case OSMO_AUTH_ALG_COMP128v1:
239 case OSMO_AUTH_ALG_COMP128v2:
240 case OSMO_AUTH_ALG_COMP128v3:
Harald Welte829713a2023-05-30 16:57:27 +0200241 case OSMO_AUTH_ALG_XOR_2G:
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200242 break;
Harald Welte829713a2023-05-30 16:57:27 +0200243 case OSMO_AUTH_ALG_XOR_3G:
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200244 case OSMO_AUTH_ALG_MILENAGE:
245 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
246 " auth algo not suited for 2G: %s\n",
247 osmo_auth_alg_name(aud->algo));
248 return -EINVAL;
249 default:
250 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
251 " Unknown auth algo: %d\n", aud->algo);
252 return -EINVAL;
253 }
254
255 if (aud->algo == OSMO_AUTH_ALG_NONE)
256 break;
257 if (!osmo_is_hexstr(aud->u.gsm.ki, 32, 32, true)) {
258 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
259 " Invalid KI: '%s'\n", aud->u.gsm.ki);
260 return -EINVAL;
261 }
262 break;
263
264 case OSMO_AUTH_TYPE_UMTS:
265 label = "auc_3g";
266 stmt_del = dbc->stmt[DB_STMT_AUC_3G_DELETE];
267 stmt_ins = dbc->stmt[DB_STMT_AUC_3G_INSERT];
268 switch (aud->algo) {
269 case OSMO_AUTH_ALG_NONE:
270 case OSMO_AUTH_ALG_MILENAGE:
Harald Welte829713a2023-05-30 16:57:27 +0200271 case OSMO_AUTH_ALG_XOR_3G:
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200272 break;
273 case OSMO_AUTH_ALG_COMP128v1:
274 case OSMO_AUTH_ALG_COMP128v2:
275 case OSMO_AUTH_ALG_COMP128v3:
Harald Welte829713a2023-05-30 16:57:27 +0200276 case OSMO_AUTH_ALG_XOR_2G:
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200277 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
278 " auth algo not suited for 3G: %s\n",
279 osmo_auth_alg_name(aud->algo));
280 return -EINVAL;
281 default:
282 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
283 " Unknown auth algo: %d\n", aud->algo);
284 return -EINVAL;
285 }
286
287 if (aud->algo == OSMO_AUTH_ALG_NONE)
288 break;
Harald Welte626f5eb2023-05-30 18:28:15 +0200289 if (!osmo_is_hexstr(aud->u.umts.k, 32, 64, true)) {
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200290 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
291 " Invalid K: '%s'\n", aud->u.umts.k);
292 return -EINVAL;
293 }
Harald Welte626f5eb2023-05-30 18:28:15 +0200294 if (!osmo_is_hexstr(aud->u.umts.opc, 32, 64, true)) {
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200295 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
296 " Invalid OP/OPC: '%s'\n", aud->u.umts.opc);
297 return -EINVAL;
298 }
299 if (aud->u.umts.ind_bitlen > OSMO_MILENAGE_IND_BITLEN_MAX) {
300 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
301 " Invalid ind_bitlen: %d\n", aud->u.umts.ind_bitlen);
302 return -EINVAL;
303 }
304 break;
305 default:
306 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
307 " unknown auth type: %d\n", aud->type);
308 return -EINVAL;
309 }
310
311 stmt = stmt_del;
312
313 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
314 return -EIO;
315
316 /* execute the statement */
317 rc = sqlite3_step(stmt);
318 if (rc != SQLITE_DONE) {
319 LOGP(DAUC, LOGL_ERROR,
320 "Cannot delete %s row: SQL error: (%d) %s\n",
321 label, rc, sqlite3_errmsg(dbc->db));
322 ret = -EIO;
323 goto out;
324 }
325
326 /* verify execution result */
327 rc = sqlite3_changes(dbc->db);
328 if (!rc)
329 /* Leave "no such entry" logging to the caller -- during
330 * db_subscr_delete_by_id(), we call this to make sure it is
331 * empty, and no entry is not an error then.*/
332 ret = -ENOENT;
333 else if (rc != 1) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100334 LOGP(DAUC, LOGL_ERROR, "Delete subscriber ID=%" PRId64
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200335 " from %s: SQL modified %d rows (expected 1)\n",
336 subscr_id, label, rc);
337 ret = -EIO;
338 }
339
340 db_remove_reset(stmt);
341
342 /* Error situation? Return now. */
343 if (ret && ret != -ENOENT)
344 return ret;
345
346 /* Just delete requested? */
347 if (aud->algo == OSMO_AUTH_ALG_NONE)
348 return ret;
349
350 /* Don't return -ENOENT if inserting new data. */
351 ret = 0;
352
353 /* Insert new row */
354 stmt = stmt_ins;
355
356 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
357 return -EIO;
358
359 switch (aud->type) {
360 case OSMO_AUTH_TYPE_GSM:
361 if (!db_bind_int(stmt, "$algo_id_2g", aud->algo))
362 return -EIO;
363 if (!db_bind_text(stmt, "$ki", aud->u.gsm.ki))
364 return -EIO;
365 break;
366 case OSMO_AUTH_TYPE_UMTS:
367 if (!db_bind_int(stmt, "$algo_id_3g", aud->algo))
368 return -EIO;
369 if (!db_bind_text(stmt, "$k", aud->u.umts.k))
370 return -EIO;
371 if (!db_bind_text(stmt, "$op",
372 aud->u.umts.opc_is_op ? aud->u.umts.opc : NULL))
373 return -EIO;
374 if (!db_bind_text(stmt, "$opc",
375 aud->u.umts.opc_is_op ? NULL : aud->u.umts.opc))
376 return -EIO;
377 if (!db_bind_int(stmt, "$ind_bitlen", aud->u.umts.ind_bitlen))
378 return -EIO;
379 break;
380 default:
381 OSMO_ASSERT(false);
382 }
383
384 /* execute the statement */
385 rc = sqlite3_step(stmt);
386 if (rc != SQLITE_DONE) {
387 LOGP(DAUC, LOGL_ERROR,
388 "Cannot insert %s row: SQL error: (%d) %s\n",
389 label, rc, sqlite3_errmsg(dbc->db));
390 ret = -EIO;
391 goto out;
392 }
393
394out:
395 db_remove_reset(stmt);
396 return ret;
397}
398
Oliver Smith81db3892019-01-09 12:03:51 +0100399/*! Set a subscriber's IMEI in the HLR database.
400 * \param[in,out] dbc database context.
401 * \param[in] imsi ASCII string of IMSI digits
402 * \param[in] imei ASCII string of identifier digits, or NULL to remove the IMEI.
403 * \returns 0 on success, -ENOENT when the given subscriber does not exist,
404 * -EIO on database errors.
405 */
406int db_subscr_update_imei_by_imsi(struct db_context *dbc, const char* imsi, const char *imei)
407{
408 int rc, ret = 0;
409 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_UPD_IMEI_BY_IMSI];
410
411 if (imei && !osmo_imei_str_valid(imei, false)) {
412 LOGP(DAUC, LOGL_ERROR, "Cannot update subscriber IMSI='%s': invalid IMEI: '%s'\n", imsi, imei);
413 return -EINVAL;
414 }
415
416 if (!db_bind_text(stmt, "$imsi", imsi))
417 return -EIO;
418 if (imei && !db_bind_text(stmt, "$imei", imei))
419 return -EIO;
420
421 /* execute the statement */
422 rc = sqlite3_step(stmt);
423 if (rc != SQLITE_DONE) {
424 LOGP(DAUC, LOGL_ERROR, "Update IMEI for subscriber IMSI='%s': SQL Error: %s\n", imsi,
425 sqlite3_errmsg(dbc->db));
426 ret = -EIO;
427 goto out;
428 }
429
430 /* verify execution result */
431 rc = sqlite3_changes(dbc->db);
432 if (!rc) {
433 LOGP(DAUC, LOGL_ERROR, "Cannot update IMEI for subscriber IMSI='%s': no such subscriber\n", imsi);
434 ret = -ENOENT;
435 } else if (rc != 1) {
436 LOGP(DAUC, LOGL_ERROR, "Update IMEI for subscriber IMSI='%s': SQL modified %d rows (expected 1)\n",
437 imsi, rc);
438 ret = -EIO;
439 }
440
441out:
442 db_remove_reset(stmt);
443 return ret;
444}
445
Neels Hofmeyr07e16022019-11-20 02:36:35 +0100446static void parse_last_lu_seen(time_t *dst, const char *last_lu_seen_str, const char *imsi, const char *label)
447{
448 struct tm tm = {0};
449 time_t val;
450 if (!last_lu_seen_str || last_lu_seen_str[0] == '\0')
451 return;
452
453 if (strptime(last_lu_seen_str, DB_LAST_LU_SEEN_FMT, &tm) == NULL) {
454 LOGP(DAUC, LOGL_ERROR, "IMSI-%s: Last LU Seen %s: Cannot parse timestamp '%s'\n",
455 imsi, label, last_lu_seen_str);
456 return;
457 }
458
459 errno = 0;
460 val = mktime(&tm);
461 if (val == -1) {
462 LOGP(DAUC, LOGL_ERROR, "IMSI-%s: Last LU Seen %s: Cannot convert timestamp '%s' to time_t: %s\n",
463 imsi, label, last_lu_seen_str, strerror(errno));
464 val = 0;
465 }
466
467 *dst = val;
468}
469
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200470/* Common code for db_subscr_get_by_*() functions. */
471static int db_sel(struct db_context *dbc, sqlite3_stmt *stmt, struct hlr_subscriber *subscr,
472 const char **err)
Harald Weltee687be52016-05-03 18:49:27 +0200473{
Maxadc66482017-02-20 11:23:20 +0100474 int rc;
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200475 int ret = 0;
Harald Weltee687be52016-05-03 18:49:27 +0200476
477 /* execute the statement */
478 rc = sqlite3_step(stmt);
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200479 if (rc == SQLITE_DONE) {
480 ret = -ENOENT;
481 goto out;
482 }
Harald Weltee687be52016-05-03 18:49:27 +0200483 if (rc != SQLITE_ROW) {
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200484 ret = -EIO;
485 goto out;
Maxadc66482017-02-20 11:23:20 +0100486 }
487
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200488 if (!subscr)
489 goto out;
Harald Weltee687be52016-05-03 18:49:27 +0200490
Neels Hofmeyrb6837e32017-10-10 23:20:26 +0200491 *subscr = (struct hlr_subscriber){};
492
Harald Weltee687be52016-05-03 18:49:27 +0200493 /* obtain the various columns */
494 subscr->id = sqlite3_column_int64(stmt, 0);
Neels Hofmeyrdbced932017-10-27 02:57:51 +0200495 copy_sqlite3_text_to_buf(subscr->imsi, stmt, 1);
496 copy_sqlite3_text_to_buf(subscr->msisdn, stmt, 2);
Oliver Smith81db3892019-01-09 12:03:51 +0100497 copy_sqlite3_text_to_buf(subscr->imei, stmt, 3);
Harald Welte99909272016-05-05 18:24:15 +0200498 /* FIXME: These should all be BLOBs as they might contain NUL */
Oliver Smith81db3892019-01-09 12:03:51 +0100499 copy_sqlite3_text_to_buf(subscr->vlr_number, stmt, 4);
500 copy_sqlite3_text_to_buf(subscr->sgsn_number, stmt, 5);
501 copy_sqlite3_text_to_buf(subscr->sgsn_address, stmt, 6);
502 subscr->periodic_lu_timer = sqlite3_column_int(stmt, 7);
503 subscr->periodic_rau_tau_timer = sqlite3_column_int(stmt, 8);
504 subscr->nam_cs = sqlite3_column_int(stmt, 9);
505 subscr->nam_ps = sqlite3_column_int(stmt, 10);
506 subscr->lmsi = sqlite3_column_int(stmt, 11);
507 subscr->ms_purged_cs = sqlite3_column_int(stmt, 12);
508 subscr->ms_purged_ps = sqlite3_column_int(stmt, 13);
Neels Hofmeyr07e16022019-11-20 02:36:35 +0100509 parse_last_lu_seen(&subscr->last_lu_seen, (const char *)sqlite3_column_text(stmt, 14),
510 subscr->imsi, "CS");
511 parse_last_lu_seen(&subscr->last_lu_seen_ps, (const char *)sqlite3_column_text(stmt, 15),
512 subscr->imsi, "PS");
Neels Hofmeyr04c23752019-11-25 03:59:50 +0100513 copy_sqlite3_text_to_ipa_name(&subscr->vlr_via_proxy, stmt, 16);
514 copy_sqlite3_text_to_ipa_name(&subscr->sgsn_via_proxy, stmt, 17);
Harald Weltee687be52016-05-03 18:49:27 +0200515
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200516out:
Max00b37152017-02-20 11:09:27 +0100517 db_remove_reset(stmt);
Harald Weltee687be52016-05-03 18:49:27 +0200518
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200519 switch (ret) {
520 case 0:
521 *err = NULL;
522 break;
523 case -ENOENT:
524 *err = "No such subscriber";
525 break;
526 default:
527 *err = sqlite3_errmsg(dbc->db);
528 break;
529 }
530 return ret;
531}
532
Oliver Smith6b73fd92019-03-06 13:49:05 +0100533/*! Check if a subscriber exists in the HLR database.
534 * \param[in, out] dbc database context.
535 * \param[in] imsi ASCII string of IMSI digits.
536 * \returns 0 if it exists, -ENOENT if it does not exist, -EIO on database error.
537 */
538int db_subscr_exists_by_imsi(struct db_context *dbc, const char *imsi) {
539 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_EXISTS_BY_IMSI];
540 const char *err;
541 int rc;
542
543 if (!db_bind_text(stmt, NULL, imsi))
544 return -EIO;
545
546 rc = sqlite3_step(stmt);
547 db_remove_reset(stmt);
548 if (rc == SQLITE_ROW)
549 return 0; /* exists */
550 if (rc == SQLITE_DONE)
551 return -ENOENT; /* does not exist */
552
553 err = sqlite3_errmsg(dbc->db);
554 LOGP(DAUC, LOGL_ERROR, "Failed to check if subscriber exists by IMSI='%s': %s\n", imsi, err);
555 return rc;
556}
557
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200558/*! Retrieve subscriber data from the HLR database.
559 * \param[in,out] dbc database context.
560 * \param[in] imsi ASCII string of IMSI digits.
561 * \param[out] subscr place retrieved data in this struct.
562 * \returns 0 on success, -ENOENT if no such subscriber was found, -EIO on
563 * database error.
564 */
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200565int db_subscr_get_by_imsi(struct db_context *dbc, const char *imsi,
566 struct hlr_subscriber *subscr)
567{
568 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_IMSI];
569 const char *err;
570 int rc;
571
572 if (!db_bind_text(stmt, NULL, imsi))
573 return -EIO;
574
575 rc = db_sel(dbc, stmt, subscr, &err);
Neels Hofmeyr0d82a872019-11-25 05:37:53 +0100576 if (rc && rc != -ENOENT)
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200577 LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: IMSI='%s': %s\n",
578 imsi, err);
579 return rc;
580}
581
Vadim Yanitskiyc13599d2019-03-30 17:03:42 +0700582/*! Check if a subscriber exists in the HLR database.
583 * \param[in, out] dbc database context.
584 * \param[in] msisdn ASCII string of MSISDN digits.
585 * \returns 0 if it exists, -ENOENT if it does not exist, -EIO on database error.
586 */
587int db_subscr_exists_by_msisdn(struct db_context *dbc, const char *msisdn)
588{
589 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_EXISTS_BY_MSISDN];
590 const char *err;
591 int rc;
592
593 if (!db_bind_text(stmt, NULL, msisdn))
594 return -EIO;
595
596 rc = sqlite3_step(stmt);
597 db_remove_reset(stmt);
598 if (rc == SQLITE_ROW)
599 return 0; /* exists */
600 if (rc == SQLITE_DONE)
601 return -ENOENT; /* does not exist */
602
603 err = sqlite3_errmsg(dbc->db);
604 LOGP(DAUC, LOGL_ERROR, "Failed to check if subscriber exists "
605 "by MSISDN='%s': %s\n", msisdn, err);
606 return rc;
607}
608
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200609/*! Retrieve subscriber data from the HLR database.
610 * \param[in,out] dbc database context.
611 * \param[in] msisdn ASCII string of MSISDN digits.
612 * \param[out] subscr place retrieved data in this struct.
613 * \returns 0 on success, -ENOENT if no such subscriber was found, -EIO on
614 * database error.
615 */
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200616int db_subscr_get_by_msisdn(struct db_context *dbc, const char *msisdn,
617 struct hlr_subscriber *subscr)
618{
619 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_MSISDN];
620 const char *err;
621 int rc;
622
623 if (!db_bind_text(stmt, NULL, msisdn))
624 return -EIO;
625
626 rc = db_sel(dbc, stmt, subscr, &err);
Neels Hofmeyr0d82a872019-11-25 05:37:53 +0100627 if (rc && rc != -ENOENT)
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200628 LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: MSISDN='%s': %s\n",
629 msisdn, err);
630 return rc;
631}
632
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200633/*! Retrieve subscriber data from the HLR database.
634 * \param[in,out] dbc database context.
Keith89fda302021-01-19 07:01:33 +0100635 * \param[in] filter_type ASCII string of identifier type to search.
636 * \param[in] filter ASCII string to search.
637 * \param[in] get_cb pointer to call back function for data.
638 * \param[in,out] data pointer to pass to callback function.
639 * \param[in,out] count counter for number of matched subscribers.
640 * \param[in,our] err
641 * \returns 0 on success, -ENOENT if no subscriber was found, -EIO on
642 * database error.
643 */
644int db_subscrs_get(struct db_context *dbc, const char *filter_type, const char *filter,
645 void (*get_cb)(struct hlr_subscriber *subscr, void *data), void *data,
646 int *count, const char **err)
647{
648 sqlite3_stmt *stmt;
649 char search[256];
650 int rc;
651 struct hlr_subscriber subscr;
652 bool show_ls = false;
653
654 if (!filter_type) {
655 stmt = dbc->stmt[DB_STMT_SEL_ALL];
Keithca8e6ef2021-05-07 05:59:21 +0200656 } else if (strcmp(filter_type, "imei") == 0) {
657 stmt = dbc->stmt[DB_STMT_SEL_FILTER_IMEI];
Keith89fda302021-01-19 07:01:33 +0100658 } else if (strcmp(filter_type, "imsi") == 0) {
659 stmt = dbc->stmt[DB_STMT_SEL_FILTER_IMSI];
660 } else if (strcmp(filter_type, "msisdn") == 0) {
661 stmt = dbc->stmt[DB_STMT_SEL_FILTER_MSISDN];
662 } else if (strcmp(filter_type, "cs") == 0) {
663 stmt = dbc->stmt[DB_STMT_SEL_FILTER_CS];
664 } else if (strcmp(filter_type, "ps") == 0) {
665 stmt = dbc->stmt[DB_STMT_SEL_FILTER_PS];
666 } else if (strcmp(filter_type, "last_lu_seen") == 0) {
667 show_ls = true;
668 stmt = dbc->stmt[DB_STMT_SEL_ALL_ORDER_LAST_SEEN];
669 } else {
670 return -EIO;
671 }
672
Keithb5a56762021-01-30 13:49:15 -0600673 if (filter_type && filter && strcmp(filter_type, "last_lu_seen") != 0) {
Keith89fda302021-01-19 07:01:33 +0100674 if (strcmp(filter, "on") == 0) {
675 sprintf(search, "%s", "1");
676 } else if (strcmp(filter, "off") == 0) {
677 sprintf(search, "%s", "0");
678 } else {
679 sprintf(search, "%%%s%%", filter);
680 }
681 if (!db_bind_text(stmt, "$search", search)) {
682 *err = sqlite3_errmsg(dbc->db);
683 return -EIO;
684 }
685 }
686
687 rc = sqlite3_step(stmt);
688
689 if (rc == SQLITE_DONE) {
690 db_remove_reset(stmt);
691 *err = "No matching subscriber(s)";
692 return -ENOENT;
693 }
694
695 while (rc == SQLITE_ROW) {
696 subscr = (struct hlr_subscriber){
697 .id = sqlite3_column_int64(stmt, 0),};
698 copy_sqlite3_text_to_buf(subscr.imsi, stmt, 1);
699 copy_sqlite3_text_to_buf(subscr.msisdn, stmt, 2);
700 copy_sqlite3_text_to_buf(subscr.imei, stmt, 3);
701 subscr.nam_cs = sqlite3_column_int(stmt, 9);
702 subscr.nam_ps = sqlite3_column_int(stmt, 10);
703 if (show_ls)
704 parse_last_lu_seen(&subscr.last_lu_seen, (const char *)sqlite3_column_text(stmt, 14),
705 subscr.imsi, "CS");
706 get_cb(&subscr, data);
707 rc = sqlite3_step(stmt);
708 (*count)++;
709 }
710
711 db_remove_reset(stmt);
712 if (rc != SQLITE_DONE) {
713 *err = sqlite3_errmsg(dbc->db);
Keith89fda302021-01-19 07:01:33 +0100714 LOGP(DAUC, LOGL_ERROR, "Cannot read subscribers from db:: %s\n", *err);
715 return rc;
716 }
Keithb5a56762021-01-30 13:49:15 -0600717 *err = NULL;
718 return 0;
Keith89fda302021-01-19 07:01:33 +0100719}
720
721/*! Retrieve subscriber data from the HLR database.
722 * \param[in,out] dbc database context.
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200723 * \param[in] id ID of the subscriber in the HLR db.
724 * \param[out] subscr place retrieved data in this struct.
725 * \returns 0 on success, -ENOENT if no such subscriber was found, -EIO on
726 * database error.
727 */
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200728int db_subscr_get_by_id(struct db_context *dbc, int64_t id,
729 struct hlr_subscriber *subscr)
730{
731 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_ID];
732 const char *err;
733 int rc;
734
735 if (!db_bind_int64(stmt, NULL, id))
736 return -EIO;
737
738 rc = db_sel(dbc, stmt, subscr, &err);
Neels Hofmeyr0d82a872019-11-25 05:37:53 +0100739 if (rc && rc != -ENOENT)
Stefan Sperling705b61b2018-12-07 12:44:50 +0100740 LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: ID=%" PRId64 ": %s\n",
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200741 id, err);
742 return rc;
Harald Weltee687be52016-05-03 18:49:27 +0200743}
744
Oliver Smith81db3892019-01-09 12:03:51 +0100745/*! Retrieve subscriber data from the HLR database.
746 * \param[in,out] dbc database context.
747 * \param[in] imei ASCII string of identifier digits
748 * \param[out] subscr place retrieved data in this struct.
749 * \returns 0 on success, -ENOENT if no such subscriber was found, -EIO on
750 * database error.
751 */
752int db_subscr_get_by_imei(struct db_context *dbc, const char *imei, struct hlr_subscriber *subscr)
753{
754 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_IMEI];
755 const char *err;
756 int rc;
757
758 if (!db_bind_text(stmt, NULL, imei))
759 return -EIO;
760
761 rc = db_sel(dbc, stmt, subscr, &err);
Neels Hofmeyr0d82a872019-11-25 05:37:53 +0100762 if (rc && rc != -ENOENT)
Oliver Smith81db3892019-01-09 12:03:51 +0100763 LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: IMEI=%s: %s\n", imei, err);
764 return rc;
765}
766
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200767/*! You should use hlr_subscr_nam() instead; enable or disable PS or CS for a
768 * subscriber without notifying GSUP clients.
769 * \param[in,out] dbc database context.
770 * \param[in] imsi ASCII string of IMSI digits.
771 * \param[in] nam_val True to enable CS/PS, false to disable.
772 * \param[in] is_ps when true, set nam_ps, else set nam_cs.
773 * \returns 0 on success, -ENOENT when the given IMSI does not exist, -EIO on
774 * database errors.
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200775 */
776int db_subscr_nam(struct db_context *dbc, const char *imsi, bool nam_val, bool is_ps)
Max3ce36862017-02-20 11:18:04 +0100777{
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200778 sqlite3_stmt *stmt;
Max3ce36862017-02-20 11:18:04 +0100779 int rc;
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200780 int ret = 0;
Max3ce36862017-02-20 11:18:04 +0100781
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200782 stmt = dbc->stmt[is_ps ? DB_STMT_UPD_NAM_PS_BY_IMSI
783 : DB_STMT_UPD_NAM_CS_BY_IMSI];
Max3ce36862017-02-20 11:18:04 +0100784
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200785 if (!db_bind_text(stmt, "$imsi", imsi))
786 return -EIO;
787 if (!db_bind_int(stmt, "$val", nam_val ? 1 : 0))
788 return -EIO;
789
790 /* execute the statement */
791 rc = sqlite3_step(stmt);
Max3ce36862017-02-20 11:18:04 +0100792 if (rc != SQLITE_DONE) {
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200793 LOGHLR(imsi, LOGL_ERROR, "%s %s: SQL error: %s\n",
794 nam_val ? "enable" : "disable",
795 is_ps ? "PS" : "CS",
796 sqlite3_errmsg(dbc->db));
797 ret = -EIO;
798 goto out;
Max3ce36862017-02-20 11:18:04 +0100799 }
800
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200801 /* verify execution result */
802 rc = sqlite3_changes(dbc->db);
803 if (!rc) {
804 LOGP(DAUC, LOGL_ERROR, "Cannot %s %s: no such subscriber: IMSI='%s'\n",
805 nam_val ? "enable" : "disable",
806 is_ps ? "PS" : "CS",
807 imsi);
808 ret = -ENOENT;
809 goto out;
810 } else if (rc != 1) {
811 LOGHLR(imsi, LOGL_ERROR, "%s %s: SQL modified %d rows (expected 1)\n",
812 nam_val ? "enable" : "disable",
813 is_ps ? "PS" : "CS",
Max3ce36862017-02-20 11:18:04 +0100814 rc);
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200815 ret = -EIO;
Max3ce36862017-02-20 11:18:04 +0100816 }
817
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200818out:
Max3ce36862017-02-20 11:18:04 +0100819 db_remove_reset(stmt);
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200820 return ret;
Max3ce36862017-02-20 11:18:04 +0100821}
822
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200823/*! Record a Location Updating in the database.
824 * \param[in,out] dbc database context.
825 * \param[in] subscr_id ID of the subscriber in the HLR db.
826 * \param[in] vlr_or_sgsn_number ASCII string of identifier digits.
827 * \param[in] is_ps when true, set sgsn_number, else set vlr_number.
828 * \returns 0 on success, -ENOENT when the given subscriber does not exist,
829 * -EIO on database errors.
830 */
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200831int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
Neels Hofmeyrad868e22019-11-20 02:36:45 +0100832 const struct osmo_ipa_name *vlr_name, bool is_ps,
833 const struct osmo_ipa_name *via_proxy)
Harald Weltee687be52016-05-03 18:49:27 +0200834{
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200835 sqlite3_stmt *stmt;
Harald Weltee687be52016-05-03 18:49:27 +0200836 int rc, ret = 0;
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100837 struct timespec localtime;
Harald Weltee687be52016-05-03 18:49:27 +0200838
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200839 stmt = dbc->stmt[is_ps ? DB_STMT_UPD_SGSN_BY_ID
840 : DB_STMT_UPD_VLR_BY_ID];
Harald Weltee687be52016-05-03 18:49:27 +0200841
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200842 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
843 return -EIO;
Harald Weltee687be52016-05-03 18:49:27 +0200844
Neels Hofmeyrad868e22019-11-20 02:36:45 +0100845 if (!db_bind_text(stmt, "$number", (char*)vlr_name->val))
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200846 return -EIO;
Harald Weltee687be52016-05-03 18:49:27 +0200847
Neels Hofmeyr04c23752019-11-25 03:59:50 +0100848 if (via_proxy && via_proxy->len) {
849 if (!db_bind_text(stmt, "$proxy", (char*)via_proxy->val))
850 return -EIO;
851 } else {
852 if (!db_bind_null(stmt, "$proxy"))
853 return -EIO;
854 }
855
Harald Weltee687be52016-05-03 18:49:27 +0200856 /* execute the statement */
857 rc = sqlite3_step(stmt);
858 if (rc != SQLITE_DONE) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100859 LOGP(DAUC, LOGL_ERROR, "Update %s number for subscriber ID=%" PRId64 ": SQL Error: %s\n",
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200860 is_ps? "SGSN" : "VLR", subscr_id, sqlite3_errmsg(dbc->db));
861 ret = -EIO;
862 goto out;
Harald Weltee687be52016-05-03 18:49:27 +0200863 }
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200864
865 /* verify execution result */
866 rc = sqlite3_changes(dbc->db);
867 if (!rc) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100868 LOGP(DAUC, LOGL_ERROR, "Cannot update %s number for subscriber ID=%" PRId64
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200869 ": no such subscriber\n",
870 is_ps? "SGSN" : "VLR", subscr_id);
871 ret = -ENOENT;
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100872 goto out;
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200873 } else if (rc != 1) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100874 LOGP(DAUC, LOGL_ERROR, "Update %s number for subscriber ID=%" PRId64
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200875 ": SQL modified %d rows (expected 1)\n",
876 is_ps? "SGSN" : "VLR", subscr_id, rc);
877 ret = -EIO;
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100878 goto out;
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200879 }
880
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100881 db_remove_reset(stmt);
882
883 if (osmo_clock_gettime(CLOCK_REALTIME, &localtime) != 0) {
884 LOGP(DAUC, LOGL_ERROR, "Cannot get the current time: (%d) %s\n", errno, strerror(errno));
885 ret = -errno;
886 goto out;
887 }
888
Neels Hofmeyr07e16022019-11-20 02:36:35 +0100889 stmt = dbc->stmt[is_ps? DB_STMT_SET_LAST_LU_SEEN_PS : DB_STMT_SET_LAST_LU_SEEN];
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100890
891 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
892 return -EIO;
893 /* The timestamp will be converted to UTC by SQLite. */
894 if (!db_bind_int64(stmt, "$val", (int64_t)localtime.tv_sec)) {
895 ret = -EIO;
896 goto out;
897 }
898
899 rc = sqlite3_step(stmt);
900 if (rc != SQLITE_DONE) {
901 LOGP(DAUC, LOGL_ERROR,
Stefan Sperling705b61b2018-12-07 12:44:50 +0100902 "Cannot update LU timestamp for subscriber ID=%" PRId64 ": SQL error: (%d) %s\n",
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100903 subscr_id, rc, sqlite3_errmsg(dbc->db));
904 ret = -EIO;
905 goto out;
906 }
907
908 /* verify execution result */
909 rc = sqlite3_changes(dbc->db);
910 if (!rc) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100911 LOGP(DAUC, LOGL_ERROR, "Cannot update LU timestamp for subscriber ID=%" PRId64
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100912 ": no such subscriber\n", subscr_id);
913 ret = -ENOENT;
914 goto out;
915 } else if (rc != 1) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100916 LOGP(DAUC, LOGL_ERROR, "Update LU timestamp for subscriber ID=%" PRId64
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100917 ": SQL modified %d rows (expected 1)\n", subscr_id, rc);
918 ret = -EIO;
919 }
Harald Weltee687be52016-05-03 18:49:27 +0200920out:
Max00b37152017-02-20 11:09:27 +0100921 db_remove_reset(stmt);
Harald Weltee687be52016-05-03 18:49:27 +0200922 return ret;
923}
Harald Welteb18f0e02016-05-05 21:03:03 +0200924
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200925/*! Set the ms_purged_cs or ms_purged_ps values in the database.
926 * \param[in,out] dbc database context.
927 * \param[in] by_imsi ASCII string of IMSI digits.
928 * \param[in] purge_val true to purge, false to un-purge.
929 * \param[in] is_ps when true, set ms_purged_ps, else set ms_purged_cs.
930 * \returns 0 on success, -ENOENT when the given IMSI does not exist, -EIO on
931 * database errors.
932 */
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200933int db_subscr_purge(struct db_context *dbc, const char *by_imsi,
934 bool purge_val, bool is_ps)
Harald Welteb18f0e02016-05-05 21:03:03 +0200935{
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200936 sqlite3_stmt *stmt;
937 int rc, ret = 0;
Harald Welteb18f0e02016-05-05 21:03:03 +0200938
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200939 stmt = dbc->stmt[is_ps ? DB_STMT_UPD_PURGE_PS_BY_IMSI
940 : DB_STMT_UPD_PURGE_CS_BY_IMSI];
Harald Welteb18f0e02016-05-05 21:03:03 +0200941
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200942 if (!db_bind_text(stmt, "$imsi", by_imsi))
943 return -EIO;
944 if (!db_bind_int(stmt, "$val", purge_val ? 1 : 0))
945 return -EIO;
Harald Welteb18f0e02016-05-05 21:03:03 +0200946
947 /* execute the statement */
948 rc = sqlite3_step(stmt);
949 if (rc != SQLITE_DONE) {
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200950 LOGP(DAUC, LOGL_ERROR, "%s %s: SQL error: %s\n",
951 purge_val ? "purge" : "un-purge",
952 is_ps ? "PS" : "CS",
953 sqlite3_errmsg(dbc->db));
954 ret = -EIO;
955 goto out;
Harald Welteb18f0e02016-05-05 21:03:03 +0200956 }
Max00b37152017-02-20 11:09:27 +0100957
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200958 /* verify execution result */
959 rc = sqlite3_changes(dbc->db);
960 if (!rc) {
961 LOGP(DAUC, LOGL_ERROR, "Cannot %s %s: no such subscriber: IMSI='%s'\n",
962 purge_val ? "purge" : "un-purge",
963 is_ps ? "PS" : "CS",
964 by_imsi);
965 ret = -ENOENT;
966 goto out;
967 } else if (rc != 1) {
968 LOGHLR(by_imsi, LOGL_ERROR, "%s %s: SQL modified %d rows (expected 1)\n",
969 purge_val ? "purge" : "un-purge",
970 is_ps ? "PS" : "CS",
971 rc);
972 ret = -EIO;
973 }
974
975out:
Max00b37152017-02-20 11:09:27 +0100976 db_remove_reset(stmt);
Harald Welteb18f0e02016-05-05 21:03:03 +0200977
978 return ret;
979}
Neels Hofmeyr3f9d1972019-12-12 04:04:53 +0100980
981static int _db_ind_run(struct db_context *dbc, sqlite3_stmt *stmt, const char *vlr, bool reset)
982{
983 int rc;
984
985 if (!db_bind_text(stmt, "$vlr", vlr))
986 return -EIO;
987
988 /* execute the statement */
989 rc = sqlite3_step(stmt);
990 if (reset)
991 db_remove_reset(stmt);
992 return rc;
993}
994
995static int _db_ind_add(struct db_context *dbc, const char *vlr)
996{
997 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_IND_ADD];
998 if (_db_ind_run(dbc, stmt, vlr, true) != SQLITE_DONE) {
999 LOGP(DDB, LOGL_ERROR, "Cannot create IND entry for %s\n", osmo_quote_str_c(OTC_SELECT, vlr, -1));
1000 return -EIO;
1001 }
1002 return 0;
1003}
1004
1005static int _db_ind_del(struct db_context *dbc, const char *vlr)
1006{
1007 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_IND_DEL];
1008 _db_ind_run(dbc, stmt, vlr, true);
1009 /* We don't really care about the result. If it didn't exist, then that was the goal anyway. */
1010 return 0;
1011}
1012
1013static int _db_ind_get(struct db_context *dbc, const char *vlr, unsigned int *ind)
1014{
1015 int ret = 0;
1016 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_IND_SELECT];
1017 int rc = _db_ind_run(dbc, stmt, vlr, false);
1018 if (rc == SQLITE_DONE) {
1019 /* Does not exist yet */
1020 ret = -ENOENT;
1021 goto out;
1022 } else if (rc != SQLITE_ROW) {
1023 LOGP(DDB, LOGL_ERROR, "Error executing SQL: %d\n", rc);
1024 ret = -EIO;
1025 goto out;
1026 }
1027
1028 OSMO_ASSERT(ind);
1029 *ind = sqlite3_column_int64(stmt, 0);
1030out:
1031 db_remove_reset(stmt);
1032 return ret;
1033}
1034
1035int _db_ind(struct db_context *dbc, const struct osmo_cni_peer_id *vlr,
1036 unsigned int *ind, bool del)
1037{
1038 const char *vlr_name = NULL;
1039 int rc;
1040
1041 switch (vlr->type) {
1042 case OSMO_CNI_PEER_ID_IPA_NAME:
1043 if (vlr->ipa_name.len < 2 || vlr->ipa_name.val[vlr->ipa_name.len - 1] != '\0') {
1044 LOGP(DDB, LOGL_ERROR, "Expecting VLR ipa_name to be zero terminated; found %s\n",
1045 osmo_ipa_name_to_str(&vlr->ipa_name));
1046 return -ENOTSUP;
1047 }
1048 vlr_name = (const char*)vlr->ipa_name.val;
1049 break;
1050 default:
1051 LOGP(DDB, LOGL_ERROR, "Unsupported osmo_cni_peer_id type: %s\n",
1052 osmo_cni_peer_id_type_name(vlr->type));
1053 return -ENOTSUP;
1054 }
1055
1056 if (del)
1057 return _db_ind_del(dbc, vlr_name);
1058
1059 rc = _db_ind_get(dbc, vlr_name, ind);
1060 if (!rc)
1061 return 0;
1062
1063 /* Does not exist yet, create. */
1064 rc = _db_ind_add(dbc, vlr_name);
1065 if (rc) {
1066 LOGP(DDB, LOGL_ERROR, "Error creating IND entry for %s\n", osmo_quote_str_c(OTC_SELECT, vlr_name, -1));
1067 return rc;
1068 }
1069
1070 /* To be sure, query again from scratch. */
1071 return _db_ind_get(dbc, vlr_name, ind);
1072}
1073
1074int db_ind(struct db_context *dbc, const struct osmo_cni_peer_id *vlr, unsigned int *ind)
1075{
1076 return _db_ind(dbc, vlr, ind, false);
1077}
1078
1079int db_ind_del(struct db_context *dbc, const struct osmo_cni_peer_id *vlr)
1080{
1081 return _db_ind(dbc, vlr, NULL, true);
1082}