blob: 7191a1c18b82ae28c8258722ba48b1c33e1033bf [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);
260 return CMD_SUCCESS;
261}
262
263static bool is_hexkey_valid(struct vty *vty, const char *label,
264 const char *hex_str, int minlen, int maxlen)
265{
266 if (osmo_is_hexstr(hex_str, minlen * 2, maxlen * 2, true))
267 return true;
268 vty_out(vty, "%% Invalid value for %s: '%s'%s", label, hex_str, VTY_NEWLINE);
269 return false;
270}
271
272#define AUTH_ALG_TYPES_2G "(comp128v1|comp128v2|comp128v3|xor)"
273#define AUTH_ALG_TYPES_2G_HELP \
274 "Use COMP128v1 algorithm\n" \
275 "Use COMP128v2 algorithm\n" \
276 "Use COMP128v3 algorithm\n" \
277 "Use XOR algorithm\n"
278
279#define AUTH_ALG_TYPES_3G "milenage"
280#define AUTH_ALG_TYPES_3G_HELP \
281 "Use Milenage algorithm\n"
282
283#define A38_XOR_MIN_KEY_LEN 12
284#define A38_XOR_MAX_KEY_LEN 16
285#define A38_COMP128_KEY_LEN 16
286
287#define MILENAGE_KEY_LEN 16
288
289static bool auth_algo_parse(const char *alg_str, enum osmo_auth_algo *algo,
290 int *minlen, int *maxlen)
291{
292 if (!strcasecmp(alg_str, "none")) {
293 *algo = OSMO_AUTH_ALG_NONE;
294 *minlen = *maxlen = 0;
295 } else if (!strcasecmp(alg_str, "comp128v1")) {
296 *algo = OSMO_AUTH_ALG_COMP128v1;
297 *minlen = *maxlen = A38_COMP128_KEY_LEN;
298 } else if (!strcasecmp(alg_str, "comp128v2")) {
299 *algo = OSMO_AUTH_ALG_COMP128v2;
300 *minlen = *maxlen = A38_COMP128_KEY_LEN;
301 } else if (!strcasecmp(alg_str, "comp128v3")) {
302 *algo = OSMO_AUTH_ALG_COMP128v3;
303 *minlen = *maxlen = A38_COMP128_KEY_LEN;
304 } else if (!strcasecmp(alg_str, "xor")) {
305 *algo = OSMO_AUTH_ALG_XOR;
306 *minlen = A38_XOR_MIN_KEY_LEN;
307 *maxlen = A38_XOR_MAX_KEY_LEN;
308 } else if (!strcasecmp(alg_str, "milenage")) {
309 *algo = OSMO_AUTH_ALG_MILENAGE;
310 *minlen = *maxlen = MILENAGE_KEY_LEN;
311 } else
312 return false;
313 return true;
314}
315
316DEFUN(subscriber_no_aud2g,
317 subscriber_no_aud2g_cmd,
318 SUBSCR_UPDATE "aud2g none",
319 SUBSCR_UPDATE_HELP
320 "Set 2G authentication data\n"
321 "Delete 2G authentication data\n")
322{
323 struct hlr_subscriber subscr;
324 int rc;
325 const char *id_type = argv[0];
326 const char *id = argv[1];
327 struct sub_auth_data_str aud = {
328 .type = OSMO_AUTH_TYPE_GSM,
329 .algo = OSMO_AUTH_ALG_NONE,
330 };
331
332 if (get_subscr_by_argv(vty, id_type, id, &subscr))
333 return CMD_WARNING;
334
335 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud);
336
Harald Welte880a34d2018-03-01 21:32:01 +0100337 if (rc && rc != -ENOENT) {
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200338 vty_out(vty, "%% Error: cannot disable 2G auth data for IMSI='%s'%s",
339 subscr.imsi, VTY_NEWLINE);
340 return CMD_WARNING;
341 }
342 return CMD_SUCCESS;
343}
344
345DEFUN(subscriber_aud2g,
346 subscriber_aud2g_cmd,
347 SUBSCR_UPDATE "aud2g " AUTH_ALG_TYPES_2G " ki KI",
348 SUBSCR_UPDATE_HELP
349 "Set 2G authentication data\n"
350 AUTH_ALG_TYPES_2G_HELP
351 "Set Ki Encryption Key\n" "Ki as 32 hexadecimal characters\n")
352{
353 struct hlr_subscriber subscr;
354 int rc;
355 int minlen = 0;
356 int maxlen = 0;
357 const char *id_type = argv[0];
358 const char *id = argv[1];
359 const char *alg_type = argv[2];
360 const char *ki = argv[3];
361 struct sub_auth_data_str aud2g = {
362 .type = OSMO_AUTH_TYPE_GSM,
363 .u.gsm.ki = ki,
364 };
365
366 if (!auth_algo_parse(alg_type, &aud2g.algo, &minlen, &maxlen)) {
367 vty_out(vty, "%% Unknown auth algorithm: '%s'%s", alg_type, VTY_NEWLINE);
368 return CMD_WARNING;
369 }
370
371 if (!is_hexkey_valid(vty, "KI", aud2g.u.gsm.ki, minlen, maxlen))
372 return CMD_WARNING;
373
374 if (get_subscr_by_argv(vty, id_type, id, &subscr))
375 return CMD_WARNING;
376
377 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud2g);
378
379 if (rc) {
380 vty_out(vty, "%% Error: cannot set 2G auth data for IMSI='%s'%s",
381 subscr.imsi, VTY_NEWLINE);
382 return CMD_WARNING;
383 }
384 return CMD_SUCCESS;
385}
386
387DEFUN(subscriber_no_aud3g,
388 subscriber_no_aud3g_cmd,
389 SUBSCR_UPDATE "aud3g none",
390 SUBSCR_UPDATE_HELP
391 "Set UMTS authentication data (3G, and 2G with UMTS AKA)\n"
392 "Delete 3G authentication data\n")
393{
394 struct hlr_subscriber subscr;
395 int rc;
396 const char *id_type = argv[0];
397 const char *id = argv[1];
398 struct sub_auth_data_str aud = {
399 .type = OSMO_AUTH_TYPE_UMTS,
400 .algo = OSMO_AUTH_ALG_NONE,
401 };
402
403 if (get_subscr_by_argv(vty, id_type, id, &subscr))
404 return CMD_WARNING;
405
406 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud);
407
Harald Welte880a34d2018-03-01 21:32:01 +0100408 if (rc && rc != -ENOENT) {
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200409 vty_out(vty, "%% Error: cannot disable 3G auth data for IMSI='%s'%s",
410 subscr.imsi, VTY_NEWLINE);
411 return CMD_WARNING;
412 }
413 return CMD_SUCCESS;
414}
415
416DEFUN(subscriber_aud3g,
417 subscriber_aud3g_cmd,
418 SUBSCR_UPDATE "aud3g " AUTH_ALG_TYPES_3G
419 " k K"
420 " (op|opc) OP_C"
421 " [ind-bitlen] [<0-28>]",
422 SUBSCR_UPDATE_HELP
423 "Set UMTS authentication data (3G, and 2G with UMTS AKA)\n"
424 AUTH_ALG_TYPES_3G_HELP
425 "Set Encryption Key K\n" "K as 32 hexadecimal characters\n"
426 "Set OP key\n" "Set OPC key\n" "OP or OPC as 32 hexadecimal characters\n"
427 "Set IND bit length\n" "IND bit length value (default: 5)\n")
428{
429 struct hlr_subscriber subscr;
430 int minlen = 0;
431 int maxlen = 0;
432 int rc;
433 const char *id_type = argv[0];
434 const char *id = argv[1];
435 const char *alg_type = AUTH_ALG_TYPES_3G;
436 const char *k = argv[2];
437 bool opc_is_op = (strcasecmp("op", argv[3]) == 0);
438 const char *op_opc = argv[4];
439 int ind_bitlen = argc > 6? atoi(argv[6]) : 5;
440 struct sub_auth_data_str aud3g = {
441 .type = OSMO_AUTH_TYPE_UMTS,
442 .u.umts = {
443 .k = k,
444 .opc_is_op = opc_is_op,
445 .opc = op_opc,
446 .ind_bitlen = ind_bitlen,
447 },
448 };
449
450 if (!auth_algo_parse(alg_type, &aud3g.algo, &minlen, &maxlen)) {
451 vty_out(vty, "%% Unknown auth algorithm: '%s'%s", alg_type, VTY_NEWLINE);
452 return CMD_WARNING;
453 }
454
455 if (!is_hexkey_valid(vty, "K", aud3g.u.umts.k, minlen, maxlen))
456 return CMD_WARNING;
457
458 if (!is_hexkey_valid(vty, opc_is_op ? "OP" : "OPC", aud3g.u.umts.opc,
459 MILENAGE_KEY_LEN, MILENAGE_KEY_LEN))
460 return CMD_WARNING;
461
462 if (get_subscr_by_argv(vty, id_type, id, &subscr))
463 return CMD_WARNING;
464
465 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud3g);
466
467 if (rc) {
468 vty_out(vty, "%% Error: cannot set 3G auth data for IMSI='%s'%s",
469 subscr.imsi, VTY_NEWLINE);
470 return CMD_WARNING;
471 }
472 return CMD_SUCCESS;
473}
474
475void hlr_vty_subscriber_init(struct hlr *hlr)
476{
477 g_hlr = hlr;
478
479 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}