blob: 5b091f8209786e04734114db9c88f9e1a4181ca8 [file] [log] [blame]
Max372868b2017-03-02 12:12:00 +01001/* OsmoHLR Control Interface implementation */
2
3/* (C) 2017 sysmocom s.f.m.c. GmbH <info@sysmocom.de>
4 * All Rights Reserved
5 *
6 * Author: Max Suraev <msuraev@sysmocom.de>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU Affero General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Affero General Public License for more details.
17 *
18 * You should have received a copy of the GNU Affero General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 *
21 */
22
23#include <stdbool.h>
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +020024#include <errno.h>
25#include <inttypes.h>
26#include <string.h>
Max372868b2017-03-02 12:12:00 +010027
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +020028#include <osmocom/gsm/gsm23003.h>
Max372868b2017-03-02 12:12:00 +010029#include <osmocom/ctrl/ports.h>
30
Neels Hofmeyr2f758032019-11-20 00:37:07 +010031#include <osmocom/hlr/hlr.h>
32#include <osmocom/hlr/ctrl.h>
33#include <osmocom/hlr/db.h>
Max372868b2017-03-02 12:12:00 +010034
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +020035#define SEL_BY "by-"
36#define SEL_BY_IMSI SEL_BY "imsi-"
37#define SEL_BY_MSISDN SEL_BY "msisdn-"
38#define SEL_BY_ID SEL_BY "id-"
39
40#define hexdump_buf(buf) osmo_hexdump_nospc((void*)buf, sizeof(buf))
41
42static bool startswith(const char *str, const char *start)
43{
44 return strncmp(str, start, strlen(start)) == 0;
45}
46
47static int _get_subscriber(struct db_context *dbc,
48 const char *by_selector,
49 struct hlr_subscriber *subscr)
50{
51 const char *val;
52 if (startswith(by_selector, SEL_BY_IMSI)) {
53 val = by_selector + strlen(SEL_BY_IMSI);
54 if (!osmo_imsi_str_valid(val))
55 return -EINVAL;
56 return db_subscr_get_by_imsi(dbc, val, subscr);
57 }
58 if (startswith(by_selector, SEL_BY_MSISDN)) {
59 val = by_selector + strlen(SEL_BY_MSISDN);
60 if (!osmo_msisdn_str_valid(val))
61 return -EINVAL;
62 return db_subscr_get_by_msisdn(dbc, val, subscr);
63 }
64 if (startswith(by_selector, SEL_BY_ID)) {
65 int64_t id;
66 char *endptr;
67 val = by_selector + strlen(SEL_BY_ID);
68 if (*val == '+')
69 return -EINVAL;
70 errno = 0;
71 id = strtoll(val, &endptr, 10);
72 if (errno || *endptr)
73 return -EINVAL;
74 return db_subscr_get_by_id(dbc, id, subscr);
75 }
76 return -ENOTSUP;
77}
78
79static bool get_subscriber(struct db_context *dbc,
80 const char *by_selector,
81 struct hlr_subscriber *subscr,
82 struct ctrl_cmd *cmd)
83{
84 int rc = _get_subscriber(dbc, by_selector, subscr);
85 switch (rc) {
86 case 0:
87 return true;
88 case -ENOTSUP:
89 cmd->reply = "Not a known subscriber 'by-xxx-' selector.";
90 return false;
91 case -EINVAL:
92 cmd->reply = "Invalid value part of 'by-xxx-value' selector.";
93 return false;
94 case -ENOENT:
95 cmd->reply = "No such subscriber.";
96 return false;
97 default:
Thorsten Alteholzb07f33d2019-07-16 21:21:36 +020098 cmd->reply = "An unknown error has occurred during get_subscriber().";
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +020099 return false;
100 }
101}
102
103/* Optimization: if a subscriber operation is requested by-imsi, just return
104 * the IMSI right back. */
105static const char *get_subscriber_imsi(struct db_context *dbc,
106 const char *by_selector,
107 struct ctrl_cmd *cmd)
108{
109 static struct hlr_subscriber subscr;
110
111 if (startswith(by_selector, SEL_BY_IMSI))
112 return by_selector + strlen(SEL_BY_IMSI);
113 if (!get_subscriber(dbc, by_selector, &subscr, cmd))
114 return NULL;
115 return subscr.imsi;
116}
117
118/* printf fmt and arg to completely omit a string if it is empty. */
119#define FMT_S "%s%s%s%s"
120#define ARG_S(name, val) \
121 (val) && *(val) ? "\n" : "", \
122 (val) && *(val) ? name : "", \
123 (val) && *(val) ? "\t" : "", \
124 (val) && *(val) ? (val) : "" \
125
126/* printf fmt and arg to completely omit bool of given value. */
127#define FMT_BOOL "%s"
128#define ARG_BOOL(name, val) \
129 val ? "\n" name "\t1" : "\n" name "\t0"
130
131static void print_subscr_info(struct ctrl_cmd *cmd,
132 struct hlr_subscriber *subscr)
133{
134 ctrl_cmd_reply_printf(cmd,
Stefan Sperling705b61b2018-12-07 12:44:50 +0100135 "\nid\t%" PRIu64
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +0200136 FMT_S
137 FMT_S
138 FMT_BOOL
139 FMT_BOOL
140 FMT_S
141 FMT_S
142 FMT_S
143 FMT_BOOL
144 FMT_BOOL
145 "\nperiodic_lu_timer\t%u"
146 "\nperiodic_rau_tau_timer\t%u"
147 "\nlmsi\t%08x"
148 ,
149 subscr->id,
150 ARG_S("imsi", subscr->imsi),
151 ARG_S("msisdn", subscr->msisdn),
152 ARG_BOOL("nam_cs", subscr->nam_cs),
153 ARG_BOOL("nam_ps", subscr->nam_ps),
154 ARG_S("vlr_number", subscr->vlr_number),
155 ARG_S("sgsn_number", subscr->sgsn_number),
156 ARG_S("sgsn_address", subscr->sgsn_address),
157 ARG_BOOL("ms_purged_cs", subscr->ms_purged_cs),
158 ARG_BOOL("ms_purged_ps", subscr->ms_purged_ps),
159 subscr->periodic_lu_timer,
160 subscr->periodic_rau_tau_timer,
161 subscr->lmsi
162 );
163}
164
165static void print_subscr_info_aud2g(struct ctrl_cmd *cmd, struct osmo_sub_auth_data *aud)
166{
167 if (aud->algo == OSMO_AUTH_ALG_NONE)
168 return;
169 ctrl_cmd_reply_printf(cmd,
170 "\naud2g.algo\t%s"
171 "\naud2g.ki\t%s"
172 ,
173 osmo_auth_alg_name(aud->algo),
174 hexdump_buf(aud->u.gsm.ki));
175}
176
177static void print_subscr_info_aud3g(struct ctrl_cmd *cmd, struct osmo_sub_auth_data *aud)
178{
179 if (aud->algo == OSMO_AUTH_ALG_NONE)
180 return;
181 ctrl_cmd_reply_printf(cmd,
182 "\naud3g.algo\t%s"
183 "\naud3g.k\t%s"
184 ,
185 osmo_auth_alg_name(aud->algo),
186 hexdump_buf(aud->u.umts.k));
187 /* hexdump uses a static string buffer, hence only one hexdump per
188 * printf(). */
189 ctrl_cmd_reply_printf(cmd,
190 "\naud3g.%s\t%s"
191 "\naud3g.ind_bitlen\t%u"
Stefan Sperling705b61b2018-12-07 12:44:50 +0100192 "\naud3g.sqn\t%" PRIu64
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +0200193 ,
194 aud->u.umts.opc_is_op? "op" : "opc",
195 hexdump_buf(aud->u.umts.opc),
196 aud->u.umts.ind_bitlen,
197 aud->u.umts.sqn);
198}
199
Pau Espin Pedrold63ec882022-06-17 17:54:51 +0200200CTRL_CMD_DEFINE_WO_NOVRF(subscr_create, "create");
201static int set_subscr_create(struct ctrl_cmd *cmd, void *data)
202{
203 struct hlr_subscriber subscr;
204 struct hlr *hlr = data;
205 const char *imsi = cmd->value;
206 int rc;
207
208 if (!osmo_imsi_str_valid(imsi)) {
209 cmd->reply = "Invalid IMSI value.";
210 return CTRL_CMD_ERROR;
211 }
212
213 /* Create the subscriber in the DB */
214 rc = db_subscr_create(g_hlr->dbc, imsi, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS);
215 if (rc) {
216 if (rc == -EEXIST)
217 cmd->reply = "Subscriber already exists.";
218 else
219 cmd->reply = "Cannot create subscriber.";
220 return CTRL_CMD_ERROR;
221 }
222
223 LOGP(DCTRL, LOGL_INFO, "Created subscriber IMSI='%s'\n",
224 imsi);
225
226 /* Retrieve data of newly created subscriber: */
227 rc = db_subscr_get_by_imsi(hlr->dbc, imsi, &subscr);
228 if (rc < 0) {
229 cmd->reply = "Failed retrieving ID of newly created subscriber.";
230 return CTRL_CMD_ERROR;
231 }
232
233 cmd->reply = talloc_asprintf(cmd, "%" PRIu64, subscr.id);
234 return CTRL_CMD_REPLY;
235}
236
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +0200237CTRL_CMD_DEFINE_RO(subscr_info, "info");
238static int get_subscr_info(struct ctrl_cmd *cmd, void *data)
Max9cacb6f2017-02-20 17:22:56 +0100239{
Neels Hofmeyr00b1d432017-10-17 01:43:48 +0200240 struct hlr_subscriber subscr;
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +0200241 struct hlr *hlr = data;
242 const char *by_selector = cmd->node;
Max9cacb6f2017-02-20 17:22:56 +0100243
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +0200244 if (!get_subscriber(hlr->dbc, by_selector, &subscr, cmd))
245 return CTRL_CMD_ERROR;
246
247 print_subscr_info(cmd, &subscr);
248
249 return CTRL_CMD_REPLY;
250}
251
252CTRL_CMD_DEFINE_RO(subscr_info_aud, "info-aud");
253static int get_subscr_info_aud(struct ctrl_cmd *cmd, void *data)
254{
255 const char *imsi;
256 struct osmo_sub_auth_data aud2g;
257 struct osmo_sub_auth_data aud3g;
258 struct hlr *hlr = data;
259 const char *by_selector = cmd->node;
260 int rc;
261
262 imsi = get_subscriber_imsi(hlr->dbc, by_selector, cmd);
263 if (!imsi)
264 return CTRL_CMD_ERROR;
265
266 rc = db_get_auth_data(hlr->dbc, imsi, &aud2g, &aud3g, NULL);
267
Neels Hofmeyrbd1dca02017-11-23 15:25:30 +0100268 switch (rc) {
269 case 0:
270 break;
271 case -ENOENT:
272 case -ENOKEY:
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +0200273 /* No auth data found, tell the print*() functions about it. */
274 aud2g.algo = OSMO_AUTH_ALG_NONE;
275 aud3g.algo = OSMO_AUTH_ALG_NONE;
Neels Hofmeyrbd1dca02017-11-23 15:25:30 +0100276 break;
277 default:
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +0200278 cmd->reply = "Error retrieving authentication data.";
Max9cacb6f2017-02-20 17:22:56 +0100279 return CTRL_CMD_ERROR;
280 }
281
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +0200282 print_subscr_info_aud2g(cmd, &aud2g);
283 print_subscr_info_aud3g(cmd, &aud3g);
284
285 return CTRL_CMD_REPLY;
286}
287
288CTRL_CMD_DEFINE_RO(subscr_info_all, "info-all");
289static int get_subscr_info_all(struct ctrl_cmd *cmd, void *data)
290{
291 struct hlr_subscriber subscr;
292 struct osmo_sub_auth_data aud2g;
293 struct osmo_sub_auth_data aud3g;
294 struct hlr *hlr = data;
295 const char *by_selector = cmd->node;
296 int rc;
297
298 if (!get_subscriber(hlr->dbc, by_selector, &subscr, cmd))
299 return CTRL_CMD_ERROR;
300
301 rc = db_get_auth_data(hlr->dbc, subscr.imsi, &aud2g, &aud3g, NULL);
302
Neels Hofmeyrbd1dca02017-11-23 15:25:30 +0100303 switch (rc) {
304 case 0:
305 break;
306 case -ENOENT:
307 case -ENOKEY:
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +0200308 /* No auth data found, tell the print*() functions about it. */
309 aud2g.algo = OSMO_AUTH_ALG_NONE;
310 aud3g.algo = OSMO_AUTH_ALG_NONE;
Neels Hofmeyrbd1dca02017-11-23 15:25:30 +0100311 break;
312 default:
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +0200313 cmd->reply = "Error retrieving authentication data.";
Max9cacb6f2017-02-20 17:22:56 +0100314 return CTRL_CMD_ERROR;
315 }
316
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +0200317 print_subscr_info(cmd, &subscr);
318 print_subscr_info_aud2g(cmd, &aud2g);
319 print_subscr_info_aud3g(cmd, &aud3g);
320
321 return CTRL_CMD_REPLY;
322}
323
324static int verify_subscr_cs_ps_enabled(struct ctrl_cmd *cmd, const char *value, void *data)
325{
326 if (!value || !*value
327 || (strcmp(value, "0") && strcmp(value, "1")))
328 return 1;
329 return 0;
330}
331
332static int get_subscr_cs_ps_enabled(struct ctrl_cmd *cmd, void *data,
333 bool is_ps)
334{
335 struct hlr_subscriber subscr;
336 struct hlr *hlr = data;
337 const char *by_selector = cmd->node;
338
339 if (!get_subscriber(hlr->dbc, by_selector, &subscr, cmd))
340 return CTRL_CMD_ERROR;
341
342 cmd->reply = (is_ps ? subscr.nam_ps : subscr.nam_cs)
343 ? "1" : "0";
344 return CTRL_CMD_REPLY;
345}
346
347static int set_subscr_cs_ps_enabled(struct ctrl_cmd *cmd, void *data,
348 bool is_ps)
349{
350 const char *imsi;
351 struct hlr *hlr = data;
352 const char *by_selector = cmd->node;
353
354 imsi = get_subscriber_imsi(hlr->dbc, by_selector, cmd);
355 if (!imsi)
356 return CTRL_CMD_ERROR;
357 if (db_subscr_nam(hlr->dbc, imsi, strcmp(cmd->value, "1") == 0, is_ps))
358 return CTRL_CMD_ERROR;
Max9cacb6f2017-02-20 17:22:56 +0100359 cmd->reply = "OK";
Max9cacb6f2017-02-20 17:22:56 +0100360 return CTRL_CMD_REPLY;
361}
362
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +0200363CTRL_CMD_DEFINE(subscr_ps_enabled, "ps-enabled");
364static int verify_subscr_ps_enabled(struct ctrl_cmd *cmd, const char *value, void *data)
Max9cacb6f2017-02-20 17:22:56 +0100365{
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +0200366 return verify_subscr_cs_ps_enabled(cmd, value, data);
367}
368static int get_subscr_ps_enabled(struct ctrl_cmd *cmd, void *data)
369{
370 return get_subscr_cs_ps_enabled(cmd, data, true);
371}
372static int set_subscr_ps_enabled(struct ctrl_cmd *cmd, void *data)
373{
374 return set_subscr_cs_ps_enabled(cmd, data, true);
Max9cacb6f2017-02-20 17:22:56 +0100375}
376
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +0200377CTRL_CMD_DEFINE(subscr_cs_enabled, "cs-enabled");
378static int verify_subscr_cs_enabled(struct ctrl_cmd *cmd, const char *value, void *data)
Max9cacb6f2017-02-20 17:22:56 +0100379{
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +0200380 return verify_subscr_cs_ps_enabled(cmd, value, data);
Max9cacb6f2017-02-20 17:22:56 +0100381}
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +0200382static int get_subscr_cs_enabled(struct ctrl_cmd *cmd, void *data)
Max372868b2017-03-02 12:12:00 +0100383{
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +0200384 return get_subscr_cs_ps_enabled(cmd, data, false);
385}
386static int set_subscr_cs_enabled(struct ctrl_cmd *cmd, void *data)
387{
388 return set_subscr_cs_ps_enabled(cmd, data, false);
Max372868b2017-03-02 12:12:00 +0100389}
390
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +0200391static int hlr_ctrl_node_lookup(void *data, vector vline, int *node_type,
392 void **node_data, int *i)
393{
394 const char *token = vector_slot(vline, *i);
395
396 switch (*node_type) {
397 case CTRL_NODE_ROOT:
398 if (strcmp(token, "subscriber") != 0)
399 return 0;
400 *node_data = NULL;
401 *node_type = CTRL_NODE_SUBSCR;
402 break;
403 case CTRL_NODE_SUBSCR:
404 if (!startswith(token, "by-"))
405 return 0;
406 *node_data = (void*)token;
407 *node_type = CTRL_NODE_SUBSCR_BY;
408 break;
409 default:
410 return 0;
411 }
412
413 return 1;
414}
415
Pau Espin Pedrolb4f25a02022-06-17 15:53:28 +0200416static int hlr_ctrl_cmds_install()
417{
418 int rc = 0;
419
Pau Espin Pedrold63ec882022-06-17 17:54:51 +0200420 rc |= ctrl_cmd_install(CTRL_NODE_SUBSCR, &cmd_subscr_create);
421
Pau Espin Pedrolb4f25a02022-06-17 15:53:28 +0200422 rc |= ctrl_cmd_install(CTRL_NODE_SUBSCR_BY, &cmd_subscr_info);
423 rc |= ctrl_cmd_install(CTRL_NODE_SUBSCR_BY, &cmd_subscr_info_aud);
424 rc |= ctrl_cmd_install(CTRL_NODE_SUBSCR_BY, &cmd_subscr_info_all);
425 rc |= ctrl_cmd_install(CTRL_NODE_SUBSCR_BY, &cmd_subscr_ps_enabled);
426 rc |= ctrl_cmd_install(CTRL_NODE_SUBSCR_BY, &cmd_subscr_cs_enabled);
427
428 return rc;
429}
430
Neels Hofmeyr234f9cb2017-10-24 17:23:04 +0200431struct ctrl_handle *hlr_controlif_setup(struct hlr *hlr)
Max372868b2017-03-02 12:12:00 +0100432{
433 int rc;
Neels Hofmeyr234f9cb2017-10-24 17:23:04 +0200434 struct ctrl_handle *hdl = ctrl_interface_setup_dynip2(hlr,
435 hlr->ctrl_bind_addr,
436 OSMO_CTRL_PORT_HLR,
Neels Hofmeyr446eb0f2017-10-17 01:58:24 +0200437 hlr_ctrl_node_lookup,
438 _LAST_CTRL_NODE_HLR);
Max372868b2017-03-02 12:12:00 +0100439 if (!hdl)
440 return NULL;
441
442 rc = hlr_ctrl_cmds_install();
443 if (rc) /* FIXME: close control interface? */
444 return NULL;
445
446 return hdl;
447}