blob: bae9a5ad18674f09abb49b2de9722badd52079cc [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>
Harald Weltee687be52016-05-03 18:49:27 +020023
24#include <osmocom/core/utils.h>
25#include <osmocom/crypt/auth.h>
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020026#include <osmocom/gsm/gsm23003.h>
Harald Weltee687be52016-05-03 18:49:27 +020027
28#include <sqlite3.h>
29
30#include "logging.h"
Neels Hofmeyr00b1d432017-10-17 01:43:48 +020031#include "hlr.h"
Harald Weltee687be52016-05-03 18:49:27 +020032#include "db.h"
Neels Hofmeyr00b1d432017-10-17 01:43:48 +020033#include "gsup_server.h"
34#include "luop.h"
Harald Weltee687be52016-05-03 18:49:27 +020035
Neels Hofmeyr40aa61c2017-10-09 17:56:04 +020036#define LOGHLR(imsi, level, fmt, args ...) LOGP(DAUC, level, "IMSI='%s': " fmt, imsi, ## args)
Harald Weltee687be52016-05-03 18:49:27 +020037
Neels Hofmeyrdbced932017-10-27 02:57:51 +020038/*! Call sqlite3_column_text() and copy result to a char[].
39 * \param[out] buf A char[] used as sizeof() arg(!) and osmo_strlcpy() target.
40 * \param[in] stmt An sqlite3_stmt*.
41 * \param[in] idx Index in stmt's returned columns.
42 */
43#define copy_sqlite3_text_to_buf(buf, stmt, idx) \
44 do { \
45 const char *_txt = (const char *) sqlite3_column_text(stmt, idx); \
46 osmo_strlcpy(buf, _txt, sizeof(buf)); \
Neels Hofmeyr1e31d182017-10-10 23:20:09 +020047 } while (0)
Harald Weltee687be52016-05-03 18:49:27 +020048
Neels Hofmeyr16140f72017-10-25 19:17:18 +020049/*! Add new subscriber record to the HLR database.
50 * \param[in,out] dbc database context.
51 * \param[in] imsi ASCII string of IMSI digits, is validated.
52 * \returns 0 on success, -EINVAL on invalid IMSI, -EIO on database error.
53 */
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020054int db_subscr_create(struct db_context *dbc, const char *imsi)
55{
56 sqlite3_stmt *stmt;
57 int rc;
58
59 if (!osmo_imsi_str_valid(imsi)) {
60 LOGP(DAUC, LOGL_ERROR, "Cannot create subscriber: invalid IMSI: '%s'\n",
61 imsi);
62 return -EINVAL;
63 }
64
65 stmt = dbc->stmt[DB_STMT_SUBSCR_CREATE];
66
67 if (!db_bind_text(stmt, "$imsi", imsi))
68 return -EIO;
69
70 /* execute the statement */
71 rc = sqlite3_step(stmt);
72 db_remove_reset(stmt);
73 if (rc != SQLITE_DONE) {
74 LOGHLR(imsi, LOGL_ERROR, "Cannot create subscriber: SQL error: (%d) %s\n",
75 rc, sqlite3_errmsg(dbc->db));
76 return -EIO;
77 }
78
79 return 0;
80}
81
Neels Hofmeyr16140f72017-10-25 19:17:18 +020082/*! Completely delete a subscriber record from the HLR database.
83 * Also remove authentication data.
84 * Future todo: also drop from all other database tables, which aren't used yet
85 * at the time of writing this.
86 * \param[in,out] dbc database context.
87 * \param[in] subscr_id ID of the subscriber in the HLR db.
88 * \returns if the subscriber was found and removed, -EIO on database error,
89 * -ENOENT if no such subscriber data exists.
90 */
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020091int db_subscr_delete_by_id(struct db_context *dbc, int64_t subscr_id)
92{
93 int rc;
Neels Hofmeyr1332a172017-10-10 02:25:00 +020094 struct sub_auth_data_str aud;
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020095 int ret = 0;
96
97 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_DEL_BY_ID];
98
99 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
100 return -EIO;
101
102 /* execute the statement */
103 rc = sqlite3_step(stmt);
104 if (rc != SQLITE_DONE) {
105 LOGP(DAUC, LOGL_ERROR,
106 "Cannot delete subscriber ID=%"PRId64": SQL error: (%d) %s\n",
107 subscr_id, rc, sqlite3_errmsg(dbc->db));
108 db_remove_reset(stmt);
109 return -EIO;
110 }
111
112 /* verify execution result */
113 rc = sqlite3_changes(dbc->db);
114 if (!rc) {
115 LOGP(DAUC, LOGL_ERROR, "Cannot delete: no such subscriber: ID=%"PRId64"\n",
116 subscr_id);
117 ret = -ENOENT;
118 } else if (rc != 1) {
119 LOGP(DAUC, LOGL_ERROR, "Delete subscriber ID=%"PRId64
120 ": SQL modified %d rows (expected 1)\n", subscr_id, rc);
121 ret = -EIO;
122 }
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200123 db_remove_reset(stmt);
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200124
125 /* make sure to remove authentication data for this subscriber id, for
126 * both 2G and 3G. */
127
128 aud = (struct sub_auth_data_str){
129 .type = OSMO_AUTH_TYPE_GSM,
130 .algo = OSMO_AUTH_ALG_NONE,
131 };
132 rc = db_subscr_update_aud_by_id(dbc, subscr_id, &aud);
133 if (ret == -ENOENT && !rc)
134 ret = 0;
135
136 aud = (struct sub_auth_data_str){
137 .type = OSMO_AUTH_TYPE_UMTS,
138 .algo = OSMO_AUTH_ALG_NONE,
139 };
140 rc = db_subscr_update_aud_by_id(dbc, subscr_id, &aud);
141 if (ret == -ENOENT && !rc)
142 ret = 0;
143
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200144 return ret;
145}
146
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200147/*! Set a subscriber's MSISDN in the HLR database.
148 * \param[in,out] dbc database context.
149 * \param[in] imsi ASCII string of IMSI digits.
150 * \param[in] msisdn ASCII string of MSISDN digits.
151 * \returns 0 on success, -EINVAL in case of invalid MSISDN string, -EIO on
152 * database failure, -ENOENT if no such subscriber exists.
153 */
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200154int db_subscr_update_msisdn_by_imsi(struct db_context *dbc, const char *imsi,
155 const char *msisdn)
156{
157 int rc;
158 int ret = 0;
159
160 if (!osmo_msisdn_str_valid(msisdn)) {
161 LOGHLR(imsi, LOGL_ERROR,
162 "Cannot update subscriber: invalid MSISDN: '%s'\n",
163 msisdn);
164 return -EINVAL;
165 }
166
167 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SET_MSISDN_BY_IMSI];
168
169 if (!db_bind_text(stmt, "$imsi", imsi))
170 return -EIO;
171 if (!db_bind_text(stmt, "$msisdn", msisdn))
172 return -EIO;
173
174 /* execute the statement */
175 rc = sqlite3_step(stmt);
176 if (rc != SQLITE_DONE) {
177 LOGHLR(imsi, LOGL_ERROR,
178 "Cannot update subscriber's MSISDN: SQL error: (%d) %s\n",
179 rc, sqlite3_errmsg(dbc->db));
180 ret = -EIO;
181 goto out;
182 }
183
184 /* verify execution result */
185 rc = sqlite3_changes(dbc->db);
186 if (!rc) {
187 LOGP(DAUC, LOGL_ERROR, "Cannot update MSISDN: no such subscriber: IMSI='%s'\n",
188 imsi);
189 ret = -ENOENT;
190 goto out;
191 } else if (rc != 1) {
192 LOGHLR(imsi, LOGL_ERROR, "Update MSISDN: SQL modified %d rows (expected 1)\n", rc);
193 ret = -EIO;
194 }
195
196out:
197 db_remove_reset(stmt);
198 return ret;
199
200}
201
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200202/*! Insert or update 2G or 3G authentication tokens in the database.
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200203 * If aud->type is OSMO_AUTH_TYPE_GSM, the auc_2g table entry for the
204 * subscriber will be added or modified; if aud->algo is OSMO_AUTH_ALG_NONE,
205 * however, the auc_2g entry for the subscriber is deleted. If aud->type is
206 * OSMO_AUTH_TYPE_UMTS, the auc_3g table is updated; again, if aud->algo is
207 * OSMO_AUTH_ALG_NONE, the auc_3g entry is deleted.
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200208 * \param[in,out] dbc database context.
209 * \param[in] subscr_id DB ID of the subscriber.
210 * \param[in] aud Pointer to new auth data (in ASCII string form).
211 * \returns 0 on success, -EINVAL for invalid aud, -ENOENT for unknown
212 * subscr_id, -EIO for database errors.
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200213 */
214int db_subscr_update_aud_by_id(struct db_context *dbc, int64_t subscr_id,
215 const struct sub_auth_data_str *aud)
216{
217 sqlite3_stmt *stmt_del;
218 sqlite3_stmt *stmt_ins;
219 sqlite3_stmt *stmt;
220 const char *label;
221 int rc;
222 int ret = 0;
223
224 switch (aud->type) {
225 case OSMO_AUTH_TYPE_GSM:
226 label = "auc_2g";
227 stmt_del = dbc->stmt[DB_STMT_AUC_2G_DELETE];
228 stmt_ins = dbc->stmt[DB_STMT_AUC_2G_INSERT];
229
230 switch (aud->algo) {
231 case OSMO_AUTH_ALG_NONE:
232 case OSMO_AUTH_ALG_COMP128v1:
233 case OSMO_AUTH_ALG_COMP128v2:
234 case OSMO_AUTH_ALG_COMP128v3:
235 case OSMO_AUTH_ALG_XOR:
236 break;
237 case OSMO_AUTH_ALG_MILENAGE:
238 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
239 " auth algo not suited for 2G: %s\n",
240 osmo_auth_alg_name(aud->algo));
241 return -EINVAL;
242 default:
243 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
244 " Unknown auth algo: %d\n", aud->algo);
245 return -EINVAL;
246 }
247
248 if (aud->algo == OSMO_AUTH_ALG_NONE)
249 break;
250 if (!osmo_is_hexstr(aud->u.gsm.ki, 32, 32, true)) {
251 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
252 " Invalid KI: '%s'\n", aud->u.gsm.ki);
253 return -EINVAL;
254 }
255 break;
256
257 case OSMO_AUTH_TYPE_UMTS:
258 label = "auc_3g";
259 stmt_del = dbc->stmt[DB_STMT_AUC_3G_DELETE];
260 stmt_ins = dbc->stmt[DB_STMT_AUC_3G_INSERT];
261 switch (aud->algo) {
262 case OSMO_AUTH_ALG_NONE:
263 case OSMO_AUTH_ALG_MILENAGE:
264 break;
265 case OSMO_AUTH_ALG_COMP128v1:
266 case OSMO_AUTH_ALG_COMP128v2:
267 case OSMO_AUTH_ALG_COMP128v3:
268 case OSMO_AUTH_ALG_XOR:
269 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
270 " auth algo not suited for 3G: %s\n",
271 osmo_auth_alg_name(aud->algo));
272 return -EINVAL;
273 default:
274 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
275 " Unknown auth algo: %d\n", aud->algo);
276 return -EINVAL;
277 }
278
279 if (aud->algo == OSMO_AUTH_ALG_NONE)
280 break;
281 if (!osmo_is_hexstr(aud->u.umts.k, 32, 32, true)) {
282 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
283 " Invalid K: '%s'\n", aud->u.umts.k);
284 return -EINVAL;
285 }
286 if (!osmo_is_hexstr(aud->u.umts.opc, 32, 32, true)) {
287 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
288 " Invalid OP/OPC: '%s'\n", aud->u.umts.opc);
289 return -EINVAL;
290 }
291 if (aud->u.umts.ind_bitlen > OSMO_MILENAGE_IND_BITLEN_MAX) {
292 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
293 " Invalid ind_bitlen: %d\n", aud->u.umts.ind_bitlen);
294 return -EINVAL;
295 }
296 break;
297 default:
298 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
299 " unknown auth type: %d\n", aud->type);
300 return -EINVAL;
301 }
302
303 stmt = stmt_del;
304
305 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
306 return -EIO;
307
308 /* execute the statement */
309 rc = sqlite3_step(stmt);
310 if (rc != SQLITE_DONE) {
311 LOGP(DAUC, LOGL_ERROR,
312 "Cannot delete %s row: SQL error: (%d) %s\n",
313 label, rc, sqlite3_errmsg(dbc->db));
314 ret = -EIO;
315 goto out;
316 }
317
318 /* verify execution result */
319 rc = sqlite3_changes(dbc->db);
320 if (!rc)
321 /* Leave "no such entry" logging to the caller -- during
322 * db_subscr_delete_by_id(), we call this to make sure it is
323 * empty, and no entry is not an error then.*/
324 ret = -ENOENT;
325 else if (rc != 1) {
326 LOGP(DAUC, LOGL_ERROR, "Delete subscriber ID=%"PRId64
327 " from %s: SQL modified %d rows (expected 1)\n",
328 subscr_id, label, rc);
329 ret = -EIO;
330 }
331
332 db_remove_reset(stmt);
333
334 /* Error situation? Return now. */
335 if (ret && ret != -ENOENT)
336 return ret;
337
338 /* Just delete requested? */
339 if (aud->algo == OSMO_AUTH_ALG_NONE)
340 return ret;
341
342 /* Don't return -ENOENT if inserting new data. */
343 ret = 0;
344
345 /* Insert new row */
346 stmt = stmt_ins;
347
348 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
349 return -EIO;
350
351 switch (aud->type) {
352 case OSMO_AUTH_TYPE_GSM:
353 if (!db_bind_int(stmt, "$algo_id_2g", aud->algo))
354 return -EIO;
355 if (!db_bind_text(stmt, "$ki", aud->u.gsm.ki))
356 return -EIO;
357 break;
358 case OSMO_AUTH_TYPE_UMTS:
359 if (!db_bind_int(stmt, "$algo_id_3g", aud->algo))
360 return -EIO;
361 if (!db_bind_text(stmt, "$k", aud->u.umts.k))
362 return -EIO;
363 if (!db_bind_text(stmt, "$op",
364 aud->u.umts.opc_is_op ? aud->u.umts.opc : NULL))
365 return -EIO;
366 if (!db_bind_text(stmt, "$opc",
367 aud->u.umts.opc_is_op ? NULL : aud->u.umts.opc))
368 return -EIO;
369 if (!db_bind_int(stmt, "$ind_bitlen", aud->u.umts.ind_bitlen))
370 return -EIO;
371 break;
372 default:
373 OSMO_ASSERT(false);
374 }
375
376 /* execute the statement */
377 rc = sqlite3_step(stmt);
378 if (rc != SQLITE_DONE) {
379 LOGP(DAUC, LOGL_ERROR,
380 "Cannot insert %s row: SQL error: (%d) %s\n",
381 label, rc, sqlite3_errmsg(dbc->db));
382 ret = -EIO;
383 goto out;
384 }
385
386out:
387 db_remove_reset(stmt);
388 return ret;
389}
390
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200391/* Common code for db_subscr_get_by_*() functions. */
392static int db_sel(struct db_context *dbc, sqlite3_stmt *stmt, struct hlr_subscriber *subscr,
393 const char **err)
Harald Weltee687be52016-05-03 18:49:27 +0200394{
Maxadc66482017-02-20 11:23:20 +0100395 int rc;
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200396 int ret = 0;
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 Hofmeyrb6837e32017-10-10 23:20:26 +0200412 *subscr = (struct hlr_subscriber){};
413
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);
429
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200430out:
Max00b37152017-02-20 11:09:27 +0100431 db_remove_reset(stmt);
Harald Weltee687be52016-05-03 18:49:27 +0200432
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200433 switch (ret) {
434 case 0:
435 *err = NULL;
436 break;
437 case -ENOENT:
438 *err = "No such subscriber";
439 break;
440 default:
441 *err = sqlite3_errmsg(dbc->db);
442 break;
443 }
444 return ret;
445}
446
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200447/*! Retrieve subscriber data from the HLR database.
448 * \param[in,out] dbc database context.
449 * \param[in] imsi ASCII string of IMSI digits.
450 * \param[out] subscr place retrieved data in this struct.
451 * \returns 0 on success, -ENOENT if no such subscriber was found, -EIO on
452 * database error.
453 */
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200454int db_subscr_get_by_imsi(struct db_context *dbc, const char *imsi,
455 struct hlr_subscriber *subscr)
456{
457 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_IMSI];
458 const char *err;
459 int rc;
460
461 if (!db_bind_text(stmt, NULL, imsi))
462 return -EIO;
463
464 rc = db_sel(dbc, stmt, subscr, &err);
465 if (rc)
466 LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: IMSI='%s': %s\n",
467 imsi, err);
468 return rc;
469}
470
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200471/*! Retrieve subscriber data from the HLR database.
472 * \param[in,out] dbc database context.
473 * \param[in] msisdn ASCII string of MSISDN digits.
474 * \param[out] subscr place retrieved data in this struct.
475 * \returns 0 on success, -ENOENT if no such subscriber was found, -EIO on
476 * database error.
477 */
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200478int db_subscr_get_by_msisdn(struct db_context *dbc, const char *msisdn,
479 struct hlr_subscriber *subscr)
480{
481 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_MSISDN];
482 const char *err;
483 int rc;
484
485 if (!db_bind_text(stmt, NULL, msisdn))
486 return -EIO;
487
488 rc = db_sel(dbc, stmt, subscr, &err);
489 if (rc)
490 LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: MSISDN='%s': %s\n",
491 msisdn, err);
492 return rc;
493}
494
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200495/*! Retrieve subscriber data from the HLR database.
496 * \param[in,out] dbc database context.
497 * \param[in] id ID of the subscriber in the HLR db.
498 * \param[out] subscr place retrieved data in this struct.
499 * \returns 0 on success, -ENOENT if no such subscriber was found, -EIO on
500 * database error.
501 */
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200502int db_subscr_get_by_id(struct db_context *dbc, int64_t id,
503 struct hlr_subscriber *subscr)
504{
505 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_ID];
506 const char *err;
507 int rc;
508
509 if (!db_bind_int64(stmt, NULL, id))
510 return -EIO;
511
512 rc = db_sel(dbc, stmt, subscr, &err);
513 if (rc)
514 LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: ID=%"PRId64": %s\n",
515 id, err);
516 return rc;
Harald Weltee687be52016-05-03 18:49:27 +0200517}
518
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200519/*! You should use hlr_subscr_nam() instead; enable or disable PS or CS for a
520 * subscriber without notifying GSUP clients.
521 * \param[in,out] dbc database context.
522 * \param[in] imsi ASCII string of IMSI digits.
523 * \param[in] nam_val True to enable CS/PS, false to disable.
524 * \param[in] is_ps when true, set nam_ps, else set nam_cs.
525 * \returns 0 on success, -ENOENT when the given IMSI does not exist, -EIO on
526 * database errors.
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200527 */
528int db_subscr_nam(struct db_context *dbc, const char *imsi, bool nam_val, bool is_ps)
Max3ce36862017-02-20 11:18:04 +0100529{
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200530 sqlite3_stmt *stmt;
Max3ce36862017-02-20 11:18:04 +0100531 int rc;
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200532 int ret = 0;
Max3ce36862017-02-20 11:18:04 +0100533
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200534 stmt = dbc->stmt[is_ps ? DB_STMT_UPD_NAM_PS_BY_IMSI
535 : DB_STMT_UPD_NAM_CS_BY_IMSI];
Max3ce36862017-02-20 11:18:04 +0100536
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200537 if (!db_bind_text(stmt, "$imsi", imsi))
538 return -EIO;
539 if (!db_bind_int(stmt, "$val", nam_val ? 1 : 0))
540 return -EIO;
541
542 /* execute the statement */
543 rc = sqlite3_step(stmt);
Max3ce36862017-02-20 11:18:04 +0100544 if (rc != SQLITE_DONE) {
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200545 LOGHLR(imsi, LOGL_ERROR, "%s %s: SQL error: %s\n",
546 nam_val ? "enable" : "disable",
547 is_ps ? "PS" : "CS",
548 sqlite3_errmsg(dbc->db));
549 ret = -EIO;
550 goto out;
Max3ce36862017-02-20 11:18:04 +0100551 }
552
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200553 /* verify execution result */
554 rc = sqlite3_changes(dbc->db);
555 if (!rc) {
556 LOGP(DAUC, LOGL_ERROR, "Cannot %s %s: no such subscriber: IMSI='%s'\n",
557 nam_val ? "enable" : "disable",
558 is_ps ? "PS" : "CS",
559 imsi);
560 ret = -ENOENT;
561 goto out;
562 } else if (rc != 1) {
563 LOGHLR(imsi, LOGL_ERROR, "%s %s: SQL modified %d rows (expected 1)\n",
564 nam_val ? "enable" : "disable",
565 is_ps ? "PS" : "CS",
Max3ce36862017-02-20 11:18:04 +0100566 rc);
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200567 ret = -EIO;
Max3ce36862017-02-20 11:18:04 +0100568 }
569
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200570out:
Max3ce36862017-02-20 11:18:04 +0100571 db_remove_reset(stmt);
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200572 return ret;
Max3ce36862017-02-20 11:18:04 +0100573}
574
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200575/*! Record a Location Updating in the database.
576 * \param[in,out] dbc database context.
577 * \param[in] subscr_id ID of the subscriber in the HLR db.
578 * \param[in] vlr_or_sgsn_number ASCII string of identifier digits.
579 * \param[in] is_ps when true, set sgsn_number, else set vlr_number.
580 * \returns 0 on success, -ENOENT when the given subscriber does not exist,
581 * -EIO on database errors.
582 */
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200583int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
584 const char *vlr_or_sgsn_number, bool is_ps)
Harald Weltee687be52016-05-03 18:49:27 +0200585{
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200586 sqlite3_stmt *stmt;
Harald Weltee687be52016-05-03 18:49:27 +0200587 int rc, ret = 0;
588
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200589 stmt = dbc->stmt[is_ps ? DB_STMT_UPD_SGSN_BY_ID
590 : DB_STMT_UPD_VLR_BY_ID];
Harald Weltee687be52016-05-03 18:49:27 +0200591
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200592 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
593 return -EIO;
Harald Weltee687be52016-05-03 18:49:27 +0200594
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200595 if (!db_bind_text(stmt, "$number", vlr_or_sgsn_number))
596 return -EIO;
Harald Weltee687be52016-05-03 18:49:27 +0200597
598 /* execute the statement */
599 rc = sqlite3_step(stmt);
600 if (rc != SQLITE_DONE) {
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200601 LOGP(DAUC, LOGL_ERROR, "Update %s number for subscriber ID=%"PRId64": SQL Error: %s\n",
602 is_ps? "SGSN" : "VLR", subscr_id, sqlite3_errmsg(dbc->db));
603 ret = -EIO;
604 goto out;
Harald Weltee687be52016-05-03 18:49:27 +0200605 }
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200606
607 /* verify execution result */
608 rc = sqlite3_changes(dbc->db);
609 if (!rc) {
610 LOGP(DAUC, LOGL_ERROR, "Cannot update %s number for subscriber ID=%"PRId64
611 ": no such subscriber\n",
612 is_ps? "SGSN" : "VLR", subscr_id);
613 ret = -ENOENT;
614 } else if (rc != 1) {
615 LOGP(DAUC, LOGL_ERROR, "Update %s number for subscriber ID=%"PRId64
616 ": SQL modified %d rows (expected 1)\n",
617 is_ps? "SGSN" : "VLR", subscr_id, rc);
618 ret = -EIO;
619 }
620
Harald Weltee687be52016-05-03 18:49:27 +0200621out:
Max00b37152017-02-20 11:09:27 +0100622 db_remove_reset(stmt);
Harald Weltee687be52016-05-03 18:49:27 +0200623 return ret;
624}
Harald Welteb18f0e02016-05-05 21:03:03 +0200625
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200626/*! Set the ms_purged_cs or ms_purged_ps values in the database.
627 * \param[in,out] dbc database context.
628 * \param[in] by_imsi ASCII string of IMSI digits.
629 * \param[in] purge_val true to purge, false to un-purge.
630 * \param[in] is_ps when true, set ms_purged_ps, else set ms_purged_cs.
631 * \returns 0 on success, -ENOENT when the given IMSI does not exist, -EIO on
632 * database errors.
633 */
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200634int db_subscr_purge(struct db_context *dbc, const char *by_imsi,
635 bool purge_val, bool is_ps)
Harald Welteb18f0e02016-05-05 21:03:03 +0200636{
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200637 sqlite3_stmt *stmt;
638 int rc, ret = 0;
Harald Welteb18f0e02016-05-05 21:03:03 +0200639
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200640 stmt = dbc->stmt[is_ps ? DB_STMT_UPD_PURGE_PS_BY_IMSI
641 : DB_STMT_UPD_PURGE_CS_BY_IMSI];
Harald Welteb18f0e02016-05-05 21:03:03 +0200642
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200643 if (!db_bind_text(stmt, "$imsi", by_imsi))
644 return -EIO;
645 if (!db_bind_int(stmt, "$val", purge_val ? 1 : 0))
646 return -EIO;
Harald Welteb18f0e02016-05-05 21:03:03 +0200647
648 /* execute the statement */
649 rc = sqlite3_step(stmt);
650 if (rc != SQLITE_DONE) {
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200651 LOGP(DAUC, LOGL_ERROR, "%s %s: SQL error: %s\n",
652 purge_val ? "purge" : "un-purge",
653 is_ps ? "PS" : "CS",
654 sqlite3_errmsg(dbc->db));
655 ret = -EIO;
656 goto out;
Harald Welteb18f0e02016-05-05 21:03:03 +0200657 }
Max00b37152017-02-20 11:09:27 +0100658
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200659 /* verify execution result */
660 rc = sqlite3_changes(dbc->db);
661 if (!rc) {
662 LOGP(DAUC, LOGL_ERROR, "Cannot %s %s: no such subscriber: IMSI='%s'\n",
663 purge_val ? "purge" : "un-purge",
664 is_ps ? "PS" : "CS",
665 by_imsi);
666 ret = -ENOENT;
667 goto out;
668 } else if (rc != 1) {
669 LOGHLR(by_imsi, LOGL_ERROR, "%s %s: SQL modified %d rows (expected 1)\n",
670 purge_val ? "purge" : "un-purge",
671 is_ps ? "PS" : "CS",
672 rc);
673 ret = -EIO;
674 }
675
676out:
Max00b37152017-02-20 11:09:27 +0100677 db_remove_reset(stmt);
Harald Welteb18f0e02016-05-05 21:03:03 +0200678
679 return ret;
680}
Neels Hofmeyr00b1d432017-10-17 01:43:48 +0200681
682/*! Update nam_cs/nam_ps in the db and trigger notifications to GSUP clients.
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200683 * \param[in,out] hlr Global hlr context.
684 * \param[in] subscr Subscriber from a fresh db_subscr_get_by_*() call.
685 * \param[in] nam_val True to enable CS/PS, false to disable.
686 * \param[in] is_ps True to enable/disable PS, false for CS.
Neels Hofmeyr00b1d432017-10-17 01:43:48 +0200687 * \returns 0 on success, ENOEXEC if there is no need to change, a negative
688 * value on error.
689 */
690int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps)
691{
692 int rc;
693 struct lu_operation *luop;
694 struct osmo_gsup_conn *co;
695 bool is_val = is_ps? subscr->nam_ps : subscr->nam_cs;
696
697 if (is_val == nam_val) {
698 LOGHLR(subscr->imsi, LOGL_DEBUG, "Already has the requested value when asked to %s %s\n",
699 nam_val ? "enable" : "disable", is_ps ? "PS" : "CS");
700 return ENOEXEC;
701 }
702
703 rc = db_subscr_nam(hlr->dbc, subscr->imsi, nam_val, is_ps);
704 if (rc)
705 return rc > 0? -rc : rc;
706
707 /* If we're disabling, send a notice out to the GSUP client that is
708 * responsible. Otherwise no need. */
709 if (nam_val)
710 return 0;
711
712 /* FIXME: only send to single SGSN where latest update for IMSI came from */
713 llist_for_each_entry(co, &hlr->gs->clients, list) {
714 luop = lu_op_alloc_conn(co);
715 if (!luop) {
716 LOGHLR(subscr->imsi, LOGL_ERROR,
717 "Cannot notify GSUP client, cannot allocate lu_operation,"
718 " for %s:%u\n",
719 co && co->conn && co->conn->server? co->conn->server->addr : "unset",
720 co && co->conn && co->conn->server? co->conn->server->port : 0);
721 continue;
722 }
723 luop->subscr = *subscr;
724 lu_op_tx_del_subscr_data(luop);
725 lu_op_free(luop);
726 }
727 return 0;
728}