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