blob: 6ce96fe2237b64ffcd5cb4a40a2c344b4ab8025f [file] [log] [blame]
Neels Hofmeyr183e7002017-10-06 02:59:54 +02001/* OsmoHLR subscriber management VTY implementation */
2/* (C) 2017 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
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#include <stdlib.h>
20#include <inttypes.h>
21#include <string.h>
22#include <errno.h>
23
24#include <osmocom/gsm/gsm23003.h>
25#include <osmocom/vty/vty.h>
26#include <osmocom/vty/command.h>
27#include <osmocom/core/utils.h>
28
29#include "hlr.h"
30#include "db.h"
31
32struct vty;
33
34#define hexdump_buf(buf) osmo_hexdump_nospc((void*)buf, sizeof(buf))
35
Neels Hofmeyr183e7002017-10-06 02:59:54 +020036static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
37{
38 int rc;
39 struct osmo_sub_auth_data aud2g;
40 struct osmo_sub_auth_data aud3g;
41
42 vty_out(vty, " ID: %"PRIu64"%s", subscr->id, VTY_NEWLINE);
43
Neels Hofmeyr36bec872017-10-23 18:44:23 +020044 vty_out(vty, " IMSI: %s%s", *subscr->imsi ? subscr->imsi : "none", VTY_NEWLINE);
Neels Hofmeyr183e7002017-10-06 02:59:54 +020045 vty_out(vty, " MSISDN: %s%s", *subscr->msisdn ? subscr->msisdn : "none", VTY_NEWLINE);
46 if (*subscr->vlr_number)
47 vty_out(vty, " VLR number: %s%s", subscr->vlr_number, VTY_NEWLINE);
48 if (*subscr->sgsn_number)
49 vty_out(vty, " SGSN number: %s%s", subscr->sgsn_number, VTY_NEWLINE);
50 if (*subscr->sgsn_address)
51 vty_out(vty, " SGSN address: %s%s", subscr->sgsn_address, VTY_NEWLINE);
52 if (subscr->periodic_lu_timer)
53 vty_out(vty, " Periodic LU timer: %u%s", subscr->periodic_lu_timer, VTY_NEWLINE);
54 if (subscr->periodic_rau_tau_timer)
55 vty_out(vty, " Periodic RAU/TAU timer: %u%s", subscr->periodic_rau_tau_timer, VTY_NEWLINE);
56 if (subscr->lmsi)
57 vty_out(vty, " LMSI: %x%s", subscr->lmsi, VTY_NEWLINE);
58 if (!subscr->nam_cs)
59 vty_out(vty, " CS disabled%s", VTY_NEWLINE);
60 if (subscr->ms_purged_cs)
61 vty_out(vty, " CS purged%s", VTY_NEWLINE);
62 if (!subscr->nam_ps)
63 vty_out(vty, " PS disabled%s", VTY_NEWLINE);
64 if (subscr->ms_purged_ps)
65 vty_out(vty, " PS purged%s", VTY_NEWLINE);
66
67 if (!*subscr->imsi)
68 return;
69
70 OSMO_ASSERT(g_hlr);
71 rc = db_get_auth_data(g_hlr->dbc, subscr->imsi, &aud2g, &aud3g, NULL);
72
Neels Hofmeyrbd1dca02017-11-23 15:25:30 +010073 switch (rc) {
74 case 0:
75 break;
76 case -ENOENT:
77 case -ENOKEY:
78 aud2g.algo = OSMO_AUTH_ALG_NONE;
79 aud3g.algo = OSMO_AUTH_ALG_NONE;
80 break;
81 default:
82 vty_out(vty, "%% Error retrieving data from database (%d)%s", rc, VTY_NEWLINE);
83 return;
Neels Hofmeyr183e7002017-10-06 02:59:54 +020084 }
85
86 if (aud2g.type != OSMO_AUTH_TYPE_NONE && aud2g.type != OSMO_AUTH_TYPE_GSM) {
87 vty_out(vty, "%% Error: 2G auth data is not of type 'GSM'%s", VTY_NEWLINE);
88 aud2g = (struct osmo_sub_auth_data){};
89 }
90
91 if (aud3g.type != OSMO_AUTH_TYPE_NONE && aud3g.type != OSMO_AUTH_TYPE_UMTS) {
92 vty_out(vty, "%% Error: 3G auth data is not of type 'UMTS'%s", VTY_NEWLINE);
93 aud3g = (struct osmo_sub_auth_data){};
94 }
95
96 if (aud2g.algo != OSMO_AUTH_ALG_NONE && aud2g.type != OSMO_AUTH_TYPE_NONE) {
97 vty_out(vty, " 2G auth: %s%s",
98 osmo_auth_alg_name(aud2g.algo), VTY_NEWLINE);
99 vty_out(vty, " KI=%s%s",
100 hexdump_buf(aud2g.u.gsm.ki), VTY_NEWLINE);
101 }
102
103 if (aud3g.algo != OSMO_AUTH_ALG_NONE && aud3g.type != OSMO_AUTH_TYPE_NONE) {
104 vty_out(vty, " 3G auth: %s%s", osmo_auth_alg_name(aud3g.algo), VTY_NEWLINE);
105 vty_out(vty, " K=%s%s", hexdump_buf(aud3g.u.umts.k), VTY_NEWLINE);
106 vty_out(vty, " %s=%s%s", aud3g.u.umts.opc_is_op? "OP" : "OPC",
107 hexdump_buf(aud3g.u.umts.opc), VTY_NEWLINE);
108 vty_out(vty, " IND-bitlen=%u", aud3g.u.umts.ind_bitlen);
109 if (aud3g.u.umts.sqn)
110 vty_out(vty, " last-SQN=%"PRIu64, aud3g.u.umts.sqn);
111 vty_out(vty, VTY_NEWLINE);
112 }
113}
114
115static int get_subscr_by_argv(struct vty *vty, const char *type, const char *id, struct hlr_subscriber *subscr)
116{
117 int rc = -1;
118 if (strcmp(type, "imsi") == 0)
119 rc = db_subscr_get_by_imsi(g_hlr->dbc, id, subscr);
120 else if (strcmp(type, "msisdn") == 0)
121 rc = db_subscr_get_by_msisdn(g_hlr->dbc, id, subscr);
122 else if (strcmp(type, "id") == 0)
123 rc = db_subscr_get_by_id(g_hlr->dbc, atoll(id), subscr);
124 if (rc)
125 vty_out(vty, "%% No subscriber for %s = '%s'%s",
126 type, id, VTY_NEWLINE);
127 return rc;
128}
129
130#define SUBSCR_CMD "subscriber "
131#define SUBSCR_CMD_HELP "Subscriber management commands\n"
132
133#define SUBSCR_ID "(imsi|msisdn|id) IDENT "
134#define SUBSCR_ID_HELP \
135 "Identify subscriber by IMSI\n" \
136 "Identify subscriber by MSISDN (phone number)\n" \
137 "Identify subscriber by database ID\n" \
138 "IMSI/MSISDN/ID of the subscriber\n"
139
140#define SUBSCR SUBSCR_CMD SUBSCR_ID
141#define SUBSCR_HELP SUBSCR_CMD_HELP SUBSCR_ID_HELP
142
143#define SUBSCR_UPDATE SUBSCR "update "
144#define SUBSCR_UPDATE_HELP SUBSCR_HELP "Set or update subscriber data\n"
145
146DEFUN(subscriber_show,
147 subscriber_show_cmd,
148 SUBSCR "show",
149 SUBSCR_HELP "Show subscriber information\n")
150{
151 struct hlr_subscriber subscr;
152 const char *id_type = argv[0];
153 const char *id = argv[1];
154
155 if (get_subscr_by_argv(vty, id_type, id, &subscr))
156 return CMD_WARNING;
157
158 subscr_dump_full_vty(vty, &subscr);
159 return CMD_SUCCESS;
160}
161
162DEFUN(subscriber_create,
163 subscriber_create_cmd,
164 SUBSCR_CMD "imsi IDENT create",
165 SUBSCR_CMD_HELP
Vadim Yanitskiyf473c7b2018-07-30 14:29:39 +0700166 "Identify subscriber by IMSI\n"
167 "IMSI/MSISDN/ID of the subscriber\n"
168 "Create subscriber by IMSI\n")
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200169{
170 int rc;
171 struct hlr_subscriber subscr;
172 const char *imsi = argv[0];
173
174 if (!osmo_imsi_str_valid(imsi)) {
175 vty_out(vty, "%% Not a valid IMSI: %s%s", imsi, VTY_NEWLINE);
176 return CMD_WARNING;
177 }
178
179 rc = db_subscr_create(g_hlr->dbc, imsi);
180
181 if (rc) {
182 if (rc == -EEXIST)
183 vty_out(vty, "%% Subscriber already exists for IMSI = %s%s",
184 imsi, VTY_NEWLINE);
185 else
186 vty_out(vty, "%% Error (rc=%d): cannot create subscriber for IMSI = %s%s",
187 rc, imsi, VTY_NEWLINE);
188 return CMD_WARNING;
189 }
190
191 rc = db_subscr_get_by_imsi(g_hlr->dbc, imsi, &subscr);
192 vty_out(vty, "%% Created subscriber %s%s", imsi, VTY_NEWLINE);
193
194 subscr_dump_full_vty(vty, &subscr);
195
196 return CMD_SUCCESS;
197}
198
199DEFUN(subscriber_delete,
200 subscriber_delete_cmd,
201 SUBSCR "delete",
202 SUBSCR_HELP "Delete subscriber from database\n")
203{
204 struct hlr_subscriber subscr;
205 int rc;
206 const char *id_type = argv[0];
207 const char *id = argv[1];
208
209 /* Find out the IMSI regardless of which way the caller decided to
210 * identify the subscriber by. */
211 if (get_subscr_by_argv(vty, id_type, id, &subscr))
212 return CMD_WARNING;
213
214 rc = db_subscr_delete_by_id(g_hlr->dbc, subscr.id);
215 if (rc) {
216 vty_out(vty, "%% Error: Failed to remove subscriber for IMSI '%s'%s",
217 subscr.imsi, VTY_NEWLINE);
218 return CMD_WARNING;
219 }
220
221 vty_out(vty, "%% Deleted subscriber for IMSI '%s'%s", subscr.imsi, VTY_NEWLINE);
222 return CMD_SUCCESS;
223}
224
225DEFUN(subscriber_msisdn,
226 subscriber_msisdn_cmd,
227 SUBSCR_UPDATE "msisdn MSISDN",
228 SUBSCR_UPDATE_HELP
229 "Set MSISDN (phone number) of the subscriber\n"
230 "New MSISDN (phone number)\n")
231{
232 struct hlr_subscriber subscr;
233 const char *id_type = argv[0];
234 const char *id = argv[1];
235 const char *msisdn = argv[2];
236
237 if (strlen(msisdn) > sizeof(subscr.msisdn) - 1) {
238 vty_out(vty, "%% MSISDN is too long, max. %zu characters are allowed%s",
239 sizeof(subscr.msisdn)-1, VTY_NEWLINE);
240 return CMD_WARNING;
241 }
242
243 if (!osmo_msisdn_str_valid(msisdn)) {
244 vty_out(vty, "%% MSISDN invalid: '%s'%s", msisdn, VTY_NEWLINE);
245 return CMD_WARNING;
246 }
247
248 if (get_subscr_by_argv(vty, id_type, id, &subscr))
249 return CMD_WARNING;
250
251 if (db_subscr_update_msisdn_by_imsi(g_hlr->dbc, subscr.imsi, msisdn)) {
252 vty_out(vty, "%% Error: cannot update MSISDN for subscriber IMSI='%s'%s",
253 subscr.imsi, VTY_NEWLINE);
254 return CMD_WARNING;
255 }
256
257 vty_out(vty, "%% Updated subscriber IMSI='%s' to MSISDN='%s'%s",
258 subscr.imsi, msisdn, VTY_NEWLINE);
Stefan Sperlingf1622522018-04-09 11:39:16 +0200259
260 if (db_subscr_get_by_msisdn(g_hlr->dbc, msisdn, &subscr) == 0)
261 osmo_hlr_subscriber_update_notify(&subscr);
262
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200263 return CMD_SUCCESS;
264}
265
266static bool is_hexkey_valid(struct vty *vty, const char *label,
267 const char *hex_str, int minlen, int maxlen)
268{
269 if (osmo_is_hexstr(hex_str, minlen * 2, maxlen * 2, true))
270 return true;
271 vty_out(vty, "%% Invalid value for %s: '%s'%s", label, hex_str, VTY_NEWLINE);
272 return false;
273}
274
275#define AUTH_ALG_TYPES_2G "(comp128v1|comp128v2|comp128v3|xor)"
276#define AUTH_ALG_TYPES_2G_HELP \
277 "Use COMP128v1 algorithm\n" \
278 "Use COMP128v2 algorithm\n" \
279 "Use COMP128v3 algorithm\n" \
280 "Use XOR algorithm\n"
281
282#define AUTH_ALG_TYPES_3G "milenage"
283#define AUTH_ALG_TYPES_3G_HELP \
284 "Use Milenage algorithm\n"
285
286#define A38_XOR_MIN_KEY_LEN 12
287#define A38_XOR_MAX_KEY_LEN 16
288#define A38_COMP128_KEY_LEN 16
289
290#define MILENAGE_KEY_LEN 16
291
292static bool auth_algo_parse(const char *alg_str, enum osmo_auth_algo *algo,
293 int *minlen, int *maxlen)
294{
295 if (!strcasecmp(alg_str, "none")) {
296 *algo = OSMO_AUTH_ALG_NONE;
297 *minlen = *maxlen = 0;
298 } else if (!strcasecmp(alg_str, "comp128v1")) {
299 *algo = OSMO_AUTH_ALG_COMP128v1;
300 *minlen = *maxlen = A38_COMP128_KEY_LEN;
301 } else if (!strcasecmp(alg_str, "comp128v2")) {
302 *algo = OSMO_AUTH_ALG_COMP128v2;
303 *minlen = *maxlen = A38_COMP128_KEY_LEN;
304 } else if (!strcasecmp(alg_str, "comp128v3")) {
305 *algo = OSMO_AUTH_ALG_COMP128v3;
306 *minlen = *maxlen = A38_COMP128_KEY_LEN;
307 } else if (!strcasecmp(alg_str, "xor")) {
308 *algo = OSMO_AUTH_ALG_XOR;
309 *minlen = A38_XOR_MIN_KEY_LEN;
310 *maxlen = A38_XOR_MAX_KEY_LEN;
311 } else if (!strcasecmp(alg_str, "milenage")) {
312 *algo = OSMO_AUTH_ALG_MILENAGE;
313 *minlen = *maxlen = MILENAGE_KEY_LEN;
314 } else
315 return false;
316 return true;
317}
318
319DEFUN(subscriber_no_aud2g,
320 subscriber_no_aud2g_cmd,
321 SUBSCR_UPDATE "aud2g none",
322 SUBSCR_UPDATE_HELP
323 "Set 2G authentication data\n"
324 "Delete 2G authentication data\n")
325{
326 struct hlr_subscriber subscr;
327 int rc;
328 const char *id_type = argv[0];
329 const char *id = argv[1];
330 struct sub_auth_data_str aud = {
331 .type = OSMO_AUTH_TYPE_GSM,
332 .algo = OSMO_AUTH_ALG_NONE,
333 };
334
335 if (get_subscr_by_argv(vty, id_type, id, &subscr))
336 return CMD_WARNING;
337
338 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud);
339
Harald Welte880a34d2018-03-01 21:32:01 +0100340 if (rc && rc != -ENOENT) {
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200341 vty_out(vty, "%% Error: cannot disable 2G auth data for IMSI='%s'%s",
342 subscr.imsi, VTY_NEWLINE);
343 return CMD_WARNING;
344 }
345 return CMD_SUCCESS;
346}
347
348DEFUN(subscriber_aud2g,
349 subscriber_aud2g_cmd,
350 SUBSCR_UPDATE "aud2g " AUTH_ALG_TYPES_2G " ki KI",
351 SUBSCR_UPDATE_HELP
352 "Set 2G authentication data\n"
353 AUTH_ALG_TYPES_2G_HELP
354 "Set Ki Encryption Key\n" "Ki as 32 hexadecimal characters\n")
355{
356 struct hlr_subscriber subscr;
357 int rc;
358 int minlen = 0;
359 int maxlen = 0;
360 const char *id_type = argv[0];
361 const char *id = argv[1];
362 const char *alg_type = argv[2];
363 const char *ki = argv[3];
364 struct sub_auth_data_str aud2g = {
365 .type = OSMO_AUTH_TYPE_GSM,
366 .u.gsm.ki = ki,
367 };
368
369 if (!auth_algo_parse(alg_type, &aud2g.algo, &minlen, &maxlen)) {
370 vty_out(vty, "%% Unknown auth algorithm: '%s'%s", alg_type, VTY_NEWLINE);
371 return CMD_WARNING;
372 }
373
374 if (!is_hexkey_valid(vty, "KI", aud2g.u.gsm.ki, minlen, maxlen))
375 return CMD_WARNING;
376
377 if (get_subscr_by_argv(vty, id_type, id, &subscr))
378 return CMD_WARNING;
379
380 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud2g);
381
382 if (rc) {
383 vty_out(vty, "%% Error: cannot set 2G auth data for IMSI='%s'%s",
384 subscr.imsi, VTY_NEWLINE);
385 return CMD_WARNING;
386 }
387 return CMD_SUCCESS;
388}
389
390DEFUN(subscriber_no_aud3g,
391 subscriber_no_aud3g_cmd,
392 SUBSCR_UPDATE "aud3g none",
393 SUBSCR_UPDATE_HELP
394 "Set UMTS authentication data (3G, and 2G with UMTS AKA)\n"
395 "Delete 3G authentication data\n")
396{
397 struct hlr_subscriber subscr;
398 int rc;
399 const char *id_type = argv[0];
400 const char *id = argv[1];
401 struct sub_auth_data_str aud = {
402 .type = OSMO_AUTH_TYPE_UMTS,
403 .algo = OSMO_AUTH_ALG_NONE,
404 };
405
406 if (get_subscr_by_argv(vty, id_type, id, &subscr))
407 return CMD_WARNING;
408
409 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud);
410
Harald Welte880a34d2018-03-01 21:32:01 +0100411 if (rc && rc != -ENOENT) {
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200412 vty_out(vty, "%% Error: cannot disable 3G auth data for IMSI='%s'%s",
413 subscr.imsi, VTY_NEWLINE);
414 return CMD_WARNING;
415 }
416 return CMD_SUCCESS;
417}
418
419DEFUN(subscriber_aud3g,
420 subscriber_aud3g_cmd,
421 SUBSCR_UPDATE "aud3g " AUTH_ALG_TYPES_3G
422 " k K"
423 " (op|opc) OP_C"
424 " [ind-bitlen] [<0-28>]",
425 SUBSCR_UPDATE_HELP
426 "Set UMTS authentication data (3G, and 2G with UMTS AKA)\n"
427 AUTH_ALG_TYPES_3G_HELP
428 "Set Encryption Key K\n" "K as 32 hexadecimal characters\n"
429 "Set OP key\n" "Set OPC key\n" "OP or OPC as 32 hexadecimal characters\n"
430 "Set IND bit length\n" "IND bit length value (default: 5)\n")
431{
432 struct hlr_subscriber subscr;
433 int minlen = 0;
434 int maxlen = 0;
435 int rc;
436 const char *id_type = argv[0];
437 const char *id = argv[1];
438 const char *alg_type = AUTH_ALG_TYPES_3G;
439 const char *k = argv[2];
440 bool opc_is_op = (strcasecmp("op", argv[3]) == 0);
441 const char *op_opc = argv[4];
442 int ind_bitlen = argc > 6? atoi(argv[6]) : 5;
443 struct sub_auth_data_str aud3g = {
444 .type = OSMO_AUTH_TYPE_UMTS,
445 .u.umts = {
446 .k = k,
447 .opc_is_op = opc_is_op,
448 .opc = op_opc,
449 .ind_bitlen = ind_bitlen,
450 },
451 };
452
453 if (!auth_algo_parse(alg_type, &aud3g.algo, &minlen, &maxlen)) {
454 vty_out(vty, "%% Unknown auth algorithm: '%s'%s", alg_type, VTY_NEWLINE);
455 return CMD_WARNING;
456 }
457
458 if (!is_hexkey_valid(vty, "K", aud3g.u.umts.k, minlen, maxlen))
459 return CMD_WARNING;
460
461 if (!is_hexkey_valid(vty, opc_is_op ? "OP" : "OPC", aud3g.u.umts.opc,
462 MILENAGE_KEY_LEN, MILENAGE_KEY_LEN))
463 return CMD_WARNING;
464
465 if (get_subscr_by_argv(vty, id_type, id, &subscr))
466 return CMD_WARNING;
467
468 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud3g);
469
470 if (rc) {
471 vty_out(vty, "%% Error: cannot set 3G auth data for IMSI='%s'%s",
472 subscr.imsi, VTY_NEWLINE);
473 return CMD_WARNING;
474 }
475 return CMD_SUCCESS;
476}
477
Harald Welted5807b82018-07-29 12:27:41 +0200478void hlr_vty_subscriber_init(void)
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200479{
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200480 install_element_ve(&subscriber_show_cmd);
481 install_element(ENABLE_NODE, &subscriber_create_cmd);
482 install_element(ENABLE_NODE, &subscriber_delete_cmd);
483 install_element(ENABLE_NODE, &subscriber_msisdn_cmd);
484 install_element(ENABLE_NODE, &subscriber_no_aud2g_cmd);
485 install_element(ENABLE_NODE, &subscriber_aud2g_cmd);
486 install_element(ENABLE_NODE, &subscriber_no_aud3g_cmd);
487 install_element(ENABLE_NODE, &subscriber_aud3g_cmd);
488}