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