blob: 3813393e5441aa176bf46cf7a7ab6f1a3a38f851 [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>
Stefan Sperling5c14c9c2018-12-07 12:30:21 +010023#include <sys/types.h>
24#include <time.h>
Neels Hofmeyr183e7002017-10-06 02:59:54 +020025
26#include <osmocom/gsm/gsm23003.h>
27#include <osmocom/vty/vty.h>
28#include <osmocom/vty/command.h>
29#include <osmocom/core/utils.h>
30
Neels Hofmeyr2f758032019-11-20 00:37:07 +010031#include <osmocom/hlr/hlr.h>
32#include <osmocom/hlr/db.h>
Oliver Smithedc27ef2019-11-25 13:28:23 +010033#include <osmocom/hlr/timestamp.h>
Neels Hofmeyr183e7002017-10-06 02:59:54 +020034
35struct vty;
36
37#define hexdump_buf(buf) osmo_hexdump_nospc((void*)buf, sizeof(buf))
38
Neels Hofmeyrd9b36062018-12-25 17:23:41 +010039static char *get_datestr(const time_t *t, char *buf, size_t bufsize)
Stefan Sperling5c14c9c2018-12-07 12:30:21 +010040{
Neels Hofmeyrd9b36062018-12-25 17:23:41 +010041 struct tm tm;
42 gmtime_r(t, &tm);
43 strftime(buf, bufsize, "%FT%T+00:00", &tm);
44 return buf;
Stefan Sperling5c14c9c2018-12-07 12:30:21 +010045}
46
Keith89fda302021-01-19 07:01:33 +010047static void dump_last_lu_seen(struct vty *vty, const char *domain_label, time_t last_lu_seen, bool only_age)
Neels Hofmeyr07e16022019-11-20 02:36:35 +010048{
Oliver Smithedc27ef2019-11-25 13:28:23 +010049 uint32_t age;
Neels Hofmeyrd9b36062018-12-25 17:23:41 +010050 char datebuf[32];
Neels Hofmeyr07e16022019-11-20 02:36:35 +010051 if (!last_lu_seen)
52 return;
Keith89fda302021-01-19 07:01:33 +010053 if (!only_age)
54 vty_out(vty, " last LU seen on %s: %s", domain_label, get_datestr(&last_lu_seen, datebuf, sizeof(datebuf)));
Oliver Smithedc27ef2019-11-25 13:28:23 +010055 if (!timestamp_age(&last_lu_seen, &age))
56 vty_out(vty, " (invalid timestamp)%s", VTY_NEWLINE);
Neels Hofmeyr26b49052019-11-27 16:47:50 +010057 else {
58 vty_out(vty, " (");
59#define UNIT_AGO(UNITNAME, UNITVAL) \
60 if (age >= (UNITVAL)) { \
61 vty_out(vty, "%u%s", age / (UNITVAL), UNITNAME); \
62 age = age % (UNITVAL); \
63 }
64 UNIT_AGO("d", 60*60*24);
65 UNIT_AGO("h", 60*60);
66 UNIT_AGO("m", 60);
67 UNIT_AGO("s", 1);
Keith89fda302021-01-19 07:01:33 +010068 if (!only_age)
69 vty_out(vty, " ago)%s", VTY_NEWLINE);
70 else
71 vty_out(vty, " ago)");
Neels Hofmeyr26b49052019-11-27 16:47:50 +010072#undef UNIT_AGO
73 }
Neels Hofmeyr07e16022019-11-20 02:36:35 +010074}
75
Neels Hofmeyr183e7002017-10-06 02:59:54 +020076static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
77{
78 int rc;
79 struct osmo_sub_auth_data aud2g;
80 struct osmo_sub_auth_data aud3g;
81
82 vty_out(vty, " ID: %"PRIu64"%s", subscr->id, VTY_NEWLINE);
83
Neels Hofmeyr36bec872017-10-23 18:44:23 +020084 vty_out(vty, " IMSI: %s%s", *subscr->imsi ? subscr->imsi : "none", VTY_NEWLINE);
Neels Hofmeyr183e7002017-10-06 02:59:54 +020085 vty_out(vty, " MSISDN: %s%s", *subscr->msisdn ? subscr->msisdn : "none", VTY_NEWLINE);
Oliver Smith02078b72019-01-11 15:41:29 +010086
87 if (*subscr->imei) {
88 char checksum = osmo_luhn(subscr->imei, 14);
89 if (checksum == -EINVAL)
90 vty_out(vty, " IMEI: %s (INVALID LENGTH!)%s", subscr->imei, VTY_NEWLINE);
91 else
92 vty_out(vty, " IMEI: %s%c%s", subscr->imei, checksum, VTY_NEWLINE);
93 }
94
Neels Hofmeyr183e7002017-10-06 02:59:54 +020095 if (*subscr->vlr_number)
96 vty_out(vty, " VLR number: %s%s", subscr->vlr_number, VTY_NEWLINE);
97 if (*subscr->sgsn_number)
98 vty_out(vty, " SGSN number: %s%s", subscr->sgsn_number, VTY_NEWLINE);
99 if (*subscr->sgsn_address)
100 vty_out(vty, " SGSN address: %s%s", subscr->sgsn_address, VTY_NEWLINE);
101 if (subscr->periodic_lu_timer)
102 vty_out(vty, " Periodic LU timer: %u%s", subscr->periodic_lu_timer, VTY_NEWLINE);
103 if (subscr->periodic_rau_tau_timer)
104 vty_out(vty, " Periodic RAU/TAU timer: %u%s", subscr->periodic_rau_tau_timer, VTY_NEWLINE);
105 if (subscr->lmsi)
106 vty_out(vty, " LMSI: %x%s", subscr->lmsi, VTY_NEWLINE);
107 if (!subscr->nam_cs)
108 vty_out(vty, " CS disabled%s", VTY_NEWLINE);
109 if (subscr->ms_purged_cs)
110 vty_out(vty, " CS purged%s", VTY_NEWLINE);
111 if (!subscr->nam_ps)
112 vty_out(vty, " PS disabled%s", VTY_NEWLINE);
113 if (subscr->ms_purged_ps)
114 vty_out(vty, " PS purged%s", VTY_NEWLINE);
Keith89fda302021-01-19 07:01:33 +0100115 dump_last_lu_seen(vty, "CS", subscr->last_lu_seen, false);
116 dump_last_lu_seen(vty, "PS", subscr->last_lu_seen_ps, false);
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200117
118 if (!*subscr->imsi)
119 return;
120
121 OSMO_ASSERT(g_hlr);
122 rc = db_get_auth_data(g_hlr->dbc, subscr->imsi, &aud2g, &aud3g, NULL);
123
Neels Hofmeyrbd1dca02017-11-23 15:25:30 +0100124 switch (rc) {
125 case 0:
126 break;
127 case -ENOENT:
128 case -ENOKEY:
129 aud2g.algo = OSMO_AUTH_ALG_NONE;
130 aud3g.algo = OSMO_AUTH_ALG_NONE;
131 break;
132 default:
133 vty_out(vty, "%% Error retrieving data from database (%d)%s", rc, VTY_NEWLINE);
134 return;
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200135 }
136
137 if (aud2g.type != OSMO_AUTH_TYPE_NONE && aud2g.type != OSMO_AUTH_TYPE_GSM) {
138 vty_out(vty, "%% Error: 2G auth data is not of type 'GSM'%s", VTY_NEWLINE);
139 aud2g = (struct osmo_sub_auth_data){};
140 }
141
142 if (aud3g.type != OSMO_AUTH_TYPE_NONE && aud3g.type != OSMO_AUTH_TYPE_UMTS) {
143 vty_out(vty, "%% Error: 3G auth data is not of type 'UMTS'%s", VTY_NEWLINE);
144 aud3g = (struct osmo_sub_auth_data){};
145 }
146
147 if (aud2g.algo != OSMO_AUTH_ALG_NONE && aud2g.type != OSMO_AUTH_TYPE_NONE) {
148 vty_out(vty, " 2G auth: %s%s",
149 osmo_auth_alg_name(aud2g.algo), VTY_NEWLINE);
150 vty_out(vty, " KI=%s%s",
151 hexdump_buf(aud2g.u.gsm.ki), VTY_NEWLINE);
152 }
153
154 if (aud3g.algo != OSMO_AUTH_ALG_NONE && aud3g.type != OSMO_AUTH_TYPE_NONE) {
155 vty_out(vty, " 3G auth: %s%s", osmo_auth_alg_name(aud3g.algo), VTY_NEWLINE);
156 vty_out(vty, " K=%s%s", hexdump_buf(aud3g.u.umts.k), VTY_NEWLINE);
157 vty_out(vty, " %s=%s%s", aud3g.u.umts.opc_is_op? "OP" : "OPC",
158 hexdump_buf(aud3g.u.umts.opc), VTY_NEWLINE);
159 vty_out(vty, " IND-bitlen=%u", aud3g.u.umts.ind_bitlen);
160 if (aud3g.u.umts.sqn)
161 vty_out(vty, " last-SQN=%"PRIu64, aud3g.u.umts.sqn);
162 vty_out(vty, VTY_NEWLINE);
163 }
164}
165
Keith89fda302021-01-19 07:01:33 +0100166static void subscr_dump_summary_vty(struct hlr_subscriber *subscr, void *data)
167{
168 struct vty *vty = data;
169 vty_out(vty, "%-5"PRIu64" %-12s %-16s", subscr->id,
170 *subscr->msisdn ? subscr->msisdn : "none",
171 *subscr->imsi ? subscr->imsi : "none");
172
173 if (*subscr->imei) {
174 char checksum = osmo_luhn(subscr->imei, 14);
175 if (checksum == -EINVAL)
176 vty_out(vty, " %-14s (INVALID LENGTH!)", subscr->imei);
177 else
178 vty_out(vty, " %-14s%c", subscr->imei, checksum);
179 } else {
180 vty_out(vty," ------------- ");
181 }
182 vty_out(vty, " %-2s%-2s ", subscr->nam_cs ? "CS" : "", subscr->nam_ps ? "PS" : "");
183 if (subscr->last_lu_seen)
184 dump_last_lu_seen(vty, "CS", subscr->last_lu_seen, true);
185 vty_out_newline(vty);
186}
187
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200188static int get_subscr_by_argv(struct vty *vty, const char *type, const char *id, struct hlr_subscriber *subscr)
189{
Oliver Smith02078b72019-01-11 15:41:29 +0100190 char imei_buf[GSM23003_IMEI_NUM_DIGITS_NO_CHK+1];
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200191 int rc = -1;
192 if (strcmp(type, "imsi") == 0)
193 rc = db_subscr_get_by_imsi(g_hlr->dbc, id, subscr);
194 else if (strcmp(type, "msisdn") == 0)
195 rc = db_subscr_get_by_msisdn(g_hlr->dbc, id, subscr);
196 else if (strcmp(type, "id") == 0)
197 rc = db_subscr_get_by_id(g_hlr->dbc, atoll(id), subscr);
Oliver Smith02078b72019-01-11 15:41:29 +0100198 else if (strcmp(type, "imei") == 0) {
199 /* Verify IMEI with checksum digit */
200 if (osmo_imei_str_valid(id, true)) {
201 /* Cut the checksum off */
202 osmo_strlcpy(imei_buf, id, sizeof(imei_buf));
203 id = imei_buf;
204 vty_out(vty, "%% Checksum validated and stripped for search: imei = '%s'%s", id,
205 VTY_NEWLINE);
206 }
207 rc = db_subscr_get_by_imei(g_hlr->dbc, id, subscr);
208 }
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200209 if (rc)
210 vty_out(vty, "%% No subscriber for %s = '%s'%s",
211 type, id, VTY_NEWLINE);
212 return rc;
213}
214
Keith89fda302021-01-19 07:01:33 +0100215static void dump_summary_table_vty(struct vty *vty, bool header, bool show_ls)
216{
217 const char *texts = "ID MSISDN IMSI IMEI NAM";
218 const char *lines = "----- ------------ ---------------- ---------------- -----";
219 const char *ls_text = " LAST SEEN";
220 const char *ls_line = " ------------";
221 if (header) {
222 if (!show_ls)
223 vty_out(vty, "%s%s%s%s", texts, VTY_NEWLINE, lines, VTY_NEWLINE);
224 else
225 vty_out(vty, "%s%s%s%s%s%s", texts, ls_text, VTY_NEWLINE, lines, ls_line, VTY_NEWLINE);
226 } else {
227 if (!show_ls)
228 vty_out(vty, "%s%s%s%s", lines, VTY_NEWLINE, texts, VTY_NEWLINE);
229 else
230 vty_out(vty, "%s%s%s%s%s%s", lines, ls_line, VTY_NEWLINE, texts, ls_text, VTY_NEWLINE);
231 }
232}
233
234static int get_subscrs(struct vty *vty, const char *filter_type, const char *filter)
235{
236 int rc = -1;
237 int count = 0;
238 const char *err;
239 bool show_ls = (filter_type && strcmp(filter_type, "last_lu_seen") == 0);
240 dump_summary_table_vty(vty, true, show_ls);
241 rc = db_subscrs_get(g_hlr->dbc, filter_type, filter, subscr_dump_summary_vty, vty, &count, &err);
242 if (count > 40) {
243 dump_summary_table_vty(vty, false, show_ls);
244 }
245 if (count > 0)
246 vty_out(vty, " Subscribers Shown: %d%s", count, VTY_NEWLINE);
247 if (rc)
248 vty_out(vty, "%% %s%s", err, VTY_NEWLINE);
249 return rc;
250}
251
252
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200253#define SUBSCR_CMD "subscriber "
254#define SUBSCR_CMD_HELP "Subscriber management commands\n"
Keithcc90bfd2021-01-19 06:55:22 +0100255#define SUBSCR_SHOW_HELP "Show subscriber information\n"
Keith89fda302021-01-19 07:01:33 +0100256#define SUBSCRS_SHOW_HELP "Show all subscribers (with filter possibility)\n"
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200257
Oliver Smith02078b72019-01-11 15:41:29 +0100258#define SUBSCR_ID "(imsi|msisdn|id|imei) IDENT"
Keithca8e6ef2021-05-07 05:59:21 +0200259#define SUBSCR_FILTER "(imei|imsi|msisdn) FILTER"
Keith89fda302021-01-19 07:01:33 +0100260
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200261#define SUBSCR_ID_HELP \
262 "Identify subscriber by IMSI\n" \
263 "Identify subscriber by MSISDN (phone number)\n" \
264 "Identify subscriber by database ID\n" \
Oliver Smith02078b72019-01-11 15:41:29 +0100265 "Identify subscriber by IMEI\n" \
266 "IMSI/MSISDN/ID/IMEI of the subscriber\n"
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200267
Neels Hofmeyr8aa780b2018-12-02 18:52:49 +0100268#define SUBSCR SUBSCR_CMD SUBSCR_ID " "
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200269#define SUBSCR_HELP SUBSCR_CMD_HELP SUBSCR_ID_HELP
270
271#define SUBSCR_UPDATE SUBSCR "update "
272#define SUBSCR_UPDATE_HELP SUBSCR_HELP "Set or update subscriber data\n"
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100273#define SUBSCR_MSISDN_HELP "Set MSISDN (phone number) of the subscriber\n"
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200274
275DEFUN(subscriber_show,
276 subscriber_show_cmd,
277 SUBSCR "show",
Keithcc90bfd2021-01-19 06:55:22 +0100278 SUBSCR_HELP SUBSCR_SHOW_HELP)
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200279{
280 struct hlr_subscriber subscr;
281 const char *id_type = argv[0];
282 const char *id = argv[1];
283
284 if (get_subscr_by_argv(vty, id_type, id, &subscr))
285 return CMD_WARNING;
286
287 subscr_dump_full_vty(vty, &subscr);
288 return CMD_SUCCESS;
289}
290
Neels Hofmeyr8aa780b2018-12-02 18:52:49 +0100291ALIAS(subscriber_show, show_subscriber_cmd,
292 "show " SUBSCR_CMD SUBSCR_ID,
Keithcc90bfd2021-01-19 06:55:22 +0100293 SHOW_STR SUBSCR_SHOW_HELP SUBSCR_ID_HELP);
Neels Hofmeyr8aa780b2018-12-02 18:52:49 +0100294
Keith89fda302021-01-19 07:01:33 +0100295DEFUN(show_subscriber_all,
296 show_subscriber_all_cmd,
297 "show subscribers all",
298 SHOW_STR SUBSCRS_SHOW_HELP "Show summary of all subscribers\n")
299{
300 if (get_subscrs(vty, NULL, NULL))
301 return CMD_WARNING;
302
303 return CMD_SUCCESS;
304}
305
306DEFUN(show_subscriber_filtered,
307 show_subscriber_filtered_cmd,
308 "show subscribers " SUBSCR_FILTER,
309 SHOW_STR SUBSCRS_SHOW_HELP
Keithca8e6ef2021-05-07 05:59:21 +0200310 "Filter Subscribers by IMEI\n" "Filter Subscribers by IMSI\n" "Filter Subscribers by MSISDN\n"
311 "String to match in imei, imsi or msisdn\n")
Keith89fda302021-01-19 07:01:33 +0100312{
313 const char *filter_type = argv[0];
314 const char *filter = argv[1];
315
316 if (get_subscrs(vty, filter_type, filter))
317 return CMD_WARNING;
318
319 return CMD_SUCCESS;
320}
321
322ALIAS(show_subscriber_filtered, show_subscriber_filtered_cmd2,
323 "show subscribers (cs|ps) (on|off)",
324 SHOW_STR SUBSCR_SHOW_HELP
325 "Filter Subscribers by CS Network Access Mode\n" "Filter Subscribers by PS Network Access Mode\n"
326 "Authorised\n" "Not Authorised\n");
327
328DEFUN(show_subscriber_order_last_seen, show_subscriber_order_last_seen_cmd,
329 "show subscribers last-seen",
330 SHOW_STR SUBSCR_SHOW_HELP "Show Subscribers Ordered by Last Seen Time\n")
331{
332 if (get_subscrs(vty, "last_lu_seen", NULL))
333 return CMD_WARNING;
334
335 return CMD_SUCCESS;
336}
337
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200338DEFUN(subscriber_create,
339 subscriber_create_cmd,
340 SUBSCR_CMD "imsi IDENT create",
341 SUBSCR_CMD_HELP
Vadim Yanitskiyf473c7b2018-07-30 14:29:39 +0700342 "Identify subscriber by IMSI\n"
343 "IMSI/MSISDN/ID of the subscriber\n"
344 "Create subscriber by IMSI\n")
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200345{
346 int rc;
347 struct hlr_subscriber subscr;
348 const char *imsi = argv[0];
Pau Espin Pedrolb74769f2022-06-20 16:48:12 +0200349
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200350 if (!osmo_imsi_str_valid(imsi)) {
351 vty_out(vty, "%% Not a valid IMSI: %s%s", imsi, VTY_NEWLINE);
352 return CMD_WARNING;
353 }
354
Oliver Smithcd2af5e2019-03-06 13:17:39 +0100355 rc = db_subscr_create(g_hlr->dbc, imsi, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS);
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200356
357 if (rc) {
358 if (rc == -EEXIST)
359 vty_out(vty, "%% Subscriber already exists for IMSI = %s%s",
360 imsi, VTY_NEWLINE);
361 else
362 vty_out(vty, "%% Error (rc=%d): cannot create subscriber for IMSI = %s%s",
363 rc, imsi, VTY_NEWLINE);
364 return CMD_WARNING;
365 }
366
367 rc = db_subscr_get_by_imsi(g_hlr->dbc, imsi, &subscr);
368 vty_out(vty, "%% Created subscriber %s%s", imsi, VTY_NEWLINE);
369
370 subscr_dump_full_vty(vty, &subscr);
371
372 return CMD_SUCCESS;
373}
374
375DEFUN(subscriber_delete,
376 subscriber_delete_cmd,
377 SUBSCR "delete",
378 SUBSCR_HELP "Delete subscriber from database\n")
379{
380 struct hlr_subscriber subscr;
381 int rc;
382 const char *id_type = argv[0];
383 const char *id = argv[1];
384
385 /* Find out the IMSI regardless of which way the caller decided to
386 * identify the subscriber by. */
387 if (get_subscr_by_argv(vty, id_type, id, &subscr))
388 return CMD_WARNING;
389
390 rc = db_subscr_delete_by_id(g_hlr->dbc, subscr.id);
391 if (rc) {
392 vty_out(vty, "%% Error: Failed to remove subscriber for IMSI '%s'%s",
393 subscr.imsi, VTY_NEWLINE);
394 return CMD_WARNING;
395 }
396
397 vty_out(vty, "%% Deleted subscriber for IMSI '%s'%s", subscr.imsi, VTY_NEWLINE);
398 return CMD_SUCCESS;
399}
400
401DEFUN(subscriber_msisdn,
402 subscriber_msisdn_cmd,
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100403 SUBSCR_UPDATE "msisdn (none|MSISDN)",
404 SUBSCR_UPDATE_HELP SUBSCR_MSISDN_HELP
405 "Remove MSISDN (phone number)\n"
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200406 "New MSISDN (phone number)\n")
407{
408 struct hlr_subscriber subscr;
409 const char *id_type = argv[0];
410 const char *id = argv[1];
411 const char *msisdn = argv[2];
412
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100413 if (strcmp(msisdn, "none") == 0)
414 msisdn = NULL;
415 else {
416 if (strlen(msisdn) > sizeof(subscr.msisdn) - 1) {
417 vty_out(vty, "%% MSISDN is too long, max. %zu characters are allowed%s",
418 sizeof(subscr.msisdn)-1, VTY_NEWLINE);
419 return CMD_WARNING;
420 }
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200421
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100422 if (!osmo_msisdn_str_valid(msisdn)) {
423 vty_out(vty, "%% MSISDN invalid: '%s'%s", msisdn, VTY_NEWLINE);
424 return CMD_WARNING;
425 }
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200426 }
427
428 if (get_subscr_by_argv(vty, id_type, id, &subscr))
429 return CMD_WARNING;
430
431 if (db_subscr_update_msisdn_by_imsi(g_hlr->dbc, subscr.imsi, msisdn)) {
432 vty_out(vty, "%% Error: cannot update MSISDN for subscriber IMSI='%s'%s",
433 subscr.imsi, VTY_NEWLINE);
434 return CMD_WARNING;
435 }
436
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100437 if (msisdn) {
438 vty_out(vty, "%% Updated subscriber IMSI='%s' to MSISDN='%s'%s",
439 subscr.imsi, msisdn, VTY_NEWLINE);
Stefan Sperlingf1622522018-04-09 11:39:16 +0200440
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100441 if (db_subscr_get_by_msisdn(g_hlr->dbc, msisdn, &subscr) == 0)
442 osmo_hlr_subscriber_update_notify(&subscr);
443 } else {
444 vty_out(vty, "%% Updated subscriber IMSI='%s': removed MSISDN%s",
445 subscr.imsi, VTY_NEWLINE);
446
Stefan Sperlingf1622522018-04-09 11:39:16 +0200447 osmo_hlr_subscriber_update_notify(&subscr);
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100448 }
Stefan Sperlingf1622522018-04-09 11:39:16 +0200449
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200450 return CMD_SUCCESS;
451}
452
453static bool is_hexkey_valid(struct vty *vty, const char *label,
454 const char *hex_str, int minlen, int maxlen)
455{
456 if (osmo_is_hexstr(hex_str, minlen * 2, maxlen * 2, true))
457 return true;
458 vty_out(vty, "%% Invalid value for %s: '%s'%s", label, hex_str, VTY_NEWLINE);
459 return false;
460}
461
462#define AUTH_ALG_TYPES_2G "(comp128v1|comp128v2|comp128v3|xor)"
463#define AUTH_ALG_TYPES_2G_HELP \
464 "Use COMP128v1 algorithm\n" \
465 "Use COMP128v2 algorithm\n" \
466 "Use COMP128v3 algorithm\n" \
467 "Use XOR algorithm\n"
468
469#define AUTH_ALG_TYPES_3G "milenage"
470#define AUTH_ALG_TYPES_3G_HELP \
471 "Use Milenage algorithm\n"
472
473#define A38_XOR_MIN_KEY_LEN 12
474#define A38_XOR_MAX_KEY_LEN 16
475#define A38_COMP128_KEY_LEN 16
476
477#define MILENAGE_KEY_LEN 16
478
479static bool auth_algo_parse(const char *alg_str, enum osmo_auth_algo *algo,
480 int *minlen, int *maxlen)
481{
482 if (!strcasecmp(alg_str, "none")) {
483 *algo = OSMO_AUTH_ALG_NONE;
484 *minlen = *maxlen = 0;
485 } else if (!strcasecmp(alg_str, "comp128v1")) {
486 *algo = OSMO_AUTH_ALG_COMP128v1;
487 *minlen = *maxlen = A38_COMP128_KEY_LEN;
488 } else if (!strcasecmp(alg_str, "comp128v2")) {
489 *algo = OSMO_AUTH_ALG_COMP128v2;
490 *minlen = *maxlen = A38_COMP128_KEY_LEN;
491 } else if (!strcasecmp(alg_str, "comp128v3")) {
492 *algo = OSMO_AUTH_ALG_COMP128v3;
493 *minlen = *maxlen = A38_COMP128_KEY_LEN;
494 } else if (!strcasecmp(alg_str, "xor")) {
495 *algo = OSMO_AUTH_ALG_XOR;
496 *minlen = A38_XOR_MIN_KEY_LEN;
497 *maxlen = A38_XOR_MAX_KEY_LEN;
498 } else if (!strcasecmp(alg_str, "milenage")) {
499 *algo = OSMO_AUTH_ALG_MILENAGE;
500 *minlen = *maxlen = MILENAGE_KEY_LEN;
501 } else
502 return false;
503 return true;
504}
505
506DEFUN(subscriber_no_aud2g,
507 subscriber_no_aud2g_cmd,
508 SUBSCR_UPDATE "aud2g none",
509 SUBSCR_UPDATE_HELP
510 "Set 2G authentication data\n"
511 "Delete 2G authentication data\n")
512{
513 struct hlr_subscriber subscr;
514 int rc;
515 const char *id_type = argv[0];
516 const char *id = argv[1];
517 struct sub_auth_data_str aud = {
518 .type = OSMO_AUTH_TYPE_GSM,
519 .algo = OSMO_AUTH_ALG_NONE,
520 };
521
522 if (get_subscr_by_argv(vty, id_type, id, &subscr))
523 return CMD_WARNING;
524
525 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud);
526
Harald Welte880a34d2018-03-01 21:32:01 +0100527 if (rc && rc != -ENOENT) {
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200528 vty_out(vty, "%% Error: cannot disable 2G auth data for IMSI='%s'%s",
529 subscr.imsi, VTY_NEWLINE);
530 return CMD_WARNING;
531 }
532 return CMD_SUCCESS;
533}
534
535DEFUN(subscriber_aud2g,
536 subscriber_aud2g_cmd,
537 SUBSCR_UPDATE "aud2g " AUTH_ALG_TYPES_2G " ki KI",
538 SUBSCR_UPDATE_HELP
539 "Set 2G authentication data\n"
540 AUTH_ALG_TYPES_2G_HELP
541 "Set Ki Encryption Key\n" "Ki as 32 hexadecimal characters\n")
542{
543 struct hlr_subscriber subscr;
544 int rc;
545 int minlen = 0;
546 int maxlen = 0;
547 const char *id_type = argv[0];
548 const char *id = argv[1];
549 const char *alg_type = argv[2];
550 const char *ki = argv[3];
551 struct sub_auth_data_str aud2g = {
552 .type = OSMO_AUTH_TYPE_GSM,
553 .u.gsm.ki = ki,
554 };
555
556 if (!auth_algo_parse(alg_type, &aud2g.algo, &minlen, &maxlen)) {
557 vty_out(vty, "%% Unknown auth algorithm: '%s'%s", alg_type, VTY_NEWLINE);
558 return CMD_WARNING;
559 }
560
561 if (!is_hexkey_valid(vty, "KI", aud2g.u.gsm.ki, minlen, maxlen))
562 return CMD_WARNING;
563
564 if (get_subscr_by_argv(vty, id_type, id, &subscr))
565 return CMD_WARNING;
566
567 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud2g);
568
569 if (rc) {
570 vty_out(vty, "%% Error: cannot set 2G auth data for IMSI='%s'%s",
571 subscr.imsi, VTY_NEWLINE);
572 return CMD_WARNING;
573 }
574 return CMD_SUCCESS;
575}
576
577DEFUN(subscriber_no_aud3g,
578 subscriber_no_aud3g_cmd,
579 SUBSCR_UPDATE "aud3g none",
580 SUBSCR_UPDATE_HELP
581 "Set UMTS authentication data (3G, and 2G with UMTS AKA)\n"
582 "Delete 3G authentication data\n")
583{
584 struct hlr_subscriber subscr;
585 int rc;
586 const char *id_type = argv[0];
587 const char *id = argv[1];
588 struct sub_auth_data_str aud = {
589 .type = OSMO_AUTH_TYPE_UMTS,
590 .algo = OSMO_AUTH_ALG_NONE,
591 };
592
593 if (get_subscr_by_argv(vty, id_type, id, &subscr))
594 return CMD_WARNING;
595
596 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud);
597
Harald Welte880a34d2018-03-01 21:32:01 +0100598 if (rc && rc != -ENOENT) {
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200599 vty_out(vty, "%% Error: cannot disable 3G auth data for IMSI='%s'%s",
600 subscr.imsi, VTY_NEWLINE);
601 return CMD_WARNING;
602 }
603 return CMD_SUCCESS;
604}
605
606DEFUN(subscriber_aud3g,
607 subscriber_aud3g_cmd,
608 SUBSCR_UPDATE "aud3g " AUTH_ALG_TYPES_3G
609 " k K"
610 " (op|opc) OP_C"
611 " [ind-bitlen] [<0-28>]",
612 SUBSCR_UPDATE_HELP
613 "Set UMTS authentication data (3G, and 2G with UMTS AKA)\n"
614 AUTH_ALG_TYPES_3G_HELP
615 "Set Encryption Key K\n" "K as 32 hexadecimal characters\n"
616 "Set OP key\n" "Set OPC key\n" "OP or OPC as 32 hexadecimal characters\n"
617 "Set IND bit length\n" "IND bit length value (default: 5)\n")
618{
619 struct hlr_subscriber subscr;
620 int minlen = 0;
621 int maxlen = 0;
622 int rc;
623 const char *id_type = argv[0];
624 const char *id = argv[1];
625 const char *alg_type = AUTH_ALG_TYPES_3G;
626 const char *k = argv[2];
627 bool opc_is_op = (strcasecmp("op", argv[3]) == 0);
628 const char *op_opc = argv[4];
629 int ind_bitlen = argc > 6? atoi(argv[6]) : 5;
630 struct sub_auth_data_str aud3g = {
631 .type = OSMO_AUTH_TYPE_UMTS,
632 .u.umts = {
633 .k = k,
634 .opc_is_op = opc_is_op,
635 .opc = op_opc,
636 .ind_bitlen = ind_bitlen,
637 },
638 };
Pau Espin Pedrolb74769f2022-06-20 16:48:12 +0200639
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200640 if (!auth_algo_parse(alg_type, &aud3g.algo, &minlen, &maxlen)) {
641 vty_out(vty, "%% Unknown auth algorithm: '%s'%s", alg_type, VTY_NEWLINE);
642 return CMD_WARNING;
643 }
644
645 if (!is_hexkey_valid(vty, "K", aud3g.u.umts.k, minlen, maxlen))
646 return CMD_WARNING;
647
648 if (!is_hexkey_valid(vty, opc_is_op ? "OP" : "OPC", aud3g.u.umts.opc,
649 MILENAGE_KEY_LEN, MILENAGE_KEY_LEN))
650 return CMD_WARNING;
651
652 if (get_subscr_by_argv(vty, id_type, id, &subscr))
653 return CMD_WARNING;
654
655 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud3g);
656
657 if (rc) {
658 vty_out(vty, "%% Error: cannot set 3G auth data for IMSI='%s'%s",
659 subscr.imsi, VTY_NEWLINE);
660 return CMD_WARNING;
661 }
662 return CMD_SUCCESS;
663}
664
Harald Welte6e237d32020-12-28 01:01:31 +0100665DEFUN(subscriber_aud3g_xor,
666 subscriber_aud3g_xor_cmd,
667 SUBSCR_UPDATE "aud3g xor k K"
668 " [ind-bitlen] [<0-28>]",
669 SUBSCR_UPDATE_HELP
670 "Set UMTS authentication data (3G, and 2G with UMTS AKA)\n"
671 "Use XOR algorithm\n"
672 "Set Encryption Key K\n" "K as 32 hexadecimal characters\n"
673 "Set IND bit length\n" "IND bit length value (default: 5)\n")
674{
675 struct hlr_subscriber subscr;
676 int minlen = 0;
677 int maxlen = 0;
678 int rc;
679 const char *id_type = argv[0];
680 const char *id = argv[1];
681 const char *k = argv[2];
682 int ind_bitlen = argc > 4? atoi(argv[4]) : 5;
683 struct sub_auth_data_str aud3g = {
684 .type = OSMO_AUTH_TYPE_UMTS,
685 .u.umts = {
686 .k = k,
687 .opc_is_op = 0,
688 .opc = "00000000000000000000000000000000",
689 .ind_bitlen = ind_bitlen,
690 },
691 };
692
693 if (!auth_algo_parse("xor", &aud3g.algo, &minlen, &maxlen)) {
694 vty_out(vty, "%% Unknown auth algorithm: '%s'%s", "xor", VTY_NEWLINE);
695 return CMD_WARNING;
696 }
697
698 if (!is_hexkey_valid(vty, "K", aud3g.u.umts.k, minlen, maxlen))
699 return CMD_WARNING;
700
701 if (get_subscr_by_argv(vty, id_type, id, &subscr))
702 return CMD_WARNING;
703
704 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud3g);
705
706 if (rc) {
707 vty_out(vty, "%% Error: cannot set 3G auth data for IMSI='%s'%s",
708 subscr.imsi, VTY_NEWLINE);
709 return CMD_WARNING;
710 }
711 return CMD_SUCCESS;
712}
713
Oliver Smith02078b72019-01-11 15:41:29 +0100714DEFUN(subscriber_imei,
715 subscriber_imei_cmd,
716 SUBSCR_UPDATE "imei (none|IMEI)",
717 SUBSCR_UPDATE_HELP
718 "Set IMEI of the subscriber (normally populated from MSC, no need to set this manually)\n"
719 "Forget IMEI\n"
720 "Set IMEI (use for debug only!)\n")
721{
722 struct hlr_subscriber subscr;
723 const char *id_type = argv[0];
724 const char *id = argv[1];
725 const char *imei = argv[2];
726 char imei_buf[GSM23003_IMEI_NUM_DIGITS_NO_CHK+1];
727
728 if (strcmp(imei, "none") == 0)
729 imei = NULL;
730 else {
731 /* Verify IMEI with checksum digit */
732 if (osmo_imei_str_valid(imei, true)) {
733 /* Cut the checksum off */
734 osmo_strlcpy(imei_buf, imei, sizeof(imei_buf));
735 imei = imei_buf;
736 } else if (!osmo_imei_str_valid(imei, false)) {
737 vty_out(vty, "%% IMEI invalid: '%s'%s", imei, VTY_NEWLINE);
738 return CMD_WARNING;
739 }
740 }
741
742 if (get_subscr_by_argv(vty, id_type, id, &subscr))
743 return CMD_WARNING;
744
745 if (db_subscr_update_imei_by_imsi(g_hlr->dbc, subscr.imsi, imei)) {
746 vty_out(vty, "%% Error: cannot update IMEI for subscriber IMSI='%s'%s",
747 subscr.imsi, VTY_NEWLINE);
748 return CMD_WARNING;
749 }
750
751 if (imei)
752 vty_out(vty, "%% Updated subscriber IMSI='%s' to IMEI='%s'%s",
753 subscr.imsi, imei, VTY_NEWLINE);
754 else
755 vty_out(vty, "%% Updated subscriber IMSI='%s': removed IMEI%s",
756 subscr.imsi, VTY_NEWLINE);
757
758 return CMD_SUCCESS;
759}
760
Oliver Smith3b33b012019-07-15 10:35:22 +0200761DEFUN(subscriber_nam,
762 subscriber_nam_cmd,
763 SUBSCR_UPDATE "network-access-mode (none|cs|ps|cs+ps)",
764 SUBSCR_UPDATE_HELP
765 "Set Network Access Mode (NAM) of the subscriber\n"
766 "Do not allow access to circuit switched or packet switched services\n"
767 "Allow access to circuit switched services only\n"
768 "Allow access to packet switched services only\n"
769 "Allow access to both circuit and packet switched services\n")
770{
771 struct hlr_subscriber subscr;
772 const char *id_type = argv[0];
773 const char *id = argv[1];
774 bool nam_cs = strstr(argv[2], "cs");
775 bool nam_ps = strstr(argv[2], "ps");
776
777 if (get_subscr_by_argv(vty, id_type, id, &subscr))
778 return CMD_WARNING;
779
780 if (nam_cs != subscr.nam_cs)
781 hlr_subscr_nam(g_hlr, &subscr, nam_cs, 0);
782 if (nam_ps != subscr.nam_ps)
783 hlr_subscr_nam(g_hlr, &subscr, nam_ps, 1);
784
785 return CMD_SUCCESS;
786}
787
Oliver Smith02078b72019-01-11 15:41:29 +0100788
Harald Welted5807b82018-07-29 12:27:41 +0200789void hlr_vty_subscriber_init(void)
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200790{
Keith89fda302021-01-19 07:01:33 +0100791 install_element_ve(&show_subscriber_all_cmd);
792 install_element_ve(&show_subscriber_filtered_cmd);
793 install_element_ve(&show_subscriber_filtered_cmd2);
794 install_element_ve(&show_subscriber_order_last_seen_cmd);
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200795 install_element_ve(&subscriber_show_cmd);
Neels Hofmeyr8aa780b2018-12-02 18:52:49 +0100796 install_element_ve(&show_subscriber_cmd);
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200797 install_element(ENABLE_NODE, &subscriber_create_cmd);
798 install_element(ENABLE_NODE, &subscriber_delete_cmd);
799 install_element(ENABLE_NODE, &subscriber_msisdn_cmd);
800 install_element(ENABLE_NODE, &subscriber_no_aud2g_cmd);
801 install_element(ENABLE_NODE, &subscriber_aud2g_cmd);
802 install_element(ENABLE_NODE, &subscriber_no_aud3g_cmd);
803 install_element(ENABLE_NODE, &subscriber_aud3g_cmd);
Harald Welte6e237d32020-12-28 01:01:31 +0100804 install_element(ENABLE_NODE, &subscriber_aud3g_xor_cmd);
Oliver Smith02078b72019-01-11 15:41:29 +0100805 install_element(ENABLE_NODE, &subscriber_imei_cmd);
Oliver Smith3b33b012019-07-15 10:35:22 +0200806 install_element(ENABLE_NODE, &subscriber_nam_cmd);
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200807}