blob: db310092e5140679add924a05542080ed8bec789 [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
20#include <string.h>
Max00b37152017-02-20 11:09:27 +010021#include <errno.h>
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020022#include <inttypes.h>
Stefan Sperling638ba8c2018-12-04 15:07:29 +010023#include <time.h>
Harald Weltee687be52016-05-03 18:49:27 +020024
25#include <osmocom/core/utils.h>
26#include <osmocom/crypt/auth.h>
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020027#include <osmocom/gsm/gsm23003.h>
Harald Weltee687be52016-05-03 18:49:27 +020028
29#include <sqlite3.h>
30
31#include "logging.h"
Neels Hofmeyr00b1d432017-10-17 01:43:48 +020032#include "hlr.h"
Harald Weltee687be52016-05-03 18:49:27 +020033#include "db.h"
Neels Hofmeyr00b1d432017-10-17 01:43:48 +020034#include "gsup_server.h"
35#include "luop.h"
Harald Weltee687be52016-05-03 18:49:27 +020036
Neels Hofmeyr40aa61c2017-10-09 17:56:04 +020037#define LOGHLR(imsi, level, fmt, args ...) LOGP(DAUC, level, "IMSI='%s': " fmt, imsi, ## args)
Harald Weltee687be52016-05-03 18:49:27 +020038
Neels Hofmeyr16140f72017-10-25 19:17:18 +020039/*! Add new subscriber record to the HLR database.
40 * \param[in,out] dbc database context.
41 * \param[in] imsi ASCII string of IMSI digits, is validated.
42 * \returns 0 on success, -EINVAL on invalid IMSI, -EIO on database error.
43 */
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020044int db_subscr_create(struct db_context *dbc, const char *imsi)
45{
46 sqlite3_stmt *stmt;
47 int rc;
48
49 if (!osmo_imsi_str_valid(imsi)) {
50 LOGP(DAUC, LOGL_ERROR, "Cannot create subscriber: invalid IMSI: '%s'\n",
51 imsi);
52 return -EINVAL;
53 }
54
55 stmt = dbc->stmt[DB_STMT_SUBSCR_CREATE];
56
57 if (!db_bind_text(stmt, "$imsi", imsi))
58 return -EIO;
59
60 /* execute the statement */
61 rc = sqlite3_step(stmt);
62 db_remove_reset(stmt);
63 if (rc != SQLITE_DONE) {
64 LOGHLR(imsi, LOGL_ERROR, "Cannot create subscriber: SQL error: (%d) %s\n",
65 rc, sqlite3_errmsg(dbc->db));
66 return -EIO;
67 }
68
69 return 0;
70}
71
Neels Hofmeyr16140f72017-10-25 19:17:18 +020072/*! Completely delete a subscriber record from the HLR database.
73 * Also remove authentication data.
74 * Future todo: also drop from all other database tables, which aren't used yet
75 * at the time of writing this.
76 * \param[in,out] dbc database context.
77 * \param[in] subscr_id ID of the subscriber in the HLR db.
78 * \returns if the subscriber was found and removed, -EIO on database error,
79 * -ENOENT if no such subscriber data exists.
80 */
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020081int db_subscr_delete_by_id(struct db_context *dbc, int64_t subscr_id)
82{
83 int rc;
Neels Hofmeyr1332a172017-10-10 02:25:00 +020084 struct sub_auth_data_str aud;
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020085 int ret = 0;
86
87 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_DEL_BY_ID];
88
89 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
90 return -EIO;
91
92 /* execute the statement */
93 rc = sqlite3_step(stmt);
94 if (rc != SQLITE_DONE) {
95 LOGP(DAUC, LOGL_ERROR,
Stefan Sperling705b61b2018-12-07 12:44:50 +010096 "Cannot delete subscriber ID=%" PRId64 ": SQL error: (%d) %s\n",
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020097 subscr_id, rc, sqlite3_errmsg(dbc->db));
98 db_remove_reset(stmt);
99 return -EIO;
100 }
101
102 /* verify execution result */
103 rc = sqlite3_changes(dbc->db);
104 if (!rc) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100105 LOGP(DAUC, LOGL_ERROR, "Cannot delete: no such subscriber: ID=%" PRId64 "\n",
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200106 subscr_id);
107 ret = -ENOENT;
108 } else if (rc != 1) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100109 LOGP(DAUC, LOGL_ERROR, "Delete subscriber ID=%" PRId64
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200110 ": SQL modified %d rows (expected 1)\n", subscr_id, rc);
111 ret = -EIO;
112 }
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200113 db_remove_reset(stmt);
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200114
115 /* make sure to remove authentication data for this subscriber id, for
116 * both 2G and 3G. */
117
118 aud = (struct sub_auth_data_str){
119 .type = OSMO_AUTH_TYPE_GSM,
120 .algo = OSMO_AUTH_ALG_NONE,
121 };
122 rc = db_subscr_update_aud_by_id(dbc, subscr_id, &aud);
123 if (ret == -ENOENT && !rc)
124 ret = 0;
125
126 aud = (struct sub_auth_data_str){
127 .type = OSMO_AUTH_TYPE_UMTS,
128 .algo = OSMO_AUTH_ALG_NONE,
129 };
130 rc = db_subscr_update_aud_by_id(dbc, subscr_id, &aud);
131 if (ret == -ENOENT && !rc)
132 ret = 0;
133
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200134 return ret;
135}
136
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200137/*! Set a subscriber's MSISDN in the HLR database.
138 * \param[in,out] dbc database context.
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100139 * \param[in] imsi ASCII string of IMSI digits, or NULL to remove the MSISDN.
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200140 * \param[in] msisdn ASCII string of MSISDN digits.
141 * \returns 0 on success, -EINVAL in case of invalid MSISDN string, -EIO on
142 * database failure, -ENOENT if no such subscriber exists.
143 */
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200144int db_subscr_update_msisdn_by_imsi(struct db_context *dbc, const char *imsi,
145 const char *msisdn)
146{
147 int rc;
148 int ret = 0;
149
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100150 if (msisdn && !osmo_msisdn_str_valid(msisdn)) {
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200151 LOGHLR(imsi, LOGL_ERROR,
152 "Cannot update subscriber: invalid MSISDN: '%s'\n",
153 msisdn);
154 return -EINVAL;
155 }
156
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100157 sqlite3_stmt *stmt = dbc->stmt[
158 msisdn ? DB_STMT_SET_MSISDN_BY_IMSI : DB_STMT_DELETE_MSISDN_BY_IMSI];
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200159
160 if (!db_bind_text(stmt, "$imsi", imsi))
161 return -EIO;
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100162 if (msisdn) {
163 if (!db_bind_text(stmt, "$msisdn", msisdn))
164 return -EIO;
165 }
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200166
167 /* execute the statement */
168 rc = sqlite3_step(stmt);
169 if (rc != SQLITE_DONE) {
170 LOGHLR(imsi, LOGL_ERROR,
171 "Cannot update subscriber's MSISDN: SQL error: (%d) %s\n",
172 rc, sqlite3_errmsg(dbc->db));
173 ret = -EIO;
174 goto out;
175 }
176
177 /* verify execution result */
178 rc = sqlite3_changes(dbc->db);
179 if (!rc) {
180 LOGP(DAUC, LOGL_ERROR, "Cannot update MSISDN: no such subscriber: IMSI='%s'\n",
181 imsi);
182 ret = -ENOENT;
183 goto out;
184 } else if (rc != 1) {
185 LOGHLR(imsi, LOGL_ERROR, "Update MSISDN: SQL modified %d rows (expected 1)\n", rc);
186 ret = -EIO;
187 }
188
189out:
190 db_remove_reset(stmt);
191 return ret;
192
193}
194
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200195/*! Insert or update 2G or 3G authentication tokens in the database.
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200196 * If aud->type is OSMO_AUTH_TYPE_GSM, the auc_2g table entry for the
197 * subscriber will be added or modified; if aud->algo is OSMO_AUTH_ALG_NONE,
198 * however, the auc_2g entry for the subscriber is deleted. If aud->type is
199 * OSMO_AUTH_TYPE_UMTS, the auc_3g table is updated; again, if aud->algo is
200 * OSMO_AUTH_ALG_NONE, the auc_3g entry is deleted.
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200201 * \param[in,out] dbc database context.
202 * \param[in] subscr_id DB ID of the subscriber.
203 * \param[in] aud Pointer to new auth data (in ASCII string form).
204 * \returns 0 on success, -EINVAL for invalid aud, -ENOENT for unknown
205 * subscr_id, -EIO for database errors.
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200206 */
207int db_subscr_update_aud_by_id(struct db_context *dbc, int64_t subscr_id,
208 const struct sub_auth_data_str *aud)
209{
210 sqlite3_stmt *stmt_del;
211 sqlite3_stmt *stmt_ins;
212 sqlite3_stmt *stmt;
213 const char *label;
214 int rc;
215 int ret = 0;
216
217 switch (aud->type) {
218 case OSMO_AUTH_TYPE_GSM:
219 label = "auc_2g";
220 stmt_del = dbc->stmt[DB_STMT_AUC_2G_DELETE];
221 stmt_ins = dbc->stmt[DB_STMT_AUC_2G_INSERT];
222
223 switch (aud->algo) {
224 case OSMO_AUTH_ALG_NONE:
225 case OSMO_AUTH_ALG_COMP128v1:
226 case OSMO_AUTH_ALG_COMP128v2:
227 case OSMO_AUTH_ALG_COMP128v3:
228 case OSMO_AUTH_ALG_XOR:
229 break;
230 case OSMO_AUTH_ALG_MILENAGE:
231 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
232 " auth algo not suited for 2G: %s\n",
233 osmo_auth_alg_name(aud->algo));
234 return -EINVAL;
235 default:
236 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
237 " Unknown auth algo: %d\n", aud->algo);
238 return -EINVAL;
239 }
240
241 if (aud->algo == OSMO_AUTH_ALG_NONE)
242 break;
243 if (!osmo_is_hexstr(aud->u.gsm.ki, 32, 32, true)) {
244 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
245 " Invalid KI: '%s'\n", aud->u.gsm.ki);
246 return -EINVAL;
247 }
248 break;
249
250 case OSMO_AUTH_TYPE_UMTS:
251 label = "auc_3g";
252 stmt_del = dbc->stmt[DB_STMT_AUC_3G_DELETE];
253 stmt_ins = dbc->stmt[DB_STMT_AUC_3G_INSERT];
254 switch (aud->algo) {
255 case OSMO_AUTH_ALG_NONE:
256 case OSMO_AUTH_ALG_MILENAGE:
257 break;
258 case OSMO_AUTH_ALG_COMP128v1:
259 case OSMO_AUTH_ALG_COMP128v2:
260 case OSMO_AUTH_ALG_COMP128v3:
261 case OSMO_AUTH_ALG_XOR:
262 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
263 " auth algo not suited for 3G: %s\n",
264 osmo_auth_alg_name(aud->algo));
265 return -EINVAL;
266 default:
267 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
268 " Unknown auth algo: %d\n", aud->algo);
269 return -EINVAL;
270 }
271
272 if (aud->algo == OSMO_AUTH_ALG_NONE)
273 break;
274 if (!osmo_is_hexstr(aud->u.umts.k, 32, 32, true)) {
275 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
276 " Invalid K: '%s'\n", aud->u.umts.k);
277 return -EINVAL;
278 }
279 if (!osmo_is_hexstr(aud->u.umts.opc, 32, 32, true)) {
280 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
281 " Invalid OP/OPC: '%s'\n", aud->u.umts.opc);
282 return -EINVAL;
283 }
284 if (aud->u.umts.ind_bitlen > OSMO_MILENAGE_IND_BITLEN_MAX) {
285 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
286 " Invalid ind_bitlen: %d\n", aud->u.umts.ind_bitlen);
287 return -EINVAL;
288 }
289 break;
290 default:
291 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
292 " unknown auth type: %d\n", aud->type);
293 return -EINVAL;
294 }
295
296 stmt = stmt_del;
297
298 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
299 return -EIO;
300
301 /* execute the statement */
302 rc = sqlite3_step(stmt);
303 if (rc != SQLITE_DONE) {
304 LOGP(DAUC, LOGL_ERROR,
305 "Cannot delete %s row: SQL error: (%d) %s\n",
306 label, rc, sqlite3_errmsg(dbc->db));
307 ret = -EIO;
308 goto out;
309 }
310
311 /* verify execution result */
312 rc = sqlite3_changes(dbc->db);
313 if (!rc)
314 /* Leave "no such entry" logging to the caller -- during
315 * db_subscr_delete_by_id(), we call this to make sure it is
316 * empty, and no entry is not an error then.*/
317 ret = -ENOENT;
318 else if (rc != 1) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100319 LOGP(DAUC, LOGL_ERROR, "Delete subscriber ID=%" PRId64
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200320 " from %s: SQL modified %d rows (expected 1)\n",
321 subscr_id, label, rc);
322 ret = -EIO;
323 }
324
325 db_remove_reset(stmt);
326
327 /* Error situation? Return now. */
328 if (ret && ret != -ENOENT)
329 return ret;
330
331 /* Just delete requested? */
332 if (aud->algo == OSMO_AUTH_ALG_NONE)
333 return ret;
334
335 /* Don't return -ENOENT if inserting new data. */
336 ret = 0;
337
338 /* Insert new row */
339 stmt = stmt_ins;
340
341 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
342 return -EIO;
343
344 switch (aud->type) {
345 case OSMO_AUTH_TYPE_GSM:
346 if (!db_bind_int(stmt, "$algo_id_2g", aud->algo))
347 return -EIO;
348 if (!db_bind_text(stmt, "$ki", aud->u.gsm.ki))
349 return -EIO;
350 break;
351 case OSMO_AUTH_TYPE_UMTS:
352 if (!db_bind_int(stmt, "$algo_id_3g", aud->algo))
353 return -EIO;
354 if (!db_bind_text(stmt, "$k", aud->u.umts.k))
355 return -EIO;
356 if (!db_bind_text(stmt, "$op",
357 aud->u.umts.opc_is_op ? aud->u.umts.opc : NULL))
358 return -EIO;
359 if (!db_bind_text(stmt, "$opc",
360 aud->u.umts.opc_is_op ? NULL : aud->u.umts.opc))
361 return -EIO;
362 if (!db_bind_int(stmt, "$ind_bitlen", aud->u.umts.ind_bitlen))
363 return -EIO;
364 break;
365 default:
366 OSMO_ASSERT(false);
367 }
368
369 /* execute the statement */
370 rc = sqlite3_step(stmt);
371 if (rc != SQLITE_DONE) {
372 LOGP(DAUC, LOGL_ERROR,
373 "Cannot insert %s row: SQL error: (%d) %s\n",
374 label, rc, sqlite3_errmsg(dbc->db));
375 ret = -EIO;
376 goto out;
377 }
378
379out:
380 db_remove_reset(stmt);
381 return ret;
382}
383
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200384/* Common code for db_subscr_get_by_*() functions. */
385static int db_sel(struct db_context *dbc, sqlite3_stmt *stmt, struct hlr_subscriber *subscr,
386 const char **err)
Harald Weltee687be52016-05-03 18:49:27 +0200387{
Maxadc66482017-02-20 11:23:20 +0100388 int rc;
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200389 int ret = 0;
Harald Weltee687be52016-05-03 18:49:27 +0200390
391 /* execute the statement */
392 rc = sqlite3_step(stmt);
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200393 if (rc == SQLITE_DONE) {
394 ret = -ENOENT;
395 goto out;
396 }
Harald Weltee687be52016-05-03 18:49:27 +0200397 if (rc != SQLITE_ROW) {
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200398 ret = -EIO;
399 goto out;
Maxadc66482017-02-20 11:23:20 +0100400 }
401
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200402 if (!subscr)
403 goto out;
Harald Weltee687be52016-05-03 18:49:27 +0200404
Neels Hofmeyrb6837e32017-10-10 23:20:26 +0200405 *subscr = (struct hlr_subscriber){};
406
Harald Weltee687be52016-05-03 18:49:27 +0200407 /* obtain the various columns */
408 subscr->id = sqlite3_column_int64(stmt, 0);
Neels Hofmeyrdbced932017-10-27 02:57:51 +0200409 copy_sqlite3_text_to_buf(subscr->imsi, stmt, 1);
410 copy_sqlite3_text_to_buf(subscr->msisdn, stmt, 2);
Harald Welte99909272016-05-05 18:24:15 +0200411 /* FIXME: These should all be BLOBs as they might contain NUL */
Neels Hofmeyrdbced932017-10-27 02:57:51 +0200412 copy_sqlite3_text_to_buf(subscr->vlr_number, stmt, 3);
413 copy_sqlite3_text_to_buf(subscr->sgsn_number, stmt, 4);
414 copy_sqlite3_text_to_buf(subscr->sgsn_address, stmt, 5);
Harald Weltee687be52016-05-03 18:49:27 +0200415 subscr->periodic_lu_timer = sqlite3_column_int(stmt, 6);
416 subscr->periodic_rau_tau_timer = sqlite3_column_int(stmt, 7);
417 subscr->nam_cs = sqlite3_column_int(stmt, 8);
418 subscr->nam_ps = sqlite3_column_int(stmt, 9);
419 subscr->lmsi = sqlite3_column_int(stmt, 10);
420 subscr->ms_purged_cs = sqlite3_column_int(stmt, 11);
421 subscr->ms_purged_ps = sqlite3_column_int(stmt, 12);
422
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200423out:
Max00b37152017-02-20 11:09:27 +0100424 db_remove_reset(stmt);
Harald Weltee687be52016-05-03 18:49:27 +0200425
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200426 switch (ret) {
427 case 0:
428 *err = NULL;
429 break;
430 case -ENOENT:
431 *err = "No such subscriber";
432 break;
433 default:
434 *err = sqlite3_errmsg(dbc->db);
435 break;
436 }
437 return ret;
438}
439
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200440/*! Retrieve subscriber data from the HLR database.
441 * \param[in,out] dbc database context.
442 * \param[in] imsi ASCII string of IMSI digits.
443 * \param[out] subscr place retrieved data in this struct.
444 * \returns 0 on success, -ENOENT if no such subscriber was found, -EIO on
445 * database error.
446 */
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200447int db_subscr_get_by_imsi(struct db_context *dbc, const char *imsi,
448 struct hlr_subscriber *subscr)
449{
450 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_IMSI];
451 const char *err;
452 int rc;
453
454 if (!db_bind_text(stmt, NULL, imsi))
455 return -EIO;
456
457 rc = db_sel(dbc, stmt, subscr, &err);
458 if (rc)
459 LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: IMSI='%s': %s\n",
460 imsi, err);
461 return rc;
462}
463
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200464/*! Retrieve subscriber data from the HLR database.
465 * \param[in,out] dbc database context.
466 * \param[in] msisdn ASCII string of MSISDN digits.
467 * \param[out] subscr place retrieved data in this struct.
468 * \returns 0 on success, -ENOENT if no such subscriber was found, -EIO on
469 * database error.
470 */
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200471int db_subscr_get_by_msisdn(struct db_context *dbc, const char *msisdn,
472 struct hlr_subscriber *subscr)
473{
474 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_MSISDN];
475 const char *err;
476 int rc;
477
478 if (!db_bind_text(stmt, NULL, msisdn))
479 return -EIO;
480
481 rc = db_sel(dbc, stmt, subscr, &err);
482 if (rc)
483 LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: MSISDN='%s': %s\n",
484 msisdn, err);
485 return rc;
486}
487
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200488/*! Retrieve subscriber data from the HLR database.
489 * \param[in,out] dbc database context.
490 * \param[in] id ID of the subscriber in the HLR db.
491 * \param[out] subscr place retrieved data in this struct.
492 * \returns 0 on success, -ENOENT if no such subscriber was found, -EIO on
493 * database error.
494 */
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200495int db_subscr_get_by_id(struct db_context *dbc, int64_t id,
496 struct hlr_subscriber *subscr)
497{
498 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_ID];
499 const char *err;
500 int rc;
501
502 if (!db_bind_int64(stmt, NULL, id))
503 return -EIO;
504
505 rc = db_sel(dbc, stmt, subscr, &err);
506 if (rc)
Stefan Sperling705b61b2018-12-07 12:44:50 +0100507 LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: ID=%" PRId64 ": %s\n",
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200508 id, err);
509 return rc;
Harald Weltee687be52016-05-03 18:49:27 +0200510}
511
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200512/*! You should use hlr_subscr_nam() instead; enable or disable PS or CS for a
513 * subscriber without notifying GSUP clients.
514 * \param[in,out] dbc database context.
515 * \param[in] imsi ASCII string of IMSI digits.
516 * \param[in] nam_val True to enable CS/PS, false to disable.
517 * \param[in] is_ps when true, set nam_ps, else set nam_cs.
518 * \returns 0 on success, -ENOENT when the given IMSI does not exist, -EIO on
519 * database errors.
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200520 */
521int db_subscr_nam(struct db_context *dbc, const char *imsi, bool nam_val, bool is_ps)
Max3ce36862017-02-20 11:18:04 +0100522{
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200523 sqlite3_stmt *stmt;
Max3ce36862017-02-20 11:18:04 +0100524 int rc;
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200525 int ret = 0;
Max3ce36862017-02-20 11:18:04 +0100526
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200527 stmt = dbc->stmt[is_ps ? DB_STMT_UPD_NAM_PS_BY_IMSI
528 : DB_STMT_UPD_NAM_CS_BY_IMSI];
Max3ce36862017-02-20 11:18:04 +0100529
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200530 if (!db_bind_text(stmt, "$imsi", imsi))
531 return -EIO;
532 if (!db_bind_int(stmt, "$val", nam_val ? 1 : 0))
533 return -EIO;
534
535 /* execute the statement */
536 rc = sqlite3_step(stmt);
Max3ce36862017-02-20 11:18:04 +0100537 if (rc != SQLITE_DONE) {
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200538 LOGHLR(imsi, LOGL_ERROR, "%s %s: SQL error: %s\n",
539 nam_val ? "enable" : "disable",
540 is_ps ? "PS" : "CS",
541 sqlite3_errmsg(dbc->db));
542 ret = -EIO;
543 goto out;
Max3ce36862017-02-20 11:18:04 +0100544 }
545
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200546 /* verify execution result */
547 rc = sqlite3_changes(dbc->db);
548 if (!rc) {
549 LOGP(DAUC, LOGL_ERROR, "Cannot %s %s: no such subscriber: IMSI='%s'\n",
550 nam_val ? "enable" : "disable",
551 is_ps ? "PS" : "CS",
552 imsi);
553 ret = -ENOENT;
554 goto out;
555 } else if (rc != 1) {
556 LOGHLR(imsi, LOGL_ERROR, "%s %s: SQL modified %d rows (expected 1)\n",
557 nam_val ? "enable" : "disable",
558 is_ps ? "PS" : "CS",
Max3ce36862017-02-20 11:18:04 +0100559 rc);
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200560 ret = -EIO;
Max3ce36862017-02-20 11:18:04 +0100561 }
562
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200563out:
Max3ce36862017-02-20 11:18:04 +0100564 db_remove_reset(stmt);
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200565 return ret;
Max3ce36862017-02-20 11:18:04 +0100566}
567
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200568/*! Record a Location Updating in the database.
569 * \param[in,out] dbc database context.
570 * \param[in] subscr_id ID of the subscriber in the HLR db.
571 * \param[in] vlr_or_sgsn_number ASCII string of identifier digits.
572 * \param[in] is_ps when true, set sgsn_number, else set vlr_number.
573 * \returns 0 on success, -ENOENT when the given subscriber does not exist,
574 * -EIO on database errors.
575 */
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200576int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
577 const char *vlr_or_sgsn_number, bool is_ps)
Harald Weltee687be52016-05-03 18:49:27 +0200578{
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200579 sqlite3_stmt *stmt;
Harald Weltee687be52016-05-03 18:49:27 +0200580 int rc, ret = 0;
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100581 struct timespec localtime;
Harald Weltee687be52016-05-03 18:49:27 +0200582
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200583 stmt = dbc->stmt[is_ps ? DB_STMT_UPD_SGSN_BY_ID
584 : DB_STMT_UPD_VLR_BY_ID];
Harald Weltee687be52016-05-03 18:49:27 +0200585
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200586 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
587 return -EIO;
Harald Weltee687be52016-05-03 18:49:27 +0200588
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200589 if (!db_bind_text(stmt, "$number", vlr_or_sgsn_number))
590 return -EIO;
Harald Weltee687be52016-05-03 18:49:27 +0200591
592 /* execute the statement */
593 rc = sqlite3_step(stmt);
594 if (rc != SQLITE_DONE) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100595 LOGP(DAUC, LOGL_ERROR, "Update %s number for subscriber ID=%" PRId64 ": SQL Error: %s\n",
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200596 is_ps? "SGSN" : "VLR", subscr_id, sqlite3_errmsg(dbc->db));
597 ret = -EIO;
598 goto out;
Harald Weltee687be52016-05-03 18:49:27 +0200599 }
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200600
601 /* verify execution result */
602 rc = sqlite3_changes(dbc->db);
603 if (!rc) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100604 LOGP(DAUC, LOGL_ERROR, "Cannot update %s number for subscriber ID=%" PRId64
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200605 ": no such subscriber\n",
606 is_ps? "SGSN" : "VLR", subscr_id);
607 ret = -ENOENT;
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100608 goto out;
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200609 } else if (rc != 1) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100610 LOGP(DAUC, LOGL_ERROR, "Update %s number for subscriber ID=%" PRId64
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200611 ": SQL modified %d rows (expected 1)\n",
612 is_ps? "SGSN" : "VLR", subscr_id, rc);
613 ret = -EIO;
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100614 goto out;
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200615 }
616
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100617 db_remove_reset(stmt);
618
619 if (osmo_clock_gettime(CLOCK_REALTIME, &localtime) != 0) {
620 LOGP(DAUC, LOGL_ERROR, "Cannot get the current time: (%d) %s\n", errno, strerror(errno));
621 ret = -errno;
622 goto out;
623 }
624
625 stmt = dbc->stmt[DB_STMT_SET_LAST_LU_SEEN];
626
627 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
628 return -EIO;
629 /* The timestamp will be converted to UTC by SQLite. */
630 if (!db_bind_int64(stmt, "$val", (int64_t)localtime.tv_sec)) {
631 ret = -EIO;
632 goto out;
633 }
634
635 rc = sqlite3_step(stmt);
636 if (rc != SQLITE_DONE) {
637 LOGP(DAUC, LOGL_ERROR,
Stefan Sperling705b61b2018-12-07 12:44:50 +0100638 "Cannot update LU timestamp for subscriber ID=%" PRId64 ": SQL error: (%d) %s\n",
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100639 subscr_id, rc, sqlite3_errmsg(dbc->db));
640 ret = -EIO;
641 goto out;
642 }
643
644 /* verify execution result */
645 rc = sqlite3_changes(dbc->db);
646 if (!rc) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100647 LOGP(DAUC, LOGL_ERROR, "Cannot update LU timestamp for subscriber ID=%" PRId64
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100648 ": no such subscriber\n", subscr_id);
649 ret = -ENOENT;
650 goto out;
651 } else if (rc != 1) {
Stefan Sperling705b61b2018-12-07 12:44:50 +0100652 LOGP(DAUC, LOGL_ERROR, "Update LU timestamp for subscriber ID=%" PRId64
Stefan Sperling638ba8c2018-12-04 15:07:29 +0100653 ": SQL modified %d rows (expected 1)\n", subscr_id, rc);
654 ret = -EIO;
655 }
Harald Weltee687be52016-05-03 18:49:27 +0200656out:
Max00b37152017-02-20 11:09:27 +0100657 db_remove_reset(stmt);
Harald Weltee687be52016-05-03 18:49:27 +0200658 return ret;
659}
Harald Welteb18f0e02016-05-05 21:03:03 +0200660
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200661/*! Set the ms_purged_cs or ms_purged_ps values in the database.
662 * \param[in,out] dbc database context.
663 * \param[in] by_imsi ASCII string of IMSI digits.
664 * \param[in] purge_val true to purge, false to un-purge.
665 * \param[in] is_ps when true, set ms_purged_ps, else set ms_purged_cs.
666 * \returns 0 on success, -ENOENT when the given IMSI does not exist, -EIO on
667 * database errors.
668 */
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200669int db_subscr_purge(struct db_context *dbc, const char *by_imsi,
670 bool purge_val, bool is_ps)
Harald Welteb18f0e02016-05-05 21:03:03 +0200671{
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200672 sqlite3_stmt *stmt;
673 int rc, ret = 0;
Harald Welteb18f0e02016-05-05 21:03:03 +0200674
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200675 stmt = dbc->stmt[is_ps ? DB_STMT_UPD_PURGE_PS_BY_IMSI
676 : DB_STMT_UPD_PURGE_CS_BY_IMSI];
Harald Welteb18f0e02016-05-05 21:03:03 +0200677
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200678 if (!db_bind_text(stmt, "$imsi", by_imsi))
679 return -EIO;
680 if (!db_bind_int(stmt, "$val", purge_val ? 1 : 0))
681 return -EIO;
Harald Welteb18f0e02016-05-05 21:03:03 +0200682
683 /* execute the statement */
684 rc = sqlite3_step(stmt);
685 if (rc != SQLITE_DONE) {
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200686 LOGP(DAUC, LOGL_ERROR, "%s %s: SQL error: %s\n",
687 purge_val ? "purge" : "un-purge",
688 is_ps ? "PS" : "CS",
689 sqlite3_errmsg(dbc->db));
690 ret = -EIO;
691 goto out;
Harald Welteb18f0e02016-05-05 21:03:03 +0200692 }
Max00b37152017-02-20 11:09:27 +0100693
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200694 /* verify execution result */
695 rc = sqlite3_changes(dbc->db);
696 if (!rc) {
697 LOGP(DAUC, LOGL_ERROR, "Cannot %s %s: no such subscriber: IMSI='%s'\n",
698 purge_val ? "purge" : "un-purge",
699 is_ps ? "PS" : "CS",
700 by_imsi);
701 ret = -ENOENT;
702 goto out;
703 } else if (rc != 1) {
704 LOGHLR(by_imsi, LOGL_ERROR, "%s %s: SQL modified %d rows (expected 1)\n",
705 purge_val ? "purge" : "un-purge",
706 is_ps ? "PS" : "CS",
707 rc);
708 ret = -EIO;
709 }
710
711out:
Max00b37152017-02-20 11:09:27 +0100712 db_remove_reset(stmt);
Harald Welteb18f0e02016-05-05 21:03:03 +0200713
714 return ret;
715}
Neels Hofmeyr00b1d432017-10-17 01:43:48 +0200716
717/*! Update nam_cs/nam_ps in the db and trigger notifications to GSUP clients.
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200718 * \param[in,out] hlr Global hlr context.
719 * \param[in] subscr Subscriber from a fresh db_subscr_get_by_*() call.
720 * \param[in] nam_val True to enable CS/PS, false to disable.
721 * \param[in] is_ps True to enable/disable PS, false for CS.
Neels Hofmeyr00b1d432017-10-17 01:43:48 +0200722 * \returns 0 on success, ENOEXEC if there is no need to change, a negative
723 * value on error.
724 */
725int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps)
726{
727 int rc;
728 struct lu_operation *luop;
729 struct osmo_gsup_conn *co;
730 bool is_val = is_ps? subscr->nam_ps : subscr->nam_cs;
731
732 if (is_val == nam_val) {
733 LOGHLR(subscr->imsi, LOGL_DEBUG, "Already has the requested value when asked to %s %s\n",
734 nam_val ? "enable" : "disable", is_ps ? "PS" : "CS");
735 return ENOEXEC;
736 }
737
738 rc = db_subscr_nam(hlr->dbc, subscr->imsi, nam_val, is_ps);
739 if (rc)
740 return rc > 0? -rc : rc;
741
742 /* If we're disabling, send a notice out to the GSUP client that is
743 * responsible. Otherwise no need. */
744 if (nam_val)
745 return 0;
746
747 /* FIXME: only send to single SGSN where latest update for IMSI came from */
748 llist_for_each_entry(co, &hlr->gs->clients, list) {
749 luop = lu_op_alloc_conn(co);
750 if (!luop) {
751 LOGHLR(subscr->imsi, LOGL_ERROR,
752 "Cannot notify GSUP client, cannot allocate lu_operation,"
753 " for %s:%u\n",
754 co && co->conn && co->conn->server? co->conn->server->addr : "unset",
755 co && co->conn && co->conn->server? co->conn->server->port : 0);
756 continue;
757 }
758 luop->subscr = *subscr;
759 lu_op_tx_del_subscr_data(luop);
760 lu_op_free(luop);
761 }
762 return 0;
763}