blob: 2bccc3875cf381bb08fc01c127806d426ded530c [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 Hofmeyr16140f72017-10-25 19:17:18 +020038/*! Add new subscriber record to the HLR database.
39 * \param[in,out] dbc database context.
40 * \param[in] imsi ASCII string of IMSI digits, is validated.
41 * \returns 0 on success, -EINVAL on invalid IMSI, -EIO on database error.
42 */
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020043int db_subscr_create(struct db_context *dbc, const char *imsi)
44{
45 sqlite3_stmt *stmt;
46 int rc;
47
48 if (!osmo_imsi_str_valid(imsi)) {
49 LOGP(DAUC, LOGL_ERROR, "Cannot create subscriber: invalid IMSI: '%s'\n",
50 imsi);
51 return -EINVAL;
52 }
53
54 stmt = dbc->stmt[DB_STMT_SUBSCR_CREATE];
55
56 if (!db_bind_text(stmt, "$imsi", imsi))
57 return -EIO;
58
59 /* execute the statement */
60 rc = sqlite3_step(stmt);
61 db_remove_reset(stmt);
62 if (rc != SQLITE_DONE) {
63 LOGHLR(imsi, LOGL_ERROR, "Cannot create subscriber: SQL error: (%d) %s\n",
64 rc, sqlite3_errmsg(dbc->db));
65 return -EIO;
66 }
67
68 return 0;
69}
70
Neels Hofmeyr16140f72017-10-25 19:17:18 +020071/*! Completely delete a subscriber record from the HLR database.
72 * Also remove authentication data.
73 * Future todo: also drop from all other database tables, which aren't used yet
74 * at the time of writing this.
75 * \param[in,out] dbc database context.
76 * \param[in] subscr_id ID of the subscriber in the HLR db.
77 * \returns if the subscriber was found and removed, -EIO on database error,
78 * -ENOENT if no such subscriber data exists.
79 */
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020080int db_subscr_delete_by_id(struct db_context *dbc, int64_t subscr_id)
81{
82 int rc;
Neels Hofmeyr1332a172017-10-10 02:25:00 +020083 struct sub_auth_data_str aud;
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +020084 int ret = 0;
85
86 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_DEL_BY_ID];
87
88 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
89 return -EIO;
90
91 /* execute the statement */
92 rc = sqlite3_step(stmt);
93 if (rc != SQLITE_DONE) {
94 LOGP(DAUC, LOGL_ERROR,
95 "Cannot delete subscriber ID=%"PRId64": SQL error: (%d) %s\n",
96 subscr_id, rc, sqlite3_errmsg(dbc->db));
97 db_remove_reset(stmt);
98 return -EIO;
99 }
100
101 /* verify execution result */
102 rc = sqlite3_changes(dbc->db);
103 if (!rc) {
104 LOGP(DAUC, LOGL_ERROR, "Cannot delete: no such subscriber: ID=%"PRId64"\n",
105 subscr_id);
106 ret = -ENOENT;
107 } else if (rc != 1) {
108 LOGP(DAUC, LOGL_ERROR, "Delete subscriber ID=%"PRId64
109 ": SQL modified %d rows (expected 1)\n", subscr_id, rc);
110 ret = -EIO;
111 }
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200112 db_remove_reset(stmt);
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200113
114 /* make sure to remove authentication data for this subscriber id, for
115 * both 2G and 3G. */
116
117 aud = (struct sub_auth_data_str){
118 .type = OSMO_AUTH_TYPE_GSM,
119 .algo = OSMO_AUTH_ALG_NONE,
120 };
121 rc = db_subscr_update_aud_by_id(dbc, subscr_id, &aud);
122 if (ret == -ENOENT && !rc)
123 ret = 0;
124
125 aud = (struct sub_auth_data_str){
126 .type = OSMO_AUTH_TYPE_UMTS,
127 .algo = OSMO_AUTH_ALG_NONE,
128 };
129 rc = db_subscr_update_aud_by_id(dbc, subscr_id, &aud);
130 if (ret == -ENOENT && !rc)
131 ret = 0;
132
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200133 return ret;
134}
135
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200136/*! Set a subscriber's MSISDN in the HLR database.
137 * \param[in,out] dbc database context.
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100138 * \param[in] imsi ASCII string of IMSI digits, or NULL to remove the MSISDN.
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200139 * \param[in] msisdn ASCII string of MSISDN digits.
140 * \returns 0 on success, -EINVAL in case of invalid MSISDN string, -EIO on
141 * database failure, -ENOENT if no such subscriber exists.
142 */
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200143int db_subscr_update_msisdn_by_imsi(struct db_context *dbc, const char *imsi,
144 const char *msisdn)
145{
146 int rc;
147 int ret = 0;
148
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100149 if (msisdn && !osmo_msisdn_str_valid(msisdn)) {
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200150 LOGHLR(imsi, LOGL_ERROR,
151 "Cannot update subscriber: invalid MSISDN: '%s'\n",
152 msisdn);
153 return -EINVAL;
154 }
155
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100156 sqlite3_stmt *stmt = dbc->stmt[
157 msisdn ? DB_STMT_SET_MSISDN_BY_IMSI : DB_STMT_DELETE_MSISDN_BY_IMSI];
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200158
159 if (!db_bind_text(stmt, "$imsi", imsi))
160 return -EIO;
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100161 if (msisdn) {
162 if (!db_bind_text(stmt, "$msisdn", msisdn))
163 return -EIO;
164 }
Neels Hofmeyrf7c3e6e2017-10-09 17:55:16 +0200165
166 /* execute the statement */
167 rc = sqlite3_step(stmt);
168 if (rc != SQLITE_DONE) {
169 LOGHLR(imsi, LOGL_ERROR,
170 "Cannot update subscriber's MSISDN: SQL error: (%d) %s\n",
171 rc, sqlite3_errmsg(dbc->db));
172 ret = -EIO;
173 goto out;
174 }
175
176 /* verify execution result */
177 rc = sqlite3_changes(dbc->db);
178 if (!rc) {
179 LOGP(DAUC, LOGL_ERROR, "Cannot update MSISDN: no such subscriber: IMSI='%s'\n",
180 imsi);
181 ret = -ENOENT;
182 goto out;
183 } else if (rc != 1) {
184 LOGHLR(imsi, LOGL_ERROR, "Update MSISDN: SQL modified %d rows (expected 1)\n", rc);
185 ret = -EIO;
186 }
187
188out:
189 db_remove_reset(stmt);
190 return ret;
191
192}
193
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200194/*! Insert or update 2G or 3G authentication tokens in the database.
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200195 * If aud->type is OSMO_AUTH_TYPE_GSM, the auc_2g table entry for the
196 * subscriber will be added or modified; if aud->algo is OSMO_AUTH_ALG_NONE,
197 * however, the auc_2g entry for the subscriber is deleted. If aud->type is
198 * OSMO_AUTH_TYPE_UMTS, the auc_3g table is updated; again, if aud->algo is
199 * OSMO_AUTH_ALG_NONE, the auc_3g entry is deleted.
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200200 * \param[in,out] dbc database context.
201 * \param[in] subscr_id DB ID of the subscriber.
202 * \param[in] aud Pointer to new auth data (in ASCII string form).
203 * \returns 0 on success, -EINVAL for invalid aud, -ENOENT for unknown
204 * subscr_id, -EIO for database errors.
Neels Hofmeyr1332a172017-10-10 02:25:00 +0200205 */
206int db_subscr_update_aud_by_id(struct db_context *dbc, int64_t subscr_id,
207 const struct sub_auth_data_str *aud)
208{
209 sqlite3_stmt *stmt_del;
210 sqlite3_stmt *stmt_ins;
211 sqlite3_stmt *stmt;
212 const char *label;
213 int rc;
214 int ret = 0;
215
216 switch (aud->type) {
217 case OSMO_AUTH_TYPE_GSM:
218 label = "auc_2g";
219 stmt_del = dbc->stmt[DB_STMT_AUC_2G_DELETE];
220 stmt_ins = dbc->stmt[DB_STMT_AUC_2G_INSERT];
221
222 switch (aud->algo) {
223 case OSMO_AUTH_ALG_NONE:
224 case OSMO_AUTH_ALG_COMP128v1:
225 case OSMO_AUTH_ALG_COMP128v2:
226 case OSMO_AUTH_ALG_COMP128v3:
227 case OSMO_AUTH_ALG_XOR:
228 break;
229 case OSMO_AUTH_ALG_MILENAGE:
230 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
231 " auth algo not suited for 2G: %s\n",
232 osmo_auth_alg_name(aud->algo));
233 return -EINVAL;
234 default:
235 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
236 " Unknown auth algo: %d\n", aud->algo);
237 return -EINVAL;
238 }
239
240 if (aud->algo == OSMO_AUTH_ALG_NONE)
241 break;
242 if (!osmo_is_hexstr(aud->u.gsm.ki, 32, 32, true)) {
243 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
244 " Invalid KI: '%s'\n", aud->u.gsm.ki);
245 return -EINVAL;
246 }
247 break;
248
249 case OSMO_AUTH_TYPE_UMTS:
250 label = "auc_3g";
251 stmt_del = dbc->stmt[DB_STMT_AUC_3G_DELETE];
252 stmt_ins = dbc->stmt[DB_STMT_AUC_3G_INSERT];
253 switch (aud->algo) {
254 case OSMO_AUTH_ALG_NONE:
255 case OSMO_AUTH_ALG_MILENAGE:
256 break;
257 case OSMO_AUTH_ALG_COMP128v1:
258 case OSMO_AUTH_ALG_COMP128v2:
259 case OSMO_AUTH_ALG_COMP128v3:
260 case OSMO_AUTH_ALG_XOR:
261 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
262 " auth algo not suited for 3G: %s\n",
263 osmo_auth_alg_name(aud->algo));
264 return -EINVAL;
265 default:
266 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
267 " Unknown auth algo: %d\n", aud->algo);
268 return -EINVAL;
269 }
270
271 if (aud->algo == OSMO_AUTH_ALG_NONE)
272 break;
273 if (!osmo_is_hexstr(aud->u.umts.k, 32, 32, true)) {
274 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
275 " Invalid K: '%s'\n", aud->u.umts.k);
276 return -EINVAL;
277 }
278 if (!osmo_is_hexstr(aud->u.umts.opc, 32, 32, true)) {
279 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
280 " Invalid OP/OPC: '%s'\n", aud->u.umts.opc);
281 return -EINVAL;
282 }
283 if (aud->u.umts.ind_bitlen > OSMO_MILENAGE_IND_BITLEN_MAX) {
284 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
285 " Invalid ind_bitlen: %d\n", aud->u.umts.ind_bitlen);
286 return -EINVAL;
287 }
288 break;
289 default:
290 LOGP(DAUC, LOGL_ERROR, "Cannot update auth tokens:"
291 " unknown auth type: %d\n", aud->type);
292 return -EINVAL;
293 }
294
295 stmt = stmt_del;
296
297 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
298 return -EIO;
299
300 /* execute the statement */
301 rc = sqlite3_step(stmt);
302 if (rc != SQLITE_DONE) {
303 LOGP(DAUC, LOGL_ERROR,
304 "Cannot delete %s row: SQL error: (%d) %s\n",
305 label, rc, sqlite3_errmsg(dbc->db));
306 ret = -EIO;
307 goto out;
308 }
309
310 /* verify execution result */
311 rc = sqlite3_changes(dbc->db);
312 if (!rc)
313 /* Leave "no such entry" logging to the caller -- during
314 * db_subscr_delete_by_id(), we call this to make sure it is
315 * empty, and no entry is not an error then.*/
316 ret = -ENOENT;
317 else if (rc != 1) {
318 LOGP(DAUC, LOGL_ERROR, "Delete subscriber ID=%"PRId64
319 " from %s: SQL modified %d rows (expected 1)\n",
320 subscr_id, label, rc);
321 ret = -EIO;
322 }
323
324 db_remove_reset(stmt);
325
326 /* Error situation? Return now. */
327 if (ret && ret != -ENOENT)
328 return ret;
329
330 /* Just delete requested? */
331 if (aud->algo == OSMO_AUTH_ALG_NONE)
332 return ret;
333
334 /* Don't return -ENOENT if inserting new data. */
335 ret = 0;
336
337 /* Insert new row */
338 stmt = stmt_ins;
339
340 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
341 return -EIO;
342
343 switch (aud->type) {
344 case OSMO_AUTH_TYPE_GSM:
345 if (!db_bind_int(stmt, "$algo_id_2g", aud->algo))
346 return -EIO;
347 if (!db_bind_text(stmt, "$ki", aud->u.gsm.ki))
348 return -EIO;
349 break;
350 case OSMO_AUTH_TYPE_UMTS:
351 if (!db_bind_int(stmt, "$algo_id_3g", aud->algo))
352 return -EIO;
353 if (!db_bind_text(stmt, "$k", aud->u.umts.k))
354 return -EIO;
355 if (!db_bind_text(stmt, "$op",
356 aud->u.umts.opc_is_op ? aud->u.umts.opc : NULL))
357 return -EIO;
358 if (!db_bind_text(stmt, "$opc",
359 aud->u.umts.opc_is_op ? NULL : aud->u.umts.opc))
360 return -EIO;
361 if (!db_bind_int(stmt, "$ind_bitlen", aud->u.umts.ind_bitlen))
362 return -EIO;
363 break;
364 default:
365 OSMO_ASSERT(false);
366 }
367
368 /* execute the statement */
369 rc = sqlite3_step(stmt);
370 if (rc != SQLITE_DONE) {
371 LOGP(DAUC, LOGL_ERROR,
372 "Cannot insert %s row: SQL error: (%d) %s\n",
373 label, rc, sqlite3_errmsg(dbc->db));
374 ret = -EIO;
375 goto out;
376 }
377
378out:
379 db_remove_reset(stmt);
380 return ret;
381}
382
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200383/* Common code for db_subscr_get_by_*() functions. */
384static int db_sel(struct db_context *dbc, sqlite3_stmt *stmt, struct hlr_subscriber *subscr,
385 const char **err)
Harald Weltee687be52016-05-03 18:49:27 +0200386{
Maxadc66482017-02-20 11:23:20 +0100387 int rc;
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200388 int ret = 0;
Harald Weltee687be52016-05-03 18:49:27 +0200389
390 /* execute the statement */
391 rc = sqlite3_step(stmt);
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200392 if (rc == SQLITE_DONE) {
393 ret = -ENOENT;
394 goto out;
395 }
Harald Weltee687be52016-05-03 18:49:27 +0200396 if (rc != SQLITE_ROW) {
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200397 ret = -EIO;
398 goto out;
Maxadc66482017-02-20 11:23:20 +0100399 }
400
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200401 if (!subscr)
402 goto out;
Harald Weltee687be52016-05-03 18:49:27 +0200403
Neels Hofmeyrb6837e32017-10-10 23:20:26 +0200404 *subscr = (struct hlr_subscriber){};
405
Harald Weltee687be52016-05-03 18:49:27 +0200406 /* obtain the various columns */
407 subscr->id = sqlite3_column_int64(stmt, 0);
Neels Hofmeyrdbced932017-10-27 02:57:51 +0200408 copy_sqlite3_text_to_buf(subscr->imsi, stmt, 1);
409 copy_sqlite3_text_to_buf(subscr->msisdn, stmt, 2);
Harald Welte99909272016-05-05 18:24:15 +0200410 /* FIXME: These should all be BLOBs as they might contain NUL */
Neels Hofmeyrdbced932017-10-27 02:57:51 +0200411 copy_sqlite3_text_to_buf(subscr->vlr_number, stmt, 3);
412 copy_sqlite3_text_to_buf(subscr->sgsn_number, stmt, 4);
413 copy_sqlite3_text_to_buf(subscr->sgsn_address, stmt, 5);
Harald Weltee687be52016-05-03 18:49:27 +0200414 subscr->periodic_lu_timer = sqlite3_column_int(stmt, 6);
415 subscr->periodic_rau_tau_timer = sqlite3_column_int(stmt, 7);
416 subscr->nam_cs = sqlite3_column_int(stmt, 8);
417 subscr->nam_ps = sqlite3_column_int(stmt, 9);
418 subscr->lmsi = sqlite3_column_int(stmt, 10);
419 subscr->ms_purged_cs = sqlite3_column_int(stmt, 11);
420 subscr->ms_purged_ps = sqlite3_column_int(stmt, 12);
421
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200422out:
Max00b37152017-02-20 11:09:27 +0100423 db_remove_reset(stmt);
Harald Weltee687be52016-05-03 18:49:27 +0200424
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200425 switch (ret) {
426 case 0:
427 *err = NULL;
428 break;
429 case -ENOENT:
430 *err = "No such subscriber";
431 break;
432 default:
433 *err = sqlite3_errmsg(dbc->db);
434 break;
435 }
436 return ret;
437}
438
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200439/*! Retrieve subscriber data from the HLR database.
440 * \param[in,out] dbc database context.
441 * \param[in] imsi ASCII string of IMSI digits.
442 * \param[out] subscr place retrieved data in this struct.
443 * \returns 0 on success, -ENOENT if no such subscriber was found, -EIO on
444 * database error.
445 */
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200446int db_subscr_get_by_imsi(struct db_context *dbc, const char *imsi,
447 struct hlr_subscriber *subscr)
448{
449 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_IMSI];
450 const char *err;
451 int rc;
452
453 if (!db_bind_text(stmt, NULL, imsi))
454 return -EIO;
455
456 rc = db_sel(dbc, stmt, subscr, &err);
457 if (rc)
458 LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: IMSI='%s': %s\n",
459 imsi, err);
460 return rc;
461}
462
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200463/*! Retrieve subscriber data from the HLR database.
464 * \param[in,out] dbc database context.
465 * \param[in] msisdn ASCII string of MSISDN digits.
466 * \param[out] subscr place retrieved data in this struct.
467 * \returns 0 on success, -ENOENT if no such subscriber was found, -EIO on
468 * database error.
469 */
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200470int db_subscr_get_by_msisdn(struct db_context *dbc, const char *msisdn,
471 struct hlr_subscriber *subscr)
472{
473 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_MSISDN];
474 const char *err;
475 int rc;
476
477 if (!db_bind_text(stmt, NULL, msisdn))
478 return -EIO;
479
480 rc = db_sel(dbc, stmt, subscr, &err);
481 if (rc)
482 LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: MSISDN='%s': %s\n",
483 msisdn, err);
484 return rc;
485}
486
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200487/*! Retrieve subscriber data from the HLR database.
488 * \param[in,out] dbc database context.
489 * \param[in] id ID of the subscriber in the HLR db.
490 * \param[out] subscr place retrieved data in this struct.
491 * \returns 0 on success, -ENOENT if no such subscriber was found, -EIO on
492 * database error.
493 */
Neels Hofmeyr9c2bbc82017-10-09 17:30:32 +0200494int db_subscr_get_by_id(struct db_context *dbc, int64_t id,
495 struct hlr_subscriber *subscr)
496{
497 sqlite3_stmt *stmt = dbc->stmt[DB_STMT_SEL_BY_ID];
498 const char *err;
499 int rc;
500
501 if (!db_bind_int64(stmt, NULL, id))
502 return -EIO;
503
504 rc = db_sel(dbc, stmt, subscr, &err);
505 if (rc)
506 LOGP(DAUC, LOGL_ERROR, "Cannot read subscriber from db: ID=%"PRId64": %s\n",
507 id, err);
508 return rc;
Harald Weltee687be52016-05-03 18:49:27 +0200509}
510
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200511/*! You should use hlr_subscr_nam() instead; enable or disable PS or CS for a
512 * subscriber without notifying GSUP clients.
513 * \param[in,out] dbc database context.
514 * \param[in] imsi ASCII string of IMSI digits.
515 * \param[in] nam_val True to enable CS/PS, false to disable.
516 * \param[in] is_ps when true, set nam_ps, else set nam_cs.
517 * \returns 0 on success, -ENOENT when the given IMSI does not exist, -EIO on
518 * database errors.
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200519 */
520int db_subscr_nam(struct db_context *dbc, const char *imsi, bool nam_val, bool is_ps)
Max3ce36862017-02-20 11:18:04 +0100521{
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200522 sqlite3_stmt *stmt;
Max3ce36862017-02-20 11:18:04 +0100523 int rc;
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200524 int ret = 0;
Max3ce36862017-02-20 11:18:04 +0100525
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200526 stmt = dbc->stmt[is_ps ? DB_STMT_UPD_NAM_PS_BY_IMSI
527 : DB_STMT_UPD_NAM_CS_BY_IMSI];
Max3ce36862017-02-20 11:18:04 +0100528
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200529 if (!db_bind_text(stmt, "$imsi", imsi))
530 return -EIO;
531 if (!db_bind_int(stmt, "$val", nam_val ? 1 : 0))
532 return -EIO;
533
534 /* execute the statement */
535 rc = sqlite3_step(stmt);
Max3ce36862017-02-20 11:18:04 +0100536 if (rc != SQLITE_DONE) {
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200537 LOGHLR(imsi, LOGL_ERROR, "%s %s: SQL error: %s\n",
538 nam_val ? "enable" : "disable",
539 is_ps ? "PS" : "CS",
540 sqlite3_errmsg(dbc->db));
541 ret = -EIO;
542 goto out;
Max3ce36862017-02-20 11:18:04 +0100543 }
544
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200545 /* verify execution result */
546 rc = sqlite3_changes(dbc->db);
547 if (!rc) {
548 LOGP(DAUC, LOGL_ERROR, "Cannot %s %s: no such subscriber: IMSI='%s'\n",
549 nam_val ? "enable" : "disable",
550 is_ps ? "PS" : "CS",
551 imsi);
552 ret = -ENOENT;
553 goto out;
554 } else if (rc != 1) {
555 LOGHLR(imsi, LOGL_ERROR, "%s %s: SQL modified %d rows (expected 1)\n",
556 nam_val ? "enable" : "disable",
557 is_ps ? "PS" : "CS",
Max3ce36862017-02-20 11:18:04 +0100558 rc);
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200559 ret = -EIO;
Max3ce36862017-02-20 11:18:04 +0100560 }
561
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200562out:
Max3ce36862017-02-20 11:18:04 +0100563 db_remove_reset(stmt);
Neels Hofmeyre8ccd502017-10-06 04:10:06 +0200564 return ret;
Max3ce36862017-02-20 11:18:04 +0100565}
566
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200567/*! Record a Location Updating in the database.
568 * \param[in,out] dbc database context.
569 * \param[in] subscr_id ID of the subscriber in the HLR db.
570 * \param[in] vlr_or_sgsn_number ASCII string of identifier digits.
571 * \param[in] is_ps when true, set sgsn_number, else set vlr_number.
572 * \returns 0 on success, -ENOENT when the given subscriber does not exist,
573 * -EIO on database errors.
574 */
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200575int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
576 const char *vlr_or_sgsn_number, bool is_ps)
Harald Weltee687be52016-05-03 18:49:27 +0200577{
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200578 sqlite3_stmt *stmt;
Harald Weltee687be52016-05-03 18:49:27 +0200579 int rc, ret = 0;
580
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200581 stmt = dbc->stmt[is_ps ? DB_STMT_UPD_SGSN_BY_ID
582 : DB_STMT_UPD_VLR_BY_ID];
Harald Weltee687be52016-05-03 18:49:27 +0200583
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200584 if (!db_bind_int64(stmt, "$subscriber_id", subscr_id))
585 return -EIO;
Harald Weltee687be52016-05-03 18:49:27 +0200586
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200587 if (!db_bind_text(stmt, "$number", vlr_or_sgsn_number))
588 return -EIO;
Harald Weltee687be52016-05-03 18:49:27 +0200589
590 /* execute the statement */
591 rc = sqlite3_step(stmt);
592 if (rc != SQLITE_DONE) {
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200593 LOGP(DAUC, LOGL_ERROR, "Update %s number for subscriber ID=%"PRId64": SQL Error: %s\n",
594 is_ps? "SGSN" : "VLR", subscr_id, sqlite3_errmsg(dbc->db));
595 ret = -EIO;
596 goto out;
Harald Weltee687be52016-05-03 18:49:27 +0200597 }
Neels Hofmeyrdd783052017-10-09 17:36:08 +0200598
599 /* verify execution result */
600 rc = sqlite3_changes(dbc->db);
601 if (!rc) {
602 LOGP(DAUC, LOGL_ERROR, "Cannot update %s number for subscriber ID=%"PRId64
603 ": no such subscriber\n",
604 is_ps? "SGSN" : "VLR", subscr_id);
605 ret = -ENOENT;
606 } else if (rc != 1) {
607 LOGP(DAUC, LOGL_ERROR, "Update %s number for subscriber ID=%"PRId64
608 ": SQL modified %d rows (expected 1)\n",
609 is_ps? "SGSN" : "VLR", subscr_id, rc);
610 ret = -EIO;
611 }
612
Harald Weltee687be52016-05-03 18:49:27 +0200613out:
Max00b37152017-02-20 11:09:27 +0100614 db_remove_reset(stmt);
Harald Weltee687be52016-05-03 18:49:27 +0200615 return ret;
616}
Harald Welteb18f0e02016-05-05 21:03:03 +0200617
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200618/*! Set the ms_purged_cs or ms_purged_ps values in the database.
619 * \param[in,out] dbc database context.
620 * \param[in] by_imsi ASCII string of IMSI digits.
621 * \param[in] purge_val true to purge, false to un-purge.
622 * \param[in] is_ps when true, set ms_purged_ps, else set ms_purged_cs.
623 * \returns 0 on success, -ENOENT when the given IMSI does not exist, -EIO on
624 * database errors.
625 */
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200626int db_subscr_purge(struct db_context *dbc, const char *by_imsi,
627 bool purge_val, bool is_ps)
Harald Welteb18f0e02016-05-05 21:03:03 +0200628{
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200629 sqlite3_stmt *stmt;
630 int rc, ret = 0;
Harald Welteb18f0e02016-05-05 21:03:03 +0200631
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200632 stmt = dbc->stmt[is_ps ? DB_STMT_UPD_PURGE_PS_BY_IMSI
633 : DB_STMT_UPD_PURGE_CS_BY_IMSI];
Harald Welteb18f0e02016-05-05 21:03:03 +0200634
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200635 if (!db_bind_text(stmt, "$imsi", by_imsi))
636 return -EIO;
637 if (!db_bind_int(stmt, "$val", purge_val ? 1 : 0))
638 return -EIO;
Harald Welteb18f0e02016-05-05 21:03:03 +0200639
640 /* execute the statement */
641 rc = sqlite3_step(stmt);
642 if (rc != SQLITE_DONE) {
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200643 LOGP(DAUC, LOGL_ERROR, "%s %s: SQL error: %s\n",
644 purge_val ? "purge" : "un-purge",
645 is_ps ? "PS" : "CS",
646 sqlite3_errmsg(dbc->db));
647 ret = -EIO;
648 goto out;
Harald Welteb18f0e02016-05-05 21:03:03 +0200649 }
Max00b37152017-02-20 11:09:27 +0100650
Neels Hofmeyre50121e2017-10-09 17:48:51 +0200651 /* verify execution result */
652 rc = sqlite3_changes(dbc->db);
653 if (!rc) {
654 LOGP(DAUC, LOGL_ERROR, "Cannot %s %s: no such subscriber: IMSI='%s'\n",
655 purge_val ? "purge" : "un-purge",
656 is_ps ? "PS" : "CS",
657 by_imsi);
658 ret = -ENOENT;
659 goto out;
660 } else if (rc != 1) {
661 LOGHLR(by_imsi, LOGL_ERROR, "%s %s: SQL modified %d rows (expected 1)\n",
662 purge_val ? "purge" : "un-purge",
663 is_ps ? "PS" : "CS",
664 rc);
665 ret = -EIO;
666 }
667
668out:
Max00b37152017-02-20 11:09:27 +0100669 db_remove_reset(stmt);
Harald Welteb18f0e02016-05-05 21:03:03 +0200670
671 return ret;
672}
Neels Hofmeyr00b1d432017-10-17 01:43:48 +0200673
674/*! Update nam_cs/nam_ps in the db and trigger notifications to GSUP clients.
Neels Hofmeyr16140f72017-10-25 19:17:18 +0200675 * \param[in,out] hlr Global hlr context.
676 * \param[in] subscr Subscriber from a fresh db_subscr_get_by_*() call.
677 * \param[in] nam_val True to enable CS/PS, false to disable.
678 * \param[in] is_ps True to enable/disable PS, false for CS.
Neels Hofmeyr00b1d432017-10-17 01:43:48 +0200679 * \returns 0 on success, ENOEXEC if there is no need to change, a negative
680 * value on error.
681 */
682int hlr_subscr_nam(struct hlr *hlr, struct hlr_subscriber *subscr, bool nam_val, bool is_ps)
683{
684 int rc;
685 struct lu_operation *luop;
686 struct osmo_gsup_conn *co;
687 bool is_val = is_ps? subscr->nam_ps : subscr->nam_cs;
688
689 if (is_val == nam_val) {
690 LOGHLR(subscr->imsi, LOGL_DEBUG, "Already has the requested value when asked to %s %s\n",
691 nam_val ? "enable" : "disable", is_ps ? "PS" : "CS");
692 return ENOEXEC;
693 }
694
695 rc = db_subscr_nam(hlr->dbc, subscr->imsi, nam_val, is_ps);
696 if (rc)
697 return rc > 0? -rc : rc;
698
699 /* If we're disabling, send a notice out to the GSUP client that is
700 * responsible. Otherwise no need. */
701 if (nam_val)
702 return 0;
703
704 /* FIXME: only send to single SGSN where latest update for IMSI came from */
705 llist_for_each_entry(co, &hlr->gs->clients, list) {
706 luop = lu_op_alloc_conn(co);
707 if (!luop) {
708 LOGHLR(subscr->imsi, LOGL_ERROR,
709 "Cannot notify GSUP client, cannot allocate lu_operation,"
710 " for %s:%u\n",
711 co && co->conn && co->conn->server? co->conn->server->addr : "unset",
712 co && co->conn && co->conn->server? co->conn->server->port : 0);
713 continue;
714 }
715 luop->subscr = *subscr;
716 lu_op_tx_del_subscr_data(luop);
717 lu_op_free(luop);
718 }
719 return 0;
720}