blob: b800c0b3363e4deafe14f0202667d8bb595e7ec3 [file] [log] [blame]
Harald Weltee687be52016-05-03 18:49:27 +02001/* (C) 2015 by Harald Welte <laforge@gnumonks.org>
2 *
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>
31#include <osmocom/crypt/auth.h>
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020032#include <osmocom/gsm/gsm23003.h>
Harald Weltee687be52016-05-03 18:49:27 +020033
34#include <sqlite3.h>
35
36#include "logging.h"
Neels Hofmeyr00b1d432017-10-17 01:43:48 +020037#include "hlr.h"
Harald Weltee687be52016-05-03 18:49:27 +020038#include "db.h"
Neels Hofmeyr00b1d432017-10-17 01:43:48 +020039#include "gsup_server.h"
40#include "luop.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.
47 * \returns 0 on success, -EINVAL on invalid IMSI, -EIO on database error.
48 */
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020049int db_subscr_create(struct db_context *dbc, const char *imsi)
50{
51 sqlite3_stmt *stmt;
52 int rc;
53
54 if (!osmo_imsi_str_valid(imsi)) {
55 LOGP(DAUC, LOGL_ERROR, "Cannot create subscriber: invalid IMSI: '%s'\n",
56 imsi);
57 return -EINVAL;
58 }
59
60 stmt = dbc->stmt[DB_STMT_SUBSCR_CREATE];
61
62 if (!db_bind_text(stmt, "$imsi", imsi))
63 return -EIO;
64
65 /* execute the statement */
66 rc = sqlite3_step(stmt);
67 db_remove_reset(stmt);
68 if (rc != SQLITE_DONE) {
69 LOGHLR(imsi, LOGL_ERROR, "Cannot create subscriber: SQL error: (%d) %s\n",
70 rc, sqlite3_errmsg(dbc->db));
71 return -EIO;
72 }
73
74 return 0;
75}
76
Neels Hofmeyr16140f72017-10-25 19:17:18 +020077/*! Completely delete a subscriber record from the HLR database.
78 * Also remove authentication data.
79 * Future todo: also drop from all other database tables, which aren't used yet
80 * at the time of writing this.
81 * \param[in,out] dbc database context.
82 * \param[in] subscr_id ID of the subscriber in the HLR db.
83 * \returns if the subscriber was found and removed, -EIO on database error,
84 * -ENOENT if no such subscriber data exists.
85 */
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020086int db_subscr_delete_by_id(struct db_context *dbc, int64_t subscr_id)
87{
88 int rc;
Neels Hofmeyr1332a172017-10-10 02:25:00 +020089 struct sub_auth_data_str aud;
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020090 int ret = 0;
91
92 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_DEL_BY_ID];
93
94 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
95 return -EIO;
96
97 /* execute the statement */
98 rc = sqlite3_step(stmt);
99 if (rc != SQLITE_DONE) {
100 LOGP(DAUC, LOGL_ERROR,
Stefan Sperling705b61b2018-12-07 12:44:50 +0100101 "Cannot delete subscriber ID=%" PRId64 ": SQL error: (%d) %s\n",
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200102 subscr_id, rc, sqlite3_errmsg(dbc->db));
103 db_remove_reset(stmt);
104 return -EIO;
105 }
106
107 /* verify execution result */
108 rc = sqlite3_changes(dbc->db);
109 if (!rc) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100110 LOGP(DAUC, LOGL_ERROR, "Cannot delete: no such subscriber: ID=%" PRId64 "\n",
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200111 subscr_id);
112 ret = -ENOENT;
113 } else if (rc != 1) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100114 LOGP(DAUC, LOGL_ERROR, "Delete subscriber ID=%" PRId64
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200115 ": SQL modified %d rows (expected 1)\n", subscr_id, rc);
116 ret = -EIO;
117 }
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200118 db_remove_reset(stmt);
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200119
120 /* make sure to remove authentication data for this subscriber id, for
121 * both 2G and 3G. */
122
123 aud = (struct sub_auth_data_str){
124 .type = OSMO_AUTH_TYPE_GSM,
125 .algo = OSMO_AUTH_ALG_NONE,
126 };
127 rc = db_subscr_update_aud_by_id(dbc, subscr_id, &aud);
128 if (ret == -ENOENT && !rc)
129 ret = 0;
130
131 aud = (struct sub_auth_data_str){
132 .type = OSMO_AUTH_TYPE_UMTS,
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
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200139 return ret;
140}
141
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200142/*! Set a subscriber's MSISDN in the HLR database.
143 * \param[in,out] dbc database context.
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100144 * \param[in] imsi ASCII string of IMSI digits, or NULL to remove the MSISDN.
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200145 * \param[in] msisdn ASCII string of MSISDN digits.
146 * \returns 0 on success, -EINVAL in case of invalid MSISDN string, -EIO on
147 * database failure, -ENOENT if no such subscriber exists.
148 */
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200149int db_subscr_update_msisdn_by_imsi(struct db_context *dbc, const char *imsi,
150 const char *msisdn)
151{
152 int rc;
153 int ret = 0;
154
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100155 if (msisdn && !osmo_msisdn_str_valid(msisdn)) {
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200156 LOGHLR(imsi, LOGL_ERROR,
157 "Cannot update subscriber: invalid MSISDN: '%s'\n",
158 msisdn);
159 return -EINVAL;
160 }
161
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100162 sqlite3_stmt *stmt = dbc->stmt[
163 msisdn ? DB_STMT_SET_MSISDN_BY_IMSI : DB_STMT_DELETE_MSISDN_BY_IMSI];
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200164
165 if (!db_bind_text(stmt, "$imsi", imsi))
166 return -EIO;
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100167 if (msisdn) {
168 if (!db_bind_text(stmt, "$msisdn", msisdn))
169 return -EIO;
170 }
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200171
172 /* execute the statement */
173 rc = sqlite3_step(stmt);
174 if (rc != SQLITE_DONE) {
175 LOGHLR(imsi, LOGL_ERROR,
176 "Cannot update subscriber's MSISDN: SQL error: (%d) %s\n",
177 rc, sqlite3_errmsg(dbc->db));
178 ret = -EIO;
179 goto out;
180 }
181
182 /* verify execution result */
183 rc = sqlite3_changes(dbc->db);
184 if (!rc) {
185 LOGP(DAUC, LOGL_ERROR, "Cannot update MSISDN: no such subscriber: IMSI='%s'\n",
186 imsi);
187 ret = -ENOENT;
188 goto out;
189 } else if (rc != 1) {
190 LOGHLR(imsi, LOGL_ERROR, "Update MSISDN: SQL modified %d rows (expected 1)\n", rc);
191 ret = -EIO;
192 }
193
194out:
195 db_remove_reset(stmt);
196 return ret;
197
198}
199
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200200/*! Insert or update 2G or 3G authentication tokens in the database.
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200201 * If aud->type is OSMO_AUTH_TYPE_GSM, the auc_2g table entry for the
202 * subscriber will be added or modified; if aud->algo is OSMO_AUTH_ALG_NONE,
203 * however, the auc_2g entry for the subscriber is deleted. If aud->type is
204 * OSMO_AUTH_TYPE_UMTS, the auc_3g table is updated; again, if aud->algo is
205 * OSMO_AUTH_ALG_NONE, the auc_3g entry is deleted.
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200206 * \param[in,out] dbc database context.
207 * \param[in] subscr_id DB ID of the subscriber.
208 * \param[in] aud Pointer to new auth data (in ASCII string form).
209 * \returns 0 on success, -EINVAL for invalid aud, -ENOENT for unknown
210 * subscr_id, -EIO for database errors.
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200211 */
212int db_subscr_update_aud_by_id(struct db_context *dbc, int64_t subscr_id,
213 const struct sub_auth_data_str *aud)
214{
215 sqlite3_stmt *stmt_del;
216 sqlite3_stmt *stmt_ins;
217 sqlite3_stmt *stmt;
218 const char *label;
219 int rc;
220 int ret = 0;
221
222 switch (aud->type) {
223 case OSMO_AUTH_TYPE_GSM:
224 label = "auc_2g";
225 stmt_del = dbc->stmt[DB_STMT_AUC_2G_DELETE];
226 stmt_ins = dbc->stmt[DB_STMT_AUC_2G_INSERT];
227
228 switch (aud->algo) {
229 case OSMO_AUTH_ALG_NONE:
230 case OSMO_AUTH_ALG_COMP128v1:
231 case OSMO_AUTH_ALG_COMP128v2:
232 case OSMO_AUTH_ALG_COMP128v3:
233 case OSMO_AUTH_ALG_XOR:
234 break;
235 case OSMO_AUTH_ALG_MILENAGE:
236 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
237 " auth algo not suited for 2G: %s\n",
238 osmo_auth_alg_name(aud->algo));
239 return -EINVAL;
240 default:
241 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
242 " Unknown auth algo: %d\n", aud->algo);
243 return -EINVAL;
244 }
245
246 if (aud->algo == OSMO_AUTH_ALG_NONE)
247 break;
248 if (!osmo_is_hexstr(aud->u.gsm.ki, 32, 32, true)) {
249 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
250 " Invalid KI: '%s'\n", aud->u.gsm.ki);
251 return -EINVAL;
252 }
253 break;
254
255 case OSMO_AUTH_TYPE_UMTS:
256 label = "auc_3g";
257 stmt_del = dbc->stmt[DB_STMT_AUC_3G_DELETE];
258 stmt_ins = dbc->stmt[DB_STMT_AUC_3G_INSERT];
259 switch (aud->algo) {
260 case OSMO_AUTH_ALG_NONE:
261 case OSMO_AUTH_ALG_MILENAGE:
262 break;
263 case OSMO_AUTH_ALG_COMP128v1:
264 case OSMO_AUTH_ALG_COMP128v2:
265 case OSMO_AUTH_ALG_COMP128v3:
266 case OSMO_AUTH_ALG_XOR:
267 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
268 " auth algo not suited for 3G: %s\n",
269 osmo_auth_alg_name(aud->algo));
270 return -EINVAL;
271 default:
272 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
273 " Unknown auth algo: %d\n", aud->algo);
274 return -EINVAL;
275 }
276
277 if (aud->algo == OSMO_AUTH_ALG_NONE)
278 break;
279 if (!osmo_is_hexstr(aud->u.umts.k, 32, 32, true)) {
280 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
281 " Invalid K: '%s'\n", aud->u.umts.k);
282 return -EINVAL;
283 }
284 if (!osmo_is_hexstr(aud->u.umts.opc, 32, 32, true)) {
285 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
286 " Invalid OP/OPC: '%s'\n", aud->u.umts.opc);
287 return -EINVAL;
288 }
289 if (aud->u.umts.ind_bitlen > OSMO_MILENAGE_IND_BITLEN_MAX) {
290 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
291 " Invalid ind_bitlen: %d\n", aud->u.umts.ind_bitlen);
292 return -EINVAL;
293 }
294 break;
295 default:
296 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
297 " unknown auth type: %d\n", aud->type);
298 return -EINVAL;
299 }
300
301 stmt = stmt_del;
302
303 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
304 return -EIO;
305
306 /* execute the statement */
307 rc = sqlite3_step(stmt);
308 if (rc != SQLITE_DONE) {
309 LOGP(DAUC, LOGL_ERROR,
310 "Cannot delete %s row: SQL error: (%d) %s\n",
311 label, rc, sqlite3_errmsg(dbc->db));
312 ret = -EIO;
313 goto out;
314 }
315
316 /* verify execution result */
317 rc = sqlite3_changes(dbc->db);
318 if (!rc)
319 /* Leave "no such entry" logging to the caller -- during
320 * db_subscr_delete_by_id(), we call this to make sure it is
321 * empty, and no entry is not an error then.*/
322 ret = -ENOENT;
323 else if (rc != 1) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100324 LOGP(DAUC, LOGL_ERROR, "Delete subscriber ID=%" PRId64
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200325 " from %s: SQL modified %d rows (expected 1)\n",
326 subscr_id, label, rc);
327 ret = -EIO;
328 }
329
330 db_remove_reset(stmt);
331
332 /* Error situation? Return now. */
333 if (ret && ret != -ENOENT)
334 return ret;
335
336 /* Just delete requested? */
337 if (aud->algo == OSMO_AUTH_ALG_NONE)
338 return ret;
339
340 /* Don't return -ENOENT if inserting new data. */
341 ret = 0;
342
343 /* Insert new row */
344 stmt = stmt_ins;
345
346 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
347 return -EIO;
348
349 switch (aud->type) {
350 case OSMO_AUTH_TYPE_GSM:
351 if (!db_bind_int(stmt, "$algo_id_2g", aud->algo))
352 return -EIO;
353 if (!db_bind_text(stmt, "$ki", aud->u.gsm.ki))
354 return -EIO;
355 break;
356 case OSMO_AUTH_TYPE_UMTS:
357 if (!db_bind_int(stmt, "$algo_id_3g", aud->algo))
358 return -EIO;
359 if (!db_bind_text(stmt, "$k", aud->u.umts.k))
360 return -EIO;
361 if (!db_bind_text(stmt, "$op",
362 aud->u.umts.opc_is_op ? aud->u.umts.opc : NULL))
363 return -EIO;
364 if (!db_bind_text(stmt, "$opc",
365 aud->u.umts.opc_is_op ? NULL : aud->u.umts.opc))
366 return -EIO;
367 if (!db_bind_int(stmt, "$ind_bitlen", aud->u.umts.ind_bitlen))
368 return -EIO;
369 break;
370 default:
371 OSMO_ASSERT(false);
372 }
373
374 /* execute the statement */
375 rc = sqlite3_step(stmt);
376 if (rc != SQLITE_DONE) {
377 LOGP(DAUC, LOGL_ERROR,
378 "Cannot insert %s row: SQL error: (%d) %s\n",
379 label, rc, sqlite3_errmsg(dbc->db));
380 ret = -EIO;
381 goto out;
382 }
383
384out:
385 db_remove_reset(stmt);
386 return ret;
387}
388
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200389/* Common code for db_subscr_get_by_*() functions. */
390static int db_sel(struct db_context *dbc, sqlite3_stmt *stmt, struct hlr_subscriber *subscr,
391 const char **err)
Harald Weltee687be52016-05-03 18:49:27 +0200392{
Maxadc66482017-02-20 11:23:20 +0100393 int rc;
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200394 int ret = 0;
Stefan Sperling5c14c9c2018-12-07 12:30:21 +0100395 const char *last_lu_seen_str;
396 struct tm tm;
Harald Weltee687be52016-05-03 18:49:27 +0200397
398 /* execute the statement */
399 rc = sqlite3_step(stmt);
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200400 if (rc == SQLITE_DONE) {
401 ret = -ENOENT;
402 goto out;
403 }
Harald Weltee687be52016-05-03 18:49:27 +0200404 if (rc != SQLITE_ROW) {
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200405 ret = -EIO;
406 goto out;
Maxadc66482017-02-20 11:23:20 +0100407 }
408
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200409 if (!subscr)
410 goto out;
Harald Weltee687be52016-05-03 18:49:27 +0200411
Neels Hofmeyr2fb33f12018-12-26 01:49:53 +0100412 *subscr = hlr_subscriber_empty;
Neels Hofmeyrb6837e32017-10-10 23:20:26 +0200413
Harald Weltee687be52016-05-03 18:49:27 +0200414 /* obtain the various columns */
415 subscr->id = sqlite3_column_int64(stmt, 0);
Neels Hofmeyrdbced932017-10-27 02:57:51 +0200416 copy_sqlite3_text_to_buf(subscr->imsi, stmt, 1);
417 copy_sqlite3_text_to_buf(subscr->msisdn, stmt, 2);
Harald Welte99909272016-05-05 18:24:15 +0200418 /* FIXME: These should all be BLOBs as they might contain NUL */
Neels Hofmeyrdbced932017-10-27 02:57:51 +0200419 copy_sqlite3_text_to_buf(subscr->vlr_number, stmt, 3);
420 copy_sqlite3_text_to_buf(subscr->sgsn_number, stmt, 4);
421 copy_sqlite3_text_to_buf(subscr->sgsn_address, stmt, 5);
Harald Weltee687be52016-05-03 18:49:27 +0200422 subscr->periodic_lu_timer = sqlite3_column_int(stmt, 6);
423 subscr->periodic_rau_tau_timer = sqlite3_column_int(stmt, 7);
424 subscr->nam_cs = sqlite3_column_int(stmt, 8);
425 subscr->nam_ps = sqlite3_column_int(stmt, 9);
426 subscr->lmsi = sqlite3_column_int(stmt, 10);
427 subscr->ms_purged_cs = sqlite3_column_int(stmt, 11);
428 subscr->ms_purged_ps = sqlite3_column_int(stmt, 12);
Stefan Sperling5c14c9c2018-12-07 12:30:21 +0100429 last_lu_seen_str = (const char *)sqlite3_column_text(stmt, 13);
430 if (last_lu_seen_str && last_lu_seen_str[0] != '\0') {
431 if (strptime(last_lu_seen_str, DB_LAST_LU_SEEN_FMT, &tm) == NULL) {
432 LOGP(DAUC, LOGL_ERROR, "Cannot parse last LU timestamp '%s' of subscriber with IMSI='%s': %s\n",
433 last_lu_seen_str, subscr->imsi, strerror(errno));
434 } else {
435 subscr->last_lu_seen = mktime(&tm);
436 if (subscr->last_lu_seen == -1) {
437 LOGP(DAUC, LOGL_ERROR, "Cannot convert LU timestamp '%s' to time_t: %s\n",
438 last_lu_seen_str, strerror(errno));
439 subscr->last_lu_seen = 0;
440 }
441 }
442 }
Neels Hofmeyr0f9daa62018-12-29 03:28:38 +0100443 copy_sqlite3_text_to_buf(subscr->last_lu_rat, stmt, 14);
Harald Weltee687be52016-05-03 18:49:27 +0200444
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200445out:
Max00b37152017-02-20 11:09:27 +0100446 db_remove_reset(stmt);
Harald Weltee687be52016-05-03 18:49:27 +0200447
Neels Hofmeyr2fb33f12018-12-26 01:49:53 +0100448 if (ret == 0)
449 db_subscr_get_rat_types(dbc, subscr);
450
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200451 switch (ret) {
452 case 0:
453 *err = NULL;
454 break;
455 case -ENOENT:
456 *err = "No such subscriber";
457 break;
458 default:
459 *err = sqlite3_errmsg(dbc->db);
460 break;
461 }
462 return ret;
463}
464
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200465/*! Retrieve subscriber data from the HLR database.
466 * \param[in,out] dbc database context.
467 * \param[in] imsi ASCII string of IMSI digits.
468 * \param[out] subscr place retrieved data in this struct.
469 * \returns 0 on success, -ENOENT if no such subscriber was found, -EIO on
470 * database error.
471 */
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200472int db_subscr_get_by_imsi(struct db_context *dbc, const char *imsi,
473 struct hlr_subscriber *subscr)
474{
475 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_IMSI];
476 const char *err;
477 int rc;
478
479 if (!db_bind_text(stmt, NULL, imsi))
480 return -EIO;
481
482 rc = db_sel(dbc, stmt, subscr, &err);
483 if (rc)
484 LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: IMSI='%s': %s\n",
485 imsi, err);
486 return rc;
487}
488
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200489/*! Retrieve subscriber data from the HLR database.
490 * \param[in,out] dbc database context.
491 * \param[in] msisdn ASCII string of MSISDN digits.
492 * \param[out] subscr place retrieved data in this struct.
493 * \returns 0 on success, -ENOENT if no such subscriber was found, -EIO on
494 * database error.
495 */
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200496int db_subscr_get_by_msisdn(struct db_context *dbc, const char *msisdn,
497 struct hlr_subscriber *subscr)
498{
499 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_MSISDN];
500 const char *err;
501 int rc;
502
503 if (!db_bind_text(stmt, NULL, msisdn))
504 return -EIO;
505
506 rc = db_sel(dbc, stmt, subscr, &err);
507 if (rc)
508 LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: MSISDN='%s': %s\n",
509 msisdn, err);
510 return rc;
511}
512
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200513/*! Retrieve subscriber data from the HLR database.
514 * \param[in,out] dbc database context.
515 * \param[in] id ID of the subscriber in the HLR db.
516 * \param[out] subscr place retrieved data in this struct.
517 * \returns 0 on success, -ENOENT if no such subscriber was found, -EIO on
518 * database error.
519 */
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200520int db_subscr_get_by_id(struct db_context *dbc, int64_t id,
521 struct hlr_subscriber *subscr)
522{
523 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_ID];
524 const char *err;
525 int rc;
526
527 if (!db_bind_int64(stmt, NULL, id))
528 return -EIO;
529
530 rc = db_sel(dbc, stmt, subscr, &err);
531 if (rc)
Stefan Sperling705b61b2018-12-07 12:44:50 +0100532 LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: ID=%" PRId64 ": %s\n",
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200533 id, err);
534 return rc;
Harald Weltee687be52016-05-03 18:49:27 +0200535}
536
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200537/*! You should use hlr_subscr_nam() instead; enable or disable PS or CS for a
538 * subscriber without notifying GSUP clients.
539 * \param[in,out] dbc database context.
540 * \param[in] imsi ASCII string of IMSI digits.
541 * \param[in] nam_val True to enable CS/PS, false to disable.
542 * \param[in] is_ps when true, set nam_ps, else set nam_cs.
543 * \returns 0 on success, -ENOENT when the given IMSI does not exist, -EIO on
544 * database errors.
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200545 */
546int db_subscr_nam(struct db_context *dbc, const char *imsi, bool nam_val, bool is_ps)
Max3ce36862017-02-20 11:18:04 +0100547{
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200548 sqlite3_stmt *stmt;
Max3ce36862017-02-20 11:18:04 +0100549 int rc;
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200550 int ret = 0;
Max3ce36862017-02-20 11:18:04 +0100551
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200552 stmt = dbc->stmt[is_ps ? DB_STMT_UPD_NAM_PS_BY_IMSI
553 : DB_STMT_UPD_NAM_CS_BY_IMSI];
Max3ce36862017-02-20 11:18:04 +0100554
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200555 if (!db_bind_text(stmt, "$imsi", imsi))
556 return -EIO;
557 if (!db_bind_int(stmt, "$val", nam_val ? 1 : 0))
558 return -EIO;
559
560 /* execute the statement */
561 rc = sqlite3_step(stmt);
Max3ce36862017-02-20 11:18:04 +0100562 if (rc != SQLITE_DONE) {
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200563 LOGHLR(imsi, LOGL_ERROR, "%s %s: SQL error: %s\n",
564 nam_val ? "enable" : "disable",
565 is_ps ? "PS" : "CS",
566 sqlite3_errmsg(dbc->db));
567 ret = -EIO;
568 goto out;
Max3ce36862017-02-20 11:18:04 +0100569 }
570
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200571 /* verify execution result */
572 rc = sqlite3_changes(dbc->db);
573 if (!rc) {
574 LOGP(DAUC, LOGL_ERROR, "Cannot %s %s: no such subscriber: IMSI='%s'\n",
575 nam_val ? "enable" : "disable",
576 is_ps ? "PS" : "CS",
577 imsi);
578 ret = -ENOENT;
579 goto out;
580 } else if (rc != 1) {
581 LOGHLR(imsi, LOGL_ERROR, "%s %s: SQL modified %d rows (expected 1)\n",
582 nam_val ? "enable" : "disable",
583 is_ps ? "PS" : "CS",
Max3ce36862017-02-20 11:18:04 +0100584 rc);
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200585 ret = -EIO;
Max3ce36862017-02-20 11:18:04 +0100586 }
587
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200588out:
Max3ce36862017-02-20 11:18:04 +0100589 db_remove_reset(stmt);
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200590 return ret;
Max3ce36862017-02-20 11:18:04 +0100591}
592
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200593/*! Record a Location Updating in the database.
594 * \param[in,out] dbc database context.
595 * \param[in] subscr_id ID of the subscriber in the HLR db.
596 * \param[in] vlr_or_sgsn_number ASCII string of identifier digits.
597 * \param[in] is_ps when true, set sgsn_number, else set vlr_number.
598 * \returns 0 on success, -ENOENT when the given subscriber does not exist,
599 * -EIO on database errors.
600 */
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200601int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
Neels Hofmeyr0f9daa62018-12-29 03:28:38 +0100602 const char *vlr_or_sgsn_number, bool is_ps,
603 const enum osmo_rat_type rat_types[], size_t rat_types_len)
Harald Weltee687be52016-05-03 18:49:27 +0200604{
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200605 sqlite3_stmt *stmt;
Harald Weltee687be52016-05-03 18:49:27 +0200606 int rc, ret = 0;
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100607 struct timespec localtime;
Neels Hofmeyr0f9daa62018-12-29 03:28:38 +0100608 char rat_types_str[128] = "";
609 int i;
Harald Weltee687be52016-05-03 18:49:27 +0200610
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200611 stmt = dbc->stmt[is_ps ? DB_STMT_UPD_SGSN_BY_ID
612 : DB_STMT_UPD_VLR_BY_ID];
Harald Weltee687be52016-05-03 18:49:27 +0200613
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200614 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
615 return -EIO;
Harald Weltee687be52016-05-03 18:49:27 +0200616
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200617 if (!db_bind_text(stmt, "$number", vlr_or_sgsn_number))
618 return -EIO;
Harald Weltee687be52016-05-03 18:49:27 +0200619
620 /* execute the statement */
621 rc = sqlite3_step(stmt);
622 if (rc != SQLITE_DONE) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100623 LOGP(DAUC, LOGL_ERROR, "Update %s number for subscriber ID=%" PRId64 ": SQL Error: %s\n",
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200624 is_ps? "SGSN" : "VLR", subscr_id, sqlite3_errmsg(dbc->db));
625 ret = -EIO;
626 goto out;
Harald Weltee687be52016-05-03 18:49:27 +0200627 }
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200628
629 /* verify execution result */
630 rc = sqlite3_changes(dbc->db);
631 if (!rc) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100632 LOGP(DAUC, LOGL_ERROR, "Cannot update %s number for subscriber ID=%" PRId64
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200633 ": no such subscriber\n",
634 is_ps? "SGSN" : "VLR", subscr_id);
635 ret = -ENOENT;
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100636 goto out;
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200637 } else if (rc != 1) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100638 LOGP(DAUC, LOGL_ERROR, "Update %s number for subscriber ID=%" PRId64
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200639 ": SQL modified %d rows (expected 1)\n",
640 is_ps? "SGSN" : "VLR", subscr_id, rc);
641 ret = -EIO;
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100642 goto out;
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200643 }
644
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100645 db_remove_reset(stmt);
646
647 if (osmo_clock_gettime(CLOCK_REALTIME, &localtime) != 0) {
648 LOGP(DAUC, LOGL_ERROR, "Cannot get the current time: (%d) %s\n", errno, strerror(errno));
649 ret = -errno;
650 goto out;
651 }
652
653 stmt = dbc->stmt[DB_STMT_SET_LAST_LU_SEEN];
654
655 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
656 return -EIO;
657 /* The timestamp will be converted to UTC by SQLite. */
658 if (!db_bind_int64(stmt, "$val", (int64_t)localtime.tv_sec)) {
659 ret = -EIO;
660 goto out;
661 }
662
Neels Hofmeyr0f9daa62018-12-29 03:28:38 +0100663 for (i = 0; i < rat_types_len; i++) {
664 char *pos = rat_types_str + strnlen(rat_types_str, sizeof(rat_types_str));
Neels Hofmeyr081af6b2018-12-29 04:10:35 +0100665 int len = sizeof(rat_types_str) - (pos - rat_types_str);
Neels Hofmeyr0f9daa62018-12-29 03:28:38 +0100666 rc = snprintf(pos, len, "%s%s", pos == rat_types_str ? "" : ",", osmo_rat_type_name(rat_types[i]));
667 if (rc > len) {
668 osmo_strlcpy(rat_types_str + sizeof(rat_types_str) - 4, "...", 4);
669 break;
670 }
671 }
672
673 if (!db_bind_text(stmt, "$rat", rat_types_str)) {
674 ret = -EIO;
675 goto out;
676 }
677
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100678 rc = sqlite3_step(stmt);
679 if (rc != SQLITE_DONE) {
680 LOGP(DAUC, LOGL_ERROR,
Stefan Sperling705b61b2018-12-07 12:44:50 +0100681 "Cannot update LU timestamp for subscriber ID=%" PRId64 ": SQL error: (%d) %s\n",
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100682 subscr_id, rc, sqlite3_errmsg(dbc->db));
683 ret = -EIO;
684 goto out;
685 }
686
687 /* verify execution result */
688 rc = sqlite3_changes(dbc->db);
689 if (!rc) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100690 LOGP(DAUC, LOGL_ERROR, "Cannot update LU timestamp for subscriber ID=%" PRId64
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100691 ": no such subscriber\n", subscr_id);
692 ret = -ENOENT;
693 goto out;
694 } else if (rc != 1) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100695 LOGP(DAUC, LOGL_ERROR, "Update LU timestamp for subscriber ID=%" PRId64
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100696 ": SQL modified %d rows (expected 1)\n", subscr_id, rc);
697 ret = -EIO;
698 }
Harald Weltee687be52016-05-03 18:49:27 +0200699out:
Max00b37152017-02-20 11:09:27 +0100700 db_remove_reset(stmt);
Harald Weltee687be52016-05-03 18:49:27 +0200701 return ret;
702}
Harald Welteb18f0e02016-05-05 21:03:03 +0200703
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200704/*! Set the ms_purged_cs or ms_purged_ps values in the database.
705 * \param[in,out] dbc database context.
706 * \param[in] by_imsi ASCII string of IMSI digits.
707 * \param[in] purge_val true to purge, false to un-purge.
708 * \param[in] is_ps when true, set ms_purged_ps, else set ms_purged_cs.
709 * \returns 0 on success, -ENOENT when the given IMSI does not exist, -EIO on
710 * database errors.
711 */
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200712int db_subscr_purge(struct db_context *dbc, const char *by_imsi,
713 bool purge_val, bool is_ps)
Harald Welteb18f0e02016-05-05 21:03:03 +0200714{
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200715 sqlite3_stmt *stmt;
716 int rc, ret = 0;
Harald Welteb18f0e02016-05-05 21:03:03 +0200717
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200718 stmt = dbc->stmt[is_ps ? DB_STMT_UPD_PURGE_PS_BY_IMSI
719 : DB_STMT_UPD_PURGE_CS_BY_IMSI];
Harald Welteb18f0e02016-05-05 21:03:03 +0200720
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200721 if (!db_bind_text(stmt, "$imsi", by_imsi))
722 return -EIO;
723 if (!db_bind_int(stmt, "$val", purge_val ? 1 : 0))
724 return -EIO;
Harald Welteb18f0e02016-05-05 21:03:03 +0200725
726 /* execute the statement */
727 rc = sqlite3_step(stmt);
728 if (rc != SQLITE_DONE) {
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200729 LOGP(DAUC, LOGL_ERROR, "%s %s: SQL error: %s\n",
730 purge_val ? "purge" : "un-purge",
731 is_ps ? "PS" : "CS",
732 sqlite3_errmsg(dbc->db));
733 ret = -EIO;
734 goto out;
Harald Welteb18f0e02016-05-05 21:03:03 +0200735 }
Max00b37152017-02-20 11:09:27 +0100736
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200737 /* verify execution result */
738 rc = sqlite3_changes(dbc->db);
739 if (!rc) {
740 LOGP(DAUC, LOGL_ERROR, "Cannot %s %s: no such subscriber: IMSI='%s'\n",
741 purge_val ? "purge" : "un-purge",
742 is_ps ? "PS" : "CS",
743 by_imsi);
744 ret = -ENOENT;
745 goto out;
746 } else if (rc != 1) {
747 LOGHLR(by_imsi, LOGL_ERROR, "%s %s: SQL modified %d rows (expected 1)\n",
748 purge_val ? "purge" : "un-purge",
749 is_ps ? "PS" : "CS",
750 rc);
751 ret = -EIO;
752 }
753
754out:
Max00b37152017-02-20 11:09:27 +0100755 db_remove_reset(stmt);
Harald Welteb18f0e02016-05-05 21:03:03 +0200756
757 return ret;
758}
Neels Hofmeyr00b1d432017-10-17 01:43:48 +0200759
760/*! Update nam_cs/nam_ps in the db and trigger notifications to GSUP clients.
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200761 * \param[in,out] hlr Global hlr context.
762 * \param[in] subscr Subscriber from a fresh db_subscr_get_by_*() call.
763 * \param[in] nam_val True to enable CS/PS, false to disable.
764 * \param[in] is_ps True to enable/disable PS, false for CS.
Neels Hofmeyr00b1d432017-10-17 01:43:48 +0200765 * \returns 0 on success, ENOEXEC if there is no need to change, a negative
766 * value on error.
767 */
768int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps)
769{
770 int rc;
771 struct lu_operation *luop;
772 struct osmo_gsup_conn *co;
773 bool is_val = is_ps? subscr->nam_ps : subscr->nam_cs;
774
775 if (is_val == nam_val) {
776 LOGHLR(subscr->imsi, LOGL_DEBUG, "Already has the requested value when asked to %s %s\n",
777 nam_val ? "enable" : "disable", is_ps ? "PS" : "CS");
778 return ENOEXEC;
779 }
780
781 rc = db_subscr_nam(hlr->dbc, subscr->imsi, nam_val, is_ps);
782 if (rc)
783 return rc > 0? -rc : rc;
784
785 /* If we're disabling, send a notice out to the GSUP client that is
786 * responsible. Otherwise no need. */
787 if (nam_val)
788 return 0;
789
790 /* FIXME: only send to single SGSN where latest update for IMSI came from */
791 llist_for_each_entry(co, &hlr->gs->clients, list) {
792 luop = lu_op_alloc_conn(co);
793 if (!luop) {
794 LOGHLR(subscr->imsi, LOGL_ERROR,
795 "Cannot notify GSUP client, cannot allocate lu_operation,"
796 " for %s:%u\n",
797 co && co->conn && co->conn->server? co->conn->server->addr : "unset",
798 co && co->conn && co->conn->server? co->conn->server->port : 0);
799 continue;
800 }
801 luop->subscr = *subscr;
802 lu_op_tx_del_subscr_data(luop);
803 lu_op_free(luop);
804 }
805 return 0;
806}
Neels Hofmeyr2fb33f12018-12-26 01:49:53 +0100807
808int db_subscr_set_rat_type_flag(struct db_context *dbc, int64_t subscr_id, enum osmo_rat_type rat, bool allowed)
809{
810 int rc;
811 int ret = 0;
812 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_UPD_RAT_FLAG];
813
814 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
815 return -EIO;
816
817 OSMO_ASSERT(rat >= 0 && rat < OSMO_RAT_COUNT);
818 if (!db_bind_text(stmt, "$rat", osmo_rat_type_name(rat)))
819 return -EIO;
820
821 if (!db_bind_int(stmt, "$allowed", allowed ? 1 : 0))
822 return -EIO;
823
824 /* execute the statement */
825 rc = sqlite3_step(stmt);
826 if (rc != SQLITE_DONE) {
827 LOGP(DDB, LOGL_ERROR, "%s %s: SQL error: %s\n",
828 allowed ? "enable" : "disable", osmo_rat_type_name(rat),
829 sqlite3_errmsg(dbc->db));
830 ret = -EIO;
831 goto out;
832 }
833
834 /* verify execution result */
835 rc = sqlite3_changes(dbc->db);
836 if (!rc) {
837 LOGP(DDB, LOGL_ERROR, "Cannot %s %s: no such subscriber: ID=%" PRIu64 "\n",
838 allowed ? "enable" : "disable", osmo_rat_type_name(rat),
839 subscr_id);
840 ret = -ENOENT;
841 goto out;
842 } else if (rc != 1) {
843 LOGP(DDB, LOGL_ERROR, "%s %s: SQL modified %d rows (expected 1)\n",
844 allowed ? "enable" : "disable", osmo_rat_type_name(rat),
845 rc);
846 ret = -EIO;
847 }
848
849out:
850 db_remove_reset(stmt);
851 return ret;
852}
853
854int db_subscr_get_rat_types(struct db_context *dbc, struct hlr_subscriber *subscr)
855{
856 int rc;
857 int ret = 0;
858 int i;
859 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_RAT_BY_ID];
860
861 if (!db_bind_int64(stmt, "$subscriber_id", subscr->id))
862 return -EIO;
863
864 for (i = 0; i < OSMO_RAT_COUNT; i++)
865 subscr->rat_types[i] = true;
866
867 /* execute the statement */
868 while (1) {
869 enum osmo_rat_type rat;
870 bool allowed;
871
872 rc = sqlite3_step(stmt);
873
874 if (rc == SQLITE_DONE)
875 break;
876 if (rc != SQLITE_ROW)
877 return -rc;
878
879 rc = get_string_value(osmo_rat_type_names, (const char*)sqlite3_column_text(stmt, 0));
880 if (rc == -EINVAL) {
881 ret = -EINVAL;
882 goto out;
883 }
884 if (rc <= 0 || rc >= OSMO_RAT_COUNT) {
885 ret = -EINVAL;
886 goto out;
887 }
888 rat = rc;
889
890 allowed = sqlite3_column_int(stmt, 1);
891
892 subscr->rat_types[rat] = allowed;
893 LOGP(DAUC, LOGL_DEBUG, "db: imsi='%s' %s %s\n",
894 subscr->imsi, osmo_rat_type_name(rat), allowed ? "allowed" : "forbidden");
895 }
896
897out:
898 db_remove_reset(stmt);
899 return ret;
900}
901
902int hlr_subscr_rat_flag(struct hlr *hlr, struct hlr_subscriber *subscr, enum osmo_rat_type rat, bool allowed)
903{
904 int rc;
905 OSMO_ASSERT(rat >= 0 && rat < OSMO_RAT_COUNT);
906
907 db_subscr_get_rat_types(hlr->dbc, subscr);
908
909 if (subscr->rat_types[rat] == allowed) {
910 LOGHLR(subscr->imsi, LOGL_DEBUG, "Already has the requested value when asked to %s %s\n",
911 allowed ? "enable" : "disable", osmo_rat_type_name(rat));
912 return -ENOEXEC;
913 }
914
915 rc = db_subscr_set_rat_type_flag(hlr->dbc, subscr->id, rat, allowed);
916 if (rc)
917 return rc > 0? -rc : rc;
918
919 /* FIXME: If we're disabling, send message to VLR to detach subscriber */
920
921 return 0;
922}