blob: 9ee69481025edb5bb018b8226a3541ac69ef300b [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
166 "Create subscriber by IMSI\n"
167 "IMSI/MSISDN/ID of the subscriber\n")
168{
169 int rc;
170 struct hlr_subscriber subscr;
171 const char *imsi = argv[0];
172
173 if (!osmo_imsi_str_valid(imsi)) {
174 vty_out(vty, "%% Not a valid IMSI: %s%s", imsi, VTY_NEWLINE);
175 return CMD_WARNING;
176 }
177
178 rc = db_subscr_create(g_hlr->dbc, imsi);
179
180 if (rc) {
181 if (rc == -EEXIST)
182 vty_out(vty, "%% Subscriber already exists for IMSI = %s%s",
183 imsi, VTY_NEWLINE);
184 else
185 vty_out(vty, "%% Error (rc=%d): cannot create subscriber for IMSI = %s%s",
186 rc, imsi, VTY_NEWLINE);
187 return CMD_WARNING;
188 }
189
190 rc = db_subscr_get_by_imsi(g_hlr->dbc, imsi, &subscr);
191 vty_out(vty, "%% Created subscriber %s%s", imsi, VTY_NEWLINE);
192
193 subscr_dump_full_vty(vty, &subscr);
194
195 return CMD_SUCCESS;
196}
197
198DEFUN(subscriber_delete,
199 subscriber_delete_cmd,
200 SUBSCR "delete",
201 SUBSCR_HELP "Delete subscriber from database\n")
202{
203 struct hlr_subscriber subscr;
204 int rc;
205 const char *id_type = argv[0];
206 const char *id = argv[1];
207
208 /* Find out the IMSI regardless of which way the caller decided to
209 * identify the subscriber by. */
210 if (get_subscr_by_argv(vty, id_type, id, &subscr))
211 return CMD_WARNING;
212
213 rc = db_subscr_delete_by_id(g_hlr->dbc, subscr.id);
214 if (rc) {
215 vty_out(vty, "%% Error: Failed to remove subscriber for IMSI '%s'%s",
216 subscr.imsi, VTY_NEWLINE);
217 return CMD_WARNING;
218 }
219
220 vty_out(vty, "%% Deleted subscriber for IMSI '%s'%s", subscr.imsi, VTY_NEWLINE);
221 return CMD_SUCCESS;
222}
223
224DEFUN(subscriber_msisdn,
225 subscriber_msisdn_cmd,
226 SUBSCR_UPDATE "msisdn MSISDN",
227 SUBSCR_UPDATE_HELP
228 "Set MSISDN (phone number) of the subscriber\n"
229 "New MSISDN (phone number)\n")
230{
231 struct hlr_subscriber subscr;
232 const char *id_type = argv[0];
233 const char *id = argv[1];
234 const char *msisdn = argv[2];
235
236 if (strlen(msisdn) > sizeof(subscr.msisdn) - 1) {
237 vty_out(vty, "%% MSISDN is too long, max. %zu characters are allowed%s",
238 sizeof(subscr.msisdn)-1, VTY_NEWLINE);
239 return CMD_WARNING;
240 }
241
242 if (!osmo_msisdn_str_valid(msisdn)) {
243 vty_out(vty, "%% MSISDN invalid: '%s'%s", msisdn, VTY_NEWLINE);
244 return CMD_WARNING;
245 }
246
247 if (get_subscr_by_argv(vty, id_type, id, &subscr))
248 return CMD_WARNING;
249
250 if (db_subscr_update_msisdn_by_imsi(g_hlr->dbc, subscr.imsi, msisdn)) {
251 vty_out(vty, "%% Error: cannot update MSISDN for subscriber IMSI='%s'%s",
252 subscr.imsi, VTY_NEWLINE);
253 return CMD_WARNING;
254 }
255
256 vty_out(vty, "%% Updated subscriber IMSI='%s' to MSISDN='%s'%s",
257 subscr.imsi, msisdn, VTY_NEWLINE);
Stefan Sperlingf1622522018-04-09 11:39:16 +0200258
259 if (db_subscr_get_by_msisdn(g_hlr->dbc, msisdn, &subscr) == 0)
260 osmo_hlr_subscriber_update_notify(&subscr);
261
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200262 return CMD_SUCCESS;
263}
264
265static bool is_hexkey_valid(struct vty *vty, const char *label,
266 const char *hex_str, int minlen, int maxlen)
267{
268 if (osmo_is_hexstr(hex_str, minlen * 2, maxlen * 2, true))
269 return true;
270 vty_out(vty, "%% Invalid value for %s: '%s'%s", label, hex_str, VTY_NEWLINE);
271 return false;
272}
273
274#define AUTH_ALG_TYPES_2G "(comp128v1|comp128v2|comp128v3|xor)"
275#define AUTH_ALG_TYPES_2G_HELP \
276 "Use COMP128v1 algorithm\n" \
277 "Use COMP128v2 algorithm\n" \
278 "Use COMP128v3 algorithm\n" \
279 "Use XOR algorithm\n"
280
281#define AUTH_ALG_TYPES_3G "milenage"
282#define AUTH_ALG_TYPES_3G_HELP \
283 "Use Milenage algorithm\n"
284
285#define A38_XOR_MIN_KEY_LEN 12
286#define A38_XOR_MAX_KEY_LEN 16
287#define A38_COMP128_KEY_LEN 16
288
289#define MILENAGE_KEY_LEN 16
290
291static bool auth_algo_parse(const char *alg_str, enum osmo_auth_algo *algo,
292 int *minlen, int *maxlen)
293{
294 if (!strcasecmp(alg_str, "none")) {
295 *algo = OSMO_AUTH_ALG_NONE;
296 *minlen = *maxlen = 0;
297 } else if (!strcasecmp(alg_str, "comp128v1")) {
298 *algo = OSMO_AUTH_ALG_COMP128v1;
299 *minlen = *maxlen = A38_COMP128_KEY_LEN;
300 } else if (!strcasecmp(alg_str, "comp128v2")) {
301 *algo = OSMO_AUTH_ALG_COMP128v2;
302 *minlen = *maxlen = A38_COMP128_KEY_LEN;
303 } else if (!strcasecmp(alg_str, "comp128v3")) {
304 *algo = OSMO_AUTH_ALG_COMP128v3;
305 *minlen = *maxlen = A38_COMP128_KEY_LEN;
306 } else if (!strcasecmp(alg_str, "xor")) {
307 *algo = OSMO_AUTH_ALG_XOR;
308 *minlen = A38_XOR_MIN_KEY_LEN;
309 *maxlen = A38_XOR_MAX_KEY_LEN;
310 } else if (!strcasecmp(alg_str, "milenage")) {
311 *algo = OSMO_AUTH_ALG_MILENAGE;
312 *minlen = *maxlen = MILENAGE_KEY_LEN;
313 } else
314 return false;
315 return true;
316}
317
318DEFUN(subscriber_no_aud2g,
319 subscriber_no_aud2g_cmd,
320 SUBSCR_UPDATE "aud2g none",
321 SUBSCR_UPDATE_HELP
322 "Set 2G authentication data\n"
323 "Delete 2G authentication data\n")
324{
325 struct hlr_subscriber subscr;
326 int rc;
327 const char *id_type = argv[0];
328 const char *id = argv[1];
329 struct sub_auth_data_str aud = {
330 .type = OSMO_AUTH_TYPE_GSM,
331 .algo = OSMO_AUTH_ALG_NONE,
332 };
333
334 if (get_subscr_by_argv(vty, id_type, id, &subscr))
335 return CMD_WARNING;
336
337 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud);
338
Harald Welte880a34d2018-03-01 21:32:01 +0100339 if (rc && rc != -ENOENT) {
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200340 vty_out(vty, "%% Error: cannot disable 2G auth data for IMSI='%s'%s",
341 subscr.imsi, VTY_NEWLINE);
342 return CMD_WARNING;
343 }
344 return CMD_SUCCESS;
345}
346
347DEFUN(subscriber_aud2g,
348 subscriber_aud2g_cmd,
349 SUBSCR_UPDATE "aud2g " AUTH_ALG_TYPES_2G " ki KI",
350 SUBSCR_UPDATE_HELP
351 "Set 2G authentication data\n"
352 AUTH_ALG_TYPES_2G_HELP
353 "Set Ki Encryption Key\n" "Ki as 32 hexadecimal characters\n")
354{
355 struct hlr_subscriber subscr;
356 int rc;
357 int minlen = 0;
358 int maxlen = 0;
359 const char *id_type = argv[0];
360 const char *id = argv[1];
361 const char *alg_type = argv[2];
362 const char *ki = argv[3];
363 struct sub_auth_data_str aud2g = {
364 .type = OSMO_AUTH_TYPE_GSM,
365 .u.gsm.ki = ki,
366 };
367
368 if (!auth_algo_parse(alg_type, &aud2g.algo, &minlen, &maxlen)) {
369 vty_out(vty, "%% Unknown auth algorithm: '%s'%s", alg_type, VTY_NEWLINE);
370 return CMD_WARNING;
371 }
372
373 if (!is_hexkey_valid(vty, "KI", aud2g.u.gsm.ki, minlen, maxlen))
374 return CMD_WARNING;
375
376 if (get_subscr_by_argv(vty, id_type, id, &subscr))
377 return CMD_WARNING;
378
379 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud2g);
380
381 if (rc) {
382 vty_out(vty, "%% Error: cannot set 2G auth data for IMSI='%s'%s",
383 subscr.imsi, VTY_NEWLINE);
384 return CMD_WARNING;
385 }
386 return CMD_SUCCESS;
387}
388
389DEFUN(subscriber_no_aud3g,
390 subscriber_no_aud3g_cmd,
391 SUBSCR_UPDATE "aud3g none",
392 SUBSCR_UPDATE_HELP
393 "Set UMTS authentication data (3G, and 2G with UMTS AKA)\n"
394 "Delete 3G authentication data\n")
395{
396 struct hlr_subscriber subscr;
397 int rc;
398 const char *id_type = argv[0];
399 const char *id = argv[1];
400 struct sub_auth_data_str aud = {
401 .type = OSMO_AUTH_TYPE_UMTS,
402 .algo = OSMO_AUTH_ALG_NONE,
403 };
404
405 if (get_subscr_by_argv(vty, id_type, id, &subscr))
406 return CMD_WARNING;
407
408 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud);
409
Harald Welte880a34d2018-03-01 21:32:01 +0100410 if (rc && rc != -ENOENT) {
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200411 vty_out(vty, "%% Error: cannot disable 3G auth data for IMSI='%s'%s",
412 subscr.imsi, VTY_NEWLINE);
413 return CMD_WARNING;
414 }
415 return CMD_SUCCESS;
416}
417
418DEFUN(subscriber_aud3g,
419 subscriber_aud3g_cmd,
420 SUBSCR_UPDATE "aud3g " AUTH_ALG_TYPES_3G
421 " k K"
422 " (op|opc) OP_C"
423 " [ind-bitlen] [<0-28>]",
424 SUBSCR_UPDATE_HELP
425 "Set UMTS authentication data (3G, and 2G with UMTS AKA)\n"
426 AUTH_ALG_TYPES_3G_HELP
427 "Set Encryption Key K\n" "K as 32 hexadecimal characters\n"
428 "Set OP key\n" "Set OPC key\n" "OP or OPC as 32 hexadecimal characters\n"
429 "Set IND bit length\n" "IND bit length value (default: 5)\n")
430{
431 struct hlr_subscriber subscr;
432 int minlen = 0;
433 int maxlen = 0;
434 int rc;
435 const char *id_type = argv[0];
436 const char *id = argv[1];
437 const char *alg_type = AUTH_ALG_TYPES_3G;
438 const char *k = argv[2];
439 bool opc_is_op = (strcasecmp("op", argv[3]) == 0);
440 const char *op_opc = argv[4];
441 int ind_bitlen = argc > 6? atoi(argv[6]) : 5;
442 struct sub_auth_data_str aud3g = {
443 .type = OSMO_AUTH_TYPE_UMTS,
444 .u.umts = {
445 .k = k,
446 .opc_is_op = opc_is_op,
447 .opc = op_opc,
448 .ind_bitlen = ind_bitlen,
449 },
450 };
451
452 if (!auth_algo_parse(alg_type, &aud3g.algo, &minlen, &maxlen)) {
453 vty_out(vty, "%% Unknown auth algorithm: '%s'%s", alg_type, VTY_NEWLINE);
454 return CMD_WARNING;
455 }
456
457 if (!is_hexkey_valid(vty, "K", aud3g.u.umts.k, minlen, maxlen))
458 return CMD_WARNING;
459
460 if (!is_hexkey_valid(vty, opc_is_op ? "OP" : "OPC", aud3g.u.umts.opc,
461 MILENAGE_KEY_LEN, MILENAGE_KEY_LEN))
462 return CMD_WARNING;
463
464 if (get_subscr_by_argv(vty, id_type, id, &subscr))
465 return CMD_WARNING;
466
467 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud3g);
468
469 if (rc) {
470 vty_out(vty, "%% Error: cannot set 3G auth data for IMSI='%s'%s",
471 subscr.imsi, VTY_NEWLINE);
472 return CMD_WARNING;
473 }
474 return CMD_SUCCESS;
475}
476
Harald Welted5807b82018-07-29 12:27:41 +0200477void hlr_vty_subscriber_init(void)
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200478{
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200479 install_element_ve(&subscriber_show_cmd);
480 install_element(ENABLE_NODE, &subscriber_create_cmd);
481 install_element(ENABLE_NODE, &subscriber_delete_cmd);
482 install_element(ENABLE_NODE, &subscriber_msisdn_cmd);
483 install_element(ENABLE_NODE, &subscriber_no_aud2g_cmd);
484 install_element(ENABLE_NODE, &subscriber_aud2g_cmd);
485 install_element(ENABLE_NODE, &subscriber_no_aud3g_cmd);
486 install_element(ENABLE_NODE, &subscriber_aud3g_cmd);
487}