blob: 28e978213da584ffc90a81c77a7428280f0244eb [file] [log] [blame]
Holger Freyther12aa50d2009-01-01 18:02:05 +00001/* Simple HLR/VLR database backend using dbi */
Jan Luebbefaaa49c2008-12-27 01:07:07 +00002/* (C) 2008 by Jan Luebbe <jluebbe@debian.org>
Holger Freyther12aa50d2009-01-01 18:02:05 +00003 * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
Harald Weltec2e302d2009-07-05 14:08:13 +02004 * (C) 2009 by Harald Welte <laforge@gnumonks.org>
Jan Luebbefaaa49c2008-12-27 01:07:07 +00005 * All Rights Reserved
6 *
7 * This program is free software; you can redistribute it and/or modify
Harald Welte9af6ddf2011-01-01 15:25:50 +01008 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
Jan Luebbefaaa49c2008-12-27 01:07:07 +000010 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Harald Welte9af6ddf2011-01-01 15:25:50 +010015 * GNU Affero General Public License for more details.
Jan Luebbefaaa49c2008-12-27 01:07:07 +000016 *
Harald Welte9af6ddf2011-01-01 15:25:50 +010017 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
Jan Luebbefaaa49c2008-12-27 01:07:07 +000019 *
20 */
21
Harald Weltef2b4cd72010-05-13 11:45:07 +020022#include <stdint.h>
23#include <inttypes.h>
Holger Freytherbde36102008-12-28 22:51:39 +000024#include <libgen.h>
Jan Luebbe7398eb92008-12-27 00:45:41 +000025#include <stdio.h>
Maxe6052c42016-06-30 10:25:49 +020026#include <stdbool.h>
Jan Luebbe5c15c852008-12-27 15:59:25 +000027#include <stdlib.h>
28#include <string.h>
Harald Welte7e310b12009-03-30 20:56:32 +000029#include <errno.h>
Jan Luebbe7398eb92008-12-27 00:45:41 +000030#include <dbi/dbi.h>
31
Harald Weltef2b4cd72010-05-13 11:45:07 +020032#include <openbsc/gsm_data.h>
Holger Hans Peter Freyther28dcbc52010-12-22 18:21:14 +010033#include <openbsc/gsm_subscriber.h>
Harald Weltef2b4cd72010-05-13 11:45:07 +020034#include <openbsc/gsm_04_11.h>
35#include <openbsc/db.h>
Harald Weltef2b4cd72010-05-13 11:45:07 +020036#include <openbsc/debug.h>
Harald Welte2483f1b2016-06-19 18:06:02 +020037#include <openbsc/vlr.h>
Holger Hans Peter Freytherc5faf662010-12-22 18:16:01 +010038
Harald Welted3fa84d2016-04-20 17:50:17 +020039#include <osmocom/gsm/protocol/gsm_23_003.h>
Pablo Neira Ayuso136f4532011-03-22 16:47:59 +010040#include <osmocom/core/talloc.h>
41#include <osmocom/core/statistics.h>
42#include <osmocom/core/rate_ctr.h>
Neels Hofmeyr93bafb62017-01-13 03:12:08 +010043#include <osmocom/core/utils.h>
Harald Weltef2b4cd72010-05-13 11:45:07 +020044
Daniel Willmanncdeb8152015-10-08 16:10:23 +020045#include <openssl/rand.h>
46
Holger Freytherbde36102008-12-28 22:51:39 +000047static char *db_basename = NULL;
48static char *db_dirname = NULL;
Holger Freyther1d506c82009-04-19 06:35:20 +000049static dbi_conn conn;
Jan Luebbe7398eb92008-12-27 00:45:41 +000050
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +010051#define SCHEMA_REVISION "4"
Jan Luebbebfbdeec2012-12-27 00:27:16 +010052
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +010053enum {
54 SCHEMA_META,
55 INSERT_META,
56 SCHEMA_SUBSCRIBER,
57 SCHEMA_AUTH,
58 SCHEMA_EQUIPMENT,
59 SCHEMA_EQUIPMENT_WATCH,
60 SCHEMA_SMS,
61 SCHEMA_VLR,
62 SCHEMA_APDU,
63 SCHEMA_COUNTERS,
64 SCHEMA_RATE,
65 SCHEMA_AUTHKEY,
66 SCHEMA_AUTHLAST,
67};
68
69static const char *create_stmts[] = {
70 [SCHEMA_META] = "CREATE TABLE IF NOT EXISTS Meta ("
Harald Welte7e310b12009-03-30 20:56:32 +000071 "id INTEGER PRIMARY KEY AUTOINCREMENT, "
72 "key TEXT UNIQUE NOT NULL, "
73 "value TEXT NOT NULL"
74 ")",
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +010075 [INSERT_META] = "INSERT OR IGNORE INTO Meta "
Harald Welte7e310b12009-03-30 20:56:32 +000076 "(key, value) "
77 "VALUES "
Jan Luebbebfbdeec2012-12-27 00:27:16 +010078 "('revision', " SCHEMA_REVISION ")",
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +010079 [SCHEMA_SUBSCRIBER] = "CREATE TABLE IF NOT EXISTS Subscriber ("
Harald Welte7e310b12009-03-30 20:56:32 +000080 "id INTEGER PRIMARY KEY AUTOINCREMENT, "
81 "created TIMESTAMP NOT NULL, "
82 "updated TIMESTAMP NOT NULL, "
83 "imsi NUMERIC UNIQUE NOT NULL, "
84 "name TEXT, "
85 "extension TEXT UNIQUE, "
86 "authorized INTEGER NOT NULL DEFAULT 0, "
87 "tmsi TEXT UNIQUE, "
Jan Luebbebfbdeec2012-12-27 00:27:16 +010088 "lac INTEGER NOT NULL DEFAULT 0, "
89 "expire_lu TIMESTAMP DEFAULT NULL"
Harald Welte7e310b12009-03-30 20:56:32 +000090 ")",
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +010091 [SCHEMA_AUTH] = "CREATE TABLE IF NOT EXISTS AuthToken ("
Jan Luebbe31bef492009-08-12 14:31:14 +020092 "id INTEGER PRIMARY KEY AUTOINCREMENT, "
93 "subscriber_id INTEGER UNIQUE NOT NULL, "
94 "created TIMESTAMP NOT NULL, "
95 "token TEXT UNIQUE NOT NULL"
96 ")",
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +010097 [SCHEMA_EQUIPMENT] = "CREATE TABLE IF NOT EXISTS Equipment ("
Harald Welte7e310b12009-03-30 20:56:32 +000098 "id INTEGER PRIMARY KEY AUTOINCREMENT, "
99 "created TIMESTAMP NOT NULL, "
100 "updated TIMESTAMP NOT NULL, "
101 "name TEXT, "
Harald Weltec2e302d2009-07-05 14:08:13 +0200102 "classmark1 NUMERIC, "
103 "classmark2 BLOB, "
104 "classmark3 BLOB, "
Harald Welte7e310b12009-03-30 20:56:32 +0000105 "imei NUMERIC UNIQUE NOT NULL"
106 ")",
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +0100107 [SCHEMA_EQUIPMENT_WATCH] = "CREATE TABLE IF NOT EXISTS EquipmentWatch ("
Harald Welte7e310b12009-03-30 20:56:32 +0000108 "id INTEGER PRIMARY KEY AUTOINCREMENT, "
109 "created TIMESTAMP NOT NULL, "
110 "updated TIMESTAMP NOT NULL, "
111 "subscriber_id NUMERIC NOT NULL, "
112 "equipment_id NUMERIC NOT NULL, "
113 "UNIQUE (subscriber_id, equipment_id) "
114 ")",
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +0100115 [SCHEMA_SMS] = "CREATE TABLE IF NOT EXISTS SMS ("
Harald Welte76042182009-08-08 16:03:15 +0200116 /* metadata, not part of sms */
Harald Welte7e310b12009-03-30 20:56:32 +0000117 "id INTEGER PRIMARY KEY AUTOINCREMENT, "
118 "created TIMESTAMP NOT NULL, "
119 "sent TIMESTAMP, "
Harald Welte (local)db552c52009-08-15 20:15:14 +0200120 "deliver_attempts INTEGER NOT NULL DEFAULT 0, "
Harald Welte76042182009-08-08 16:03:15 +0200121 /* data directly copied/derived from SMS */
Harald Weltef3efc592009-07-27 20:11:35 +0200122 "valid_until TIMESTAMP, "
Harald Welte76042182009-08-08 16:03:15 +0200123 "reply_path_req INTEGER NOT NULL, "
124 "status_rep_req INTEGER NOT NULL, "
125 "protocol_id INTEGER NOT NULL, "
126 "data_coding_scheme INTEGER NOT NULL, "
Harald Welted0b7b772009-08-09 19:03:42 +0200127 "ud_hdr_ind INTEGER NOT NULL, "
Holger Hans Peter Freytherca3c2562013-10-08 03:17:30 +0200128 "src_addr TEXT NOT NULL, "
129 "src_ton INTEGER NOT NULL, "
130 "src_npi INTEGER NOT NULL, "
131 "dest_addr TEXT NOT NULL, "
132 "dest_ton INTEGER NOT NULL, "
133 "dest_npi INTEGER NOT NULL, "
Harald Welte76042182009-08-08 16:03:15 +0200134 "user_data BLOB, " /* TP-UD */
135 /* additional data, interpreted from SMS */
136 "header BLOB, " /* UD Header */
137 "text TEXT " /* decoded UD after UDH */
Harald Welte7e310b12009-03-30 20:56:32 +0000138 ")",
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +0100139 [SCHEMA_VLR] = "CREATE TABLE IF NOT EXISTS VLR ("
Holger Freytherc2995ea2009-04-19 06:35:23 +0000140 "id INTEGER PRIMARY KEY AUTOINCREMENT, "
141 "created TIMESTAMP NOT NULL, "
142 "updated TIMESTAMP NOT NULL, "
143 "subscriber_id NUMERIC UNIQUE NOT NULL, "
144 "last_bts NUMERIC NOT NULL "
145 ")",
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +0100146 [SCHEMA_APDU] = "CREATE TABLE IF NOT EXISTS ApduBlobs ("
Harald Welte (local)026531e2009-08-16 10:40:10 +0200147 "id INTEGER PRIMARY KEY AUTOINCREMENT, "
148 "created TIMESTAMP NOT NULL, "
149 "apdu_id_flags INTEGER NOT NULL, "
150 "subscriber_id INTEGER NOT NULL, "
151 "apdu BLOB "
152 ")",
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +0100153 [SCHEMA_COUNTERS] = "CREATE TABLE IF NOT EXISTS Counters ("
Harald Welteffa55a42009-12-22 19:07:32 +0100154 "id INTEGER PRIMARY KEY AUTOINCREMENT, "
155 "timestamp TIMESTAMP NOT NULL, "
Harald Weltef9a43c42009-12-22 21:40:42 +0100156 "value INTEGER NOT NULL, "
157 "name TEXT NOT NULL "
Harald Welte09f7ad02009-12-24 09:42:07 +0100158 ")",
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +0100159 [SCHEMA_RATE] = "CREATE TABLE IF NOT EXISTS RateCounters ("
Harald Weltec1919862010-05-13 12:55:20 +0200160 "id INTEGER PRIMARY KEY AUTOINCREMENT, "
161 "timestamp TIMESTAMP NOT NULL, "
162 "value INTEGER NOT NULL, "
163 "name TEXT NOT NULL, "
Harald Welted94d6a02010-05-14 17:38:47 +0200164 "idx INTEGER NOT NULL "
Harald Weltec1919862010-05-13 12:55:20 +0200165 ")",
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +0100166 [SCHEMA_AUTHKEY] = "CREATE TABLE IF NOT EXISTS AuthKeys ("
Sylvain Munaut10bf8122010-06-09 11:31:32 +0200167 "subscriber_id INTEGER PRIMARY KEY, "
Sylvain Munaut77d334a2009-12-27 19:26:12 +0100168 "algorithm_id INTEGER NOT NULL, "
Harald Welte3606cc52009-12-05 15:13:22 +0530169 "a3a8_ki BLOB "
170 ")",
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +0100171 [SCHEMA_AUTHLAST] = "CREATE TABLE IF NOT EXISTS AuthLastTuples ("
Sylvain Munaut10bf8122010-06-09 11:31:32 +0200172 "subscriber_id INTEGER PRIMARY KEY, "
Sylvain Munaut70881b72009-12-27 15:41:59 +0100173 "issued TIMESTAMP NOT NULL, "
174 "use_count INTEGER NOT NULL DEFAULT 0, "
175 "key_seq INTEGER NOT NULL, "
176 "rand BLOB NOT NULL, "
177 "sres BLOB NOT NULL, "
178 "kc BLOB NOT NULL "
Harald Welteffa55a42009-12-22 19:07:32 +0100179 ")",
Harald Welte7e310b12009-03-30 20:56:32 +0000180};
181
Harald Welte0b906d02009-12-24 11:21:42 +0100182void db_error_func(dbi_conn conn, void *data)
183{
184 const char *msg;
Jan Luebbe5c15c852008-12-27 15:59:25 +0000185 dbi_conn_error(conn, &msg);
Harald Welteae1f1592009-12-24 11:39:14 +0100186 LOGP(DDB, LOGL_ERROR, "DBI: %s\n", msg);
Harald Weltec7548a12014-07-10 20:18:15 +0200187 osmo_log_backtrace(DDB, LOGL_ERROR);
Jan Luebbe7398eb92008-12-27 00:45:41 +0000188}
189
Jan Luebbebfbdeec2012-12-27 00:27:16 +0100190static int update_db_revision_2(void)
191{
192 dbi_result result;
193
194 result = dbi_conn_query(conn,
195 "ALTER TABLE Subscriber "
196 "ADD COLUMN expire_lu "
197 "TIMESTAMP DEFAULT NULL");
198 if (!result) {
199 LOGP(DDB, LOGL_ERROR,
Alexander Chemeris7e20f642014-03-07 16:59:53 +0100200 "Failed to alter table Subscriber (upgrade from rev 2).\n");
Jan Luebbebfbdeec2012-12-27 00:27:16 +0100201 return -EINVAL;
202 }
203 dbi_result_free(result);
204
205 result = dbi_conn_query(conn,
206 "UPDATE Meta "
207 "SET value = '3' "
208 "WHERE key = 'revision'");
209 if (!result) {
210 LOGP(DDB, LOGL_ERROR,
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +0100211 "Failed to update DB schema revision (upgrade from rev 2).\n");
Jan Luebbebfbdeec2012-12-27 00:27:16 +0100212 return -EINVAL;
213 }
214 dbi_result_free(result);
215
216 return 0;
217}
218
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +0100219/**
220 * Copied from the normal sms_from_result_v3 to avoid having
221 * to make sure that the real routine will remain backward
222 * compatible.
223 */
224static struct gsm_sms *sms_from_result_v3(dbi_result result)
225{
226 struct gsm_sms *sms = sms_alloc();
227 long long unsigned int sender_id;
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +0100228 const char *text, *daddr;
229 const unsigned char *user_data;
230 char buf[32];
Harald Welte2483f1b2016-06-19 18:06:02 +0200231 char *quoted;
232 dbi_result result2;
233 const char *extension;
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +0100234
235 if (!sms)
236 return NULL;
237
238 sms->id = dbi_result_get_ulonglong(result, "id");
239
Harald Welte2483f1b2016-06-19 18:06:02 +0200240 /* find extension by id, assuming that the subscriber still exists in
241 * the db */
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +0100242 sender_id = dbi_result_get_ulonglong(result, "sender_id");
243 snprintf(buf, sizeof(buf), "%llu", sender_id);
Harald Welte2483f1b2016-06-19 18:06:02 +0200244
245 dbi_conn_quote_string_copy(conn, buf, &quoted);
246 result2 = dbi_conn_queryf(conn,
247 "SELECT extension FROM Subscriber "
248 "WHERE id = %s ", quoted);
249 free(quoted);
250 extension = dbi_result_get_string(result2, "extension");
251 if (extension)
252 osmo_strlcpy(sms->src.addr, extension, sizeof(sms->src.addr));
253 dbi_result_free(result2);
254 /* got the extension */
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +0100255
Holger Hans Peter Freytherb115cb62014-07-03 14:00:30 +0200256 sms->reply_path_req = dbi_result_get_ulonglong(result, "reply_path_req");
257 sms->status_rep_req = dbi_result_get_ulonglong(result, "status_rep_req");
258 sms->ud_hdr_ind = dbi_result_get_ulonglong(result, "ud_hdr_ind");
259 sms->protocol_id = dbi_result_get_ulonglong(result, "protocol_id");
260 sms->data_coding_scheme = dbi_result_get_ulonglong(result,
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +0100261 "data_coding_scheme");
262
263 daddr = dbi_result_get_string(result, "dest_addr");
Neels Hofmeyr93bafb62017-01-13 03:12:08 +0100264 if (daddr)
265 osmo_strlcpy(sms->dst.addr, daddr, sizeof(sms->dst.addr));
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +0100266
267 sms->user_data_len = dbi_result_get_field_length(result, "user_data");
268 user_data = dbi_result_get_binary(result, "user_data");
269 if (sms->user_data_len > sizeof(sms->user_data))
270 sms->user_data_len = (uint8_t) sizeof(sms->user_data);
271 memcpy(sms->user_data, user_data, sms->user_data_len);
272
273 text = dbi_result_get_string(result, "text");
Neels Hofmeyr93bafb62017-01-13 03:12:08 +0100274 if (text)
275 osmo_strlcpy(sms->text, text, sizeof(sms->text));
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +0100276 return sms;
277}
278
279static int update_db_revision_3(void)
280{
281 dbi_result result;
282 struct gsm_sms *sms;
283
Holger Hans Peter Freyther61144012014-03-08 16:41:37 +0100284 LOGP(DDB, LOGL_NOTICE, "Going to migrate from revision 3\n");
285
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +0100286 result = dbi_conn_query(conn, "BEGIN EXCLUSIVE TRANSACTION");
287 if (!result) {
288 LOGP(DDB, LOGL_ERROR,
289 "Failed to begin transaction (upgrade from rev 3)\n");
290 return -EINVAL;
291 }
292 dbi_result_free(result);
293
294 /* Rename old SMS table to be able create a new one */
295 result = dbi_conn_query(conn, "ALTER TABLE SMS RENAME TO SMS_3");
296 if (!result) {
297 LOGP(DDB, LOGL_ERROR,
298 "Failed to rename the old SMS table (upgrade from rev 3).\n");
299 goto rollback;
300 }
301 dbi_result_free(result);
302
303 /* Create new SMS table with all the bells and whistles! */
304 result = dbi_conn_query(conn, create_stmts[SCHEMA_SMS]);
305 if (!result) {
306 LOGP(DDB, LOGL_ERROR,
307 "Failed to create a new SMS table (upgrade from rev 3).\n");
308 goto rollback;
309 }
310 dbi_result_free(result);
311
312 /* Cycle through old messages and convert them to the new format */
Max5c06e402015-07-29 20:20:28 +0200313 result = dbi_conn_query(conn, "SELECT * FROM SMS_3");
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +0100314 if (!result) {
315 LOGP(DDB, LOGL_ERROR,
316 "Failed fetch messages from the old SMS table (upgrade from rev 3).\n");
317 goto rollback;
318 }
319 while (dbi_result_next_row(result)) {
320 sms = sms_from_result_v3(result);
321 if (db_sms_store(sms) != 0) {
322 LOGP(DDB, LOGL_ERROR, "Failed to store message to the new SMS table(upgrade from rev 3).\n");
323 sms_free(sms);
324 dbi_result_free(result);
325 goto rollback;
326 }
327 sms_free(sms);
328 }
329 dbi_result_free(result);
330
331 /* Remove the temporary table */
332 result = dbi_conn_query(conn, "DROP TABLE SMS_3");
333 if (!result) {
334 LOGP(DDB, LOGL_ERROR,
335 "Failed to drop the old SMS table (upgrade from rev 3).\n");
336 goto rollback;
337 }
338 dbi_result_free(result);
339
340 /* We're done. Bump DB Meta revision to 4 */
341 result = dbi_conn_query(conn,
342 "UPDATE Meta "
343 "SET value = '4' "
344 "WHERE key = 'revision'");
345 if (!result) {
346 LOGP(DDB, LOGL_ERROR,
347 "Failed to update DB schema revision (upgrade from rev 3).\n");
348 goto rollback;
349 }
350 dbi_result_free(result);
351
352 result = dbi_conn_query(conn, "COMMIT TRANSACTION");
353 if (!result) {
354 LOGP(DDB, LOGL_ERROR,
355 "Failed to commit the transaction (upgrade from rev 3)\n");
356 return -EINVAL;
Alexander Couzensf480b352017-02-04 00:20:17 +0100357 } else {
358 dbi_result_free(result);
Holger Hans Peter Freythere7cc9aa2014-03-07 18:17:22 +0100359 }
360
361 /* Shrink DB file size by actually wiping out SMS_3 table data */
362 result = dbi_conn_query(conn, "VACUUM");
363 if (!result)
364 LOGP(DDB, LOGL_ERROR,
365 "VACUUM failed. Ignoring it (upgrade from rev 3).\n");
366 else
367 dbi_result_free(result);
368
369 return 0;
370
371rollback:
372 result = dbi_conn_query(conn, "ROLLBACK TRANSACTION");
373 if (!result)
374 LOGP(DDB, LOGL_ERROR,
375 "Rollback failed (upgrade from rev 3).\n");
376 else
377 dbi_result_free(result);
378 return -EINVAL;
379}
380
Harald Welted0b7b772009-08-09 19:03:42 +0200381static int check_db_revision(void)
382{
383 dbi_result result;
Jan Luebbebfbdeec2012-12-27 00:27:16 +0100384 const char *rev_s;
Vadim Yanitskiya8d8e932016-05-13 15:38:09 +0600385 int db_rev = 0;
Harald Welted0b7b772009-08-09 19:03:42 +0200386
Vadim Yanitskiya8d8e932016-05-13 15:38:09 +0600387 /* Make a query */
Harald Welted0b7b772009-08-09 19:03:42 +0200388 result = dbi_conn_query(conn,
Vadim Yanitskiya8d8e932016-05-13 15:38:09 +0600389 "SELECT value FROM Meta "
390 "WHERE key = 'revision'");
391
Harald Welted0b7b772009-08-09 19:03:42 +0200392 if (!result)
393 return -EINVAL;
394
395 if (!dbi_result_next_row(result)) {
396 dbi_result_free(result);
397 return -EINVAL;
398 }
Vadim Yanitskiya8d8e932016-05-13 15:38:09 +0600399
400 /* Fetch the DB schema revision */
Jan Luebbebfbdeec2012-12-27 00:27:16 +0100401 rev_s = dbi_result_get_string(result, "value");
402 if (!rev_s) {
Harald Welted0b7b772009-08-09 19:03:42 +0200403 dbi_result_free(result);
404 return -EINVAL;
405 }
Vadim Yanitskiya8d8e932016-05-13 15:38:09 +0600406
407 if (!strcmp(rev_s, SCHEMA_REVISION)) {
408 /* Everything is fine */
Jan Luebbebfbdeec2012-12-27 00:27:16 +0100409 dbi_result_free(result);
Vadim Yanitskiya8d8e932016-05-13 15:38:09 +0600410 return 0;
411 }
412
413 db_rev = atoi(rev_s);
414 dbi_result_free(result);
415
416 /* Incremental migration waterfall */
417 switch (db_rev) {
418 case 2:
419 if (update_db_revision_2())
420 goto error;
421 case 3:
422 if (update_db_revision_3())
423 goto error;
424
425 /* The end of waterfall */
426 break;
427 default:
428 LOGP(DDB, LOGL_FATAL,
429 "Invalid database schema revision '%d'.\n", db_rev);
Jan Luebbebfbdeec2012-12-27 00:27:16 +0100430 return -EINVAL;
431 }
432
Jan Luebbebfbdeec2012-12-27 00:27:16 +0100433 return 0;
Vadim Yanitskiya8d8e932016-05-13 15:38:09 +0600434
435error:
436 LOGP(DDB, LOGL_FATAL, "Failed to update database "
437 "from schema revision '%d'.\n", db_rev);
438 return -EINVAL;
Jan Luebbebfbdeec2012-12-27 00:27:16 +0100439}
440
441static int db_configure(void)
442{
443 dbi_result result;
444
445 result = dbi_conn_query(conn,
446 "PRAGMA synchronous = FULL");
447 if (!result)
448 return -EINVAL;
Harald Welted0b7b772009-08-09 19:03:42 +0200449
450 dbi_result_free(result);
451 return 0;
452}
453
Harald Welte0b906d02009-12-24 11:21:42 +0100454int db_init(const char *name)
455{
Jan Luebbe5c15c852008-12-27 15:59:25 +0000456 dbi_initialize(NULL);
Harald Welte0b906d02009-12-24 11:21:42 +0100457
Jan Luebbe5c15c852008-12-27 15:59:25 +0000458 conn = dbi_conn_new("sqlite3");
Harald Welte0b906d02009-12-24 11:21:42 +0100459 if (conn == NULL) {
Harald Welteae1f1592009-12-24 11:39:14 +0100460 LOGP(DDB, LOGL_FATAL, "Failed to create connection.\n");
Jan Luebbe5c15c852008-12-27 15:59:25 +0000461 return 1;
462 }
Jan Luebbe7398eb92008-12-27 00:45:41 +0000463
Holger Freyther12aa50d2009-01-01 18:02:05 +0000464 dbi_conn_error_handler( conn, db_error_func, NULL );
Jan Luebbe7398eb92008-12-27 00:45:41 +0000465
Jan Luebbe5c15c852008-12-27 15:59:25 +0000466 /* MySQL
467 dbi_conn_set_option(conn, "host", "localhost");
468 dbi_conn_set_option(conn, "username", "your_name");
469 dbi_conn_set_option(conn, "password", "your_password");
470 dbi_conn_set_option(conn, "dbname", "your_dbname");
471 dbi_conn_set_option(conn, "encoding", "UTF-8");
472 */
Jan Luebbe7398eb92008-12-27 00:45:41 +0000473
Jan Luebbe5c15c852008-12-27 15:59:25 +0000474 /* SqLite 3 */
Holger Freyther12aa50d2009-01-01 18:02:05 +0000475 db_basename = strdup(name);
476 db_dirname = strdup(name);
Holger Freytherbde36102008-12-28 22:51:39 +0000477 dbi_conn_set_option(conn, "sqlite3_dbdir", dirname(db_dirname));
478 dbi_conn_set_option(conn, "dbname", basename(db_basename));
Jan Luebbe7398eb92008-12-27 00:45:41 +0000479
Harald Welted0b7b772009-08-09 19:03:42 +0200480 if (dbi_conn_connect(conn) < 0)
481 goto out_err;
482
Jan Luebbe5c15c852008-12-27 15:59:25 +0000483 return 0;
Harald Welted0b7b772009-08-09 19:03:42 +0200484
485out_err:
486 free(db_dirname);
487 free(db_basename);
488 db_dirname = db_basename = NULL;
489 return -1;
Jan Luebbe7398eb92008-12-27 00:45:41 +0000490}
491
Harald Welted0b7b772009-08-09 19:03:42 +0200492
Harald Welted1476bc2011-07-16 13:24:09 +0200493int db_prepare(void)
Harald Welte0b906d02009-12-24 11:21:42 +0100494{
Jan Luebbe5c15c852008-12-27 15:59:25 +0000495 dbi_result result;
Harald Welte7e310b12009-03-30 20:56:32 +0000496 int i;
Holger Freytherb4064bc2009-02-23 00:50:31 +0000497
Harald Welte7e310b12009-03-30 20:56:32 +0000498 for (i = 0; i < ARRAY_SIZE(create_stmts); i++) {
499 result = dbi_conn_query(conn, create_stmts[i]);
Harald Welte0b906d02009-12-24 11:21:42 +0100500 if (!result) {
Harald Welteae1f1592009-12-24 11:39:14 +0100501 LOGP(DDB, LOGL_ERROR,
502 "Failed to create some table.\n");
Harald Welte7e310b12009-03-30 20:56:32 +0000503 return 1;
504 }
505 dbi_result_free(result);
Holger Freytherb4064bc2009-02-23 00:50:31 +0000506 }
Holger Freytherb4064bc2009-02-23 00:50:31 +0000507
Holger Hans Peter Freyther850326e2009-08-10 08:36:04 +0200508 if (check_db_revision() < 0) {
Harald Welteae1f1592009-12-24 11:39:14 +0100509 LOGP(DDB, LOGL_FATAL, "Database schema revision invalid, "
Holger Hans Peter Freyther850326e2009-08-10 08:36:04 +0200510 "please update your database schema\n");
511 return -1;
512 }
513
Jan Luebbebfbdeec2012-12-27 00:27:16 +0100514 db_configure();
515
Jan Luebbe5c15c852008-12-27 15:59:25 +0000516 return 0;
Jan Luebbe7398eb92008-12-27 00:45:41 +0000517}
518
Harald Welted1476bc2011-07-16 13:24:09 +0200519int db_fini(void)
Harald Welte0b906d02009-12-24 11:21:42 +0100520{
Jan Luebbe5c15c852008-12-27 15:59:25 +0000521 dbi_conn_close(conn);
522 dbi_shutdown();
Holger Freytherbde36102008-12-28 22:51:39 +0000523
Harald Welte2c5f4c62011-07-16 13:22:57 +0200524 free(db_dirname);
525 free(db_basename);
Jan Luebbe5c15c852008-12-27 15:59:25 +0000526 return 0;
Jan Luebbe7398eb92008-12-27 00:45:41 +0000527}
528
Harald Welte7e310b12009-03-30 20:56:32 +0000529/* store an [unsent] SMS to the database */
530int db_sms_store(struct gsm_sms *sms)
531{
532 dbi_result result;
Holger Hans Peter Freytherca3c2562013-10-08 03:17:30 +0200533 char *q_text, *q_daddr, *q_saddr;
Harald Welte76042182009-08-08 16:03:15 +0200534 unsigned char *q_udata;
535 char *validity_timestamp = "2222-2-2";
536
537 /* FIXME: generate validity timestamp based on validity_minutes */
Harald Welte7e310b12009-03-30 20:56:32 +0000538
539 dbi_conn_quote_string_copy(conn, (char *)sms->text, &q_text);
Harald Weltec0de14d2012-11-23 23:35:01 +0100540 dbi_conn_quote_string_copy(conn, (char *)sms->dst.addr, &q_daddr);
Holger Hans Peter Freytherca3c2562013-10-08 03:17:30 +0200541 dbi_conn_quote_string_copy(conn, (char *)sms->src.addr, &q_saddr);
Harald Welte76042182009-08-08 16:03:15 +0200542 dbi_conn_quote_binary_copy(conn, sms->user_data, sms->user_data_len,
543 &q_udata);
Holger Hans Peter Freytherca3c2562013-10-08 03:17:30 +0200544
Harald Weltef3efc592009-07-27 20:11:35 +0200545 /* FIXME: correct validity period */
Harald Welte7e310b12009-03-30 20:56:32 +0000546 result = dbi_conn_queryf(conn,
547 "INSERT INTO SMS "
Alexander Chemerisca7ed2d2013-10-08 03:17:32 +0200548 "(created, valid_until, "
Harald Welte76042182009-08-08 16:03:15 +0200549 "reply_path_req, status_rep_req, protocol_id, "
Holger Hans Peter Freytherca3c2562013-10-08 03:17:30 +0200550 "data_coding_scheme, ud_hdr_ind, "
551 "user_data, text, "
552 "dest_addr, dest_ton, dest_npi, "
553 "src_addr, src_ton, src_npi) VALUES "
Alexander Chemerisca7ed2d2013-10-08 03:17:32 +0200554 "(datetime('now'), %u, "
Holger Hans Peter Freytherca3c2562013-10-08 03:17:30 +0200555 "%u, %u, %u, "
556 "%u, %u, "
557 "%s, %s, "
558 "%s, %u, %u, "
559 "%s, %u, %u)",
Alexander Chemerisca7ed2d2013-10-08 03:17:32 +0200560 validity_timestamp,
Harald Welte76042182009-08-08 16:03:15 +0200561 sms->reply_path_req, sms->status_rep_req, sms->protocol_id,
Harald Welted0b7b772009-08-09 19:03:42 +0200562 sms->data_coding_scheme, sms->ud_hdr_ind,
Holger Hans Peter Freytherca3c2562013-10-08 03:17:30 +0200563 q_udata, q_text,
564 q_daddr, sms->dst.ton, sms->dst.npi,
565 q_saddr, sms->src.ton, sms->src.npi);
Harald Welte7e310b12009-03-30 20:56:32 +0000566 free(q_text);
Harald Welte76042182009-08-08 16:03:15 +0200567 free(q_udata);
Holger Hans Peter Freytherca3c2562013-10-08 03:17:30 +0200568 free(q_daddr);
569 free(q_saddr);
Harald Welte7e310b12009-03-30 20:56:32 +0000570
571 if (!result)
572 return -EIO;
573
574 dbi_result_free(result);
575 return 0;
576}
577
Harald Welte2ebabca2009-08-09 19:05:21 +0200578static struct gsm_sms *sms_from_result(struct gsm_network *net, dbi_result result)
Harald Welte7e310b12009-03-30 20:56:32 +0000579{
Harald Welte76042182009-08-08 16:03:15 +0200580 struct gsm_sms *sms = sms_alloc();
Holger Hans Peter Freytherca3c2562013-10-08 03:17:30 +0200581 const char *text, *daddr, *saddr;
Harald Welte76042182009-08-08 16:03:15 +0200582 const unsigned char *user_data;
Harald Welte7e310b12009-03-30 20:56:32 +0000583
Harald Welte76042182009-08-08 16:03:15 +0200584 if (!sms)
Harald Welte7e310b12009-03-30 20:56:32 +0000585 return NULL;
Harald Welte7e310b12009-03-30 20:56:32 +0000586
Harald Weltebe3e3782009-07-05 14:06:41 +0200587 sms->id = dbi_result_get_ulonglong(result, "id");
Harald Welte7e310b12009-03-30 20:56:32 +0000588
Harald Weltef3efc592009-07-27 20:11:35 +0200589 /* FIXME: validity */
Harald Welte76042182009-08-08 16:03:15 +0200590 /* FIXME: those should all be get_uchar, but sqlite3 is braindead */
Holger Hans Peter Freytherb115cb62014-07-03 14:00:30 +0200591 sms->reply_path_req = dbi_result_get_ulonglong(result, "reply_path_req");
592 sms->status_rep_req = dbi_result_get_ulonglong(result, "status_rep_req");
593 sms->ud_hdr_ind = dbi_result_get_ulonglong(result, "ud_hdr_ind");
594 sms->protocol_id = dbi_result_get_ulonglong(result, "protocol_id");
595 sms->data_coding_scheme = dbi_result_get_ulonglong(result,
Harald Weltef3efc592009-07-27 20:11:35 +0200596 "data_coding_scheme");
Harald Welte76042182009-08-08 16:03:15 +0200597 /* sms->msg_ref is temporary and not stored in DB */
Harald Weltef3efc592009-07-27 20:11:35 +0200598
Holger Hans Peter Freytherb115cb62014-07-03 14:00:30 +0200599 sms->dst.npi = dbi_result_get_ulonglong(result, "dest_npi");
600 sms->dst.ton = dbi_result_get_ulonglong(result, "dest_ton");
Harald Welte76042182009-08-08 16:03:15 +0200601 daddr = dbi_result_get_string(result, "dest_addr");
Neels Hofmeyr93bafb62017-01-13 03:12:08 +0100602 if (daddr)
603 osmo_strlcpy(sms->dst.addr, daddr, sizeof(sms->dst.addr));
Harald Welte2483f1b2016-06-19 18:06:02 +0200604 sms->receiver = vlr_subscr_find_by_msisdn(net->vlr, sms->dst.addr);
Harald Welte76042182009-08-08 16:03:15 +0200605
Holger Hans Peter Freytherb115cb62014-07-03 14:00:30 +0200606 sms->src.npi = dbi_result_get_ulonglong(result, "src_npi");
607 sms->src.ton = dbi_result_get_ulonglong(result, "src_ton");
Holger Hans Peter Freytherca3c2562013-10-08 03:17:30 +0200608 saddr = dbi_result_get_string(result, "src_addr");
Neels Hofmeyr93bafb62017-01-13 03:12:08 +0100609 if (saddr)
610 osmo_strlcpy(sms->src.addr, saddr, sizeof(sms->src.addr));
Holger Hans Peter Freytherca3c2562013-10-08 03:17:30 +0200611
Harald Welte76042182009-08-08 16:03:15 +0200612 sms->user_data_len = dbi_result_get_field_length(result, "user_data");
613 user_data = dbi_result_get_binary(result, "user_data");
614 if (sms->user_data_len > sizeof(sms->user_data))
Holger Hans Peter Freytherc42ad8b2011-04-18 17:04:00 +0200615 sms->user_data_len = (uint8_t) sizeof(sms->user_data);
Harald Welte76042182009-08-08 16:03:15 +0200616 memcpy(sms->user_data, user_data, sms->user_data_len);
Harald Weltebe3e3782009-07-05 14:06:41 +0200617
618 text = dbi_result_get_string(result, "text");
Neels Hofmeyr93bafb62017-01-13 03:12:08 +0100619 if (text)
620 osmo_strlcpy(sms->text, text, sizeof(sms->text));
Harald Welte2ebabca2009-08-09 19:05:21 +0200621 return sms;
622}
623
Holger Hans Peter Freyther812dad02010-12-24 23:18:31 +0100624struct gsm_sms *db_sms_get(struct gsm_network *net, unsigned long long id)
625{
626 dbi_result result;
627 struct gsm_sms *sms;
628
629 result = dbi_conn_queryf(conn,
630 "SELECT * FROM SMS WHERE SMS.id = %llu", id);
631 if (!result)
632 return NULL;
633
634 if (!dbi_result_next_row(result)) {
635 dbi_result_free(result);
636 return NULL;
637 }
638
639 sms = sms_from_result(net, result);
640
641 dbi_result_free(result);
642
643 return sms;
644}
645
Harald Welte2483f1b2016-06-19 18:06:02 +0200646struct gsm_sms *db_sms_get_next_unsent(struct gsm_network *net,
647 unsigned long long min_sms_id,
648 unsigned int max_failed)
Harald Welte2ebabca2009-08-09 19:05:21 +0200649{
650 dbi_result result;
651 struct gsm_sms *sms;
652
653 result = dbi_conn_queryf(conn,
Harald Welte2483f1b2016-06-19 18:06:02 +0200654 "SELECT * FROM SMS"
655 " WHERE sent IS NULL"
656 " AND id >= %llu"
657 " AND deliver_attempts <= %u"
658 " ORDER BY id LIMIT 1",
659 min_sms_id, max_failed);
Harald Welte2ebabca2009-08-09 19:05:21 +0200660
Sylvain Munautff1f19e2009-12-22 13:22:29 +0100661 if (!result)
662 return NULL;
663
664 if (!dbi_result_next_row(result)) {
665 dbi_result_free(result);
666 return NULL;
667 }
668
669 sms = sms_from_result(net, result);
670
671 dbi_result_free(result);
672
673 return sms;
674}
675
Sylvain Munautd5778fc2009-12-21 01:09:57 +0100676/* retrieve the next unsent SMS for a given subscriber */
Harald Welte2483f1b2016-06-19 18:06:02 +0200677struct gsm_sms *db_sms_get_unsent_for_subscr(struct vlr_subscr *vsub,
678 unsigned int max_failed)
Harald Welte2ebabca2009-08-09 19:05:21 +0200679{
Harald Welte2483f1b2016-06-19 18:06:02 +0200680 struct gsm_network *net = vsub->vlr->user_ctx;
Harald Welte2ebabca2009-08-09 19:05:21 +0200681 dbi_result result;
682 struct gsm_sms *sms;
683
Harald Welte2483f1b2016-06-19 18:06:02 +0200684 if (!vsub->lu_complete)
685 return NULL;
686
Harald Welte2ebabca2009-08-09 19:05:21 +0200687 result = dbi_conn_queryf(conn,
Harald Welte2483f1b2016-06-19 18:06:02 +0200688 "SELECT * FROM SMS"
689 " WHERE sent IS NULL"
690 " AND dest_addr=%s"
691 " AND deliver_attempts <= %u"
692 " ORDER BY id LIMIT 1",
693 vsub->msisdn, max_failed);
Harald Welte2ebabca2009-08-09 19:05:21 +0200694 if (!result)
695 return NULL;
696
697 if (!dbi_result_next_row(result)) {
698 dbi_result_free(result);
699 return NULL;
700 }
701
Harald Welte2483f1b2016-06-19 18:06:02 +0200702 sms = sms_from_result(net, result);
703
704 dbi_result_free(result);
705
706 return sms;
707}
708
709struct gsm_sms *db_sms_get_next_unsent_rr_msisdn(struct gsm_network *net,
710 const char *last_msisdn,
711 unsigned int max_failed)
712{
713 dbi_result result;
714 struct gsm_sms *sms;
715
716 result = dbi_conn_queryf(conn,
717 "SELECT * FROM SMS"
718 " WHERE sent IS NULL"
719 " AND dest_addr > '%s'"
720 " AND deliver_attempts <= %u"
721 " ORDER BY dest_addr, id LIMIT 1",
722 last_msisdn, max_failed);
723 if (!result)
724 return NULL;
725
726 if (!dbi_result_next_row(result)) {
727 dbi_result_free(result);
728 return NULL;
729 }
730
731 sms = sms_from_result(net, result);
Harald Welte2ebabca2009-08-09 19:05:21 +0200732
733 dbi_result_free(result);
734
Harald Welte7e310b12009-03-30 20:56:32 +0000735 return sms;
736}
737
Alexander Chemeris1e77e3d2014-03-08 21:27:37 +0100738/* mark a given SMS as delivered */
739int db_sms_mark_delivered(struct gsm_sms *sms)
Harald Welte7e310b12009-03-30 20:56:32 +0000740{
741 dbi_result result;
742
743 result = dbi_conn_queryf(conn,
744 "UPDATE SMS "
745 "SET sent = datetime('now') "
746 "WHERE id = %llu", sms->id);
747 if (!result) {
Harald Welteae1f1592009-12-24 11:39:14 +0100748 LOGP(DDB, LOGL_ERROR, "Failed to mark SMS %llu as sent.\n", sms->id);
Harald Welte7e310b12009-03-30 20:56:32 +0000749 return 1;
750 }
751
752 dbi_result_free(result);
753 return 0;
754}
Harald Welte (local)db552c52009-08-15 20:15:14 +0200755
756/* increase the number of attempted deliveries */
757int db_sms_inc_deliver_attempts(struct gsm_sms *sms)
758{
759 dbi_result result;
760
761 result = dbi_conn_queryf(conn,
762 "UPDATE SMS "
763 "SET deliver_attempts = deliver_attempts + 1 "
764 "WHERE id = %llu", sms->id);
765 if (!result) {
Harald Welteae1f1592009-12-24 11:39:14 +0100766 LOGP(DDB, LOGL_ERROR, "Failed to inc deliver attempts for "
767 "SMS %llu.\n", sms->id);
Harald Welte (local)db552c52009-08-15 20:15:14 +0200768 return 1;
769 }
770
771 dbi_result_free(result);
772 return 0;
773}
Harald Welte (local)026531e2009-08-16 10:40:10 +0200774
Harald Welte2483f1b2016-06-19 18:06:02 +0200775/* Drop all pending SMS to or from the given extension */
776int db_sms_delete_by_msisdn(const char *msisdn)
Harald Welte (local)026531e2009-08-16 10:40:10 +0200777{
778 dbi_result result;
Harald Welte2483f1b2016-06-19 18:06:02 +0200779 if (!msisdn || !*msisdn)
780 return 0;
Harald Welte (local)026531e2009-08-16 10:40:10 +0200781 result = dbi_conn_queryf(conn,
Harald Welte2483f1b2016-06-19 18:06:02 +0200782 "DELETE FROM SMS WHERE src_addr=%s OR dest_addr=%s",
783 msisdn, msisdn);
784 if (!result) {
785 LOGP(DDB, LOGL_ERROR,
786 "Failed to delete SMS for %s\n", msisdn);
787 return -1;
788 }
Harald Welte (local)026531e2009-08-16 10:40:10 +0200789 dbi_result_free(result);
790 return 0;
791}
Harald Welteffa55a42009-12-22 19:07:32 +0100792
Pablo Neira Ayusodfb342c2011-05-06 12:13:10 +0200793int db_store_counter(struct osmo_counter *ctr)
Harald Welteffa55a42009-12-22 19:07:32 +0100794{
795 dbi_result result;
796 char *q_name;
797
798 dbi_conn_quote_string_copy(conn, ctr->name, &q_name);
799
800 result = dbi_conn_queryf(conn,
801 "INSERT INTO Counters "
802 "(timestamp,name,value) VALUES "
803 "(datetime('now'),%s,%lu)", q_name, ctr->value);
804
805 free(q_name);
806
807 if (!result)
808 return -EIO;
809
810 dbi_result_free(result);
811 return 0;
812}
Harald Weltef2b4cd72010-05-13 11:45:07 +0200813
814static int db_store_rate_ctr(struct rate_ctr_group *ctrg, unsigned int num,
815 char *q_prefix)
816{
817 dbi_result result;
818 char *q_name;
819
820 dbi_conn_quote_string_copy(conn, ctrg->desc->ctr_desc[num].name,
821 &q_name);
822
823 result = dbi_conn_queryf(conn,
Harald Weltec1919862010-05-13 12:55:20 +0200824 "Insert INTO RateCounters "
Harald Welted94d6a02010-05-14 17:38:47 +0200825 "(timestamp,name,idx,value) VALUES "
Harald Weltec1919862010-05-13 12:55:20 +0200826 "(datetime('now'),%s.%s,%u,%"PRIu64")",
827 q_prefix, q_name, ctrg->idx, ctrg->ctr[num].current);
Harald Weltef2b4cd72010-05-13 11:45:07 +0200828
829 free(q_name);
830
831 if (!result)
832 return -EIO;
833
834 dbi_result_free(result);
835 return 0;
836}
837
838int db_store_rate_ctr_group(struct rate_ctr_group *ctrg)
839{
840 unsigned int i;
841 char *q_prefix;
842
Harald Weltec1919862010-05-13 12:55:20 +0200843 dbi_conn_quote_string_copy(conn, ctrg->desc->group_name_prefix, &q_prefix);
Harald Weltef2b4cd72010-05-13 11:45:07 +0200844
845 for (i = 0; i < ctrg->desc->num_ctr; i++)
846 db_store_rate_ctr(ctrg, i, q_prefix);
847
848 free(q_prefix);
849
850 return 0;
851}