blob: c85106234b7206dcf70094e5e31dd8ae6733e085 [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>
Pau Espin Pedrol777860d2022-06-20 18:02:26 +020034#include <osmocom/hlr/hlr_vty.h>
Neels Hofmeyr183e7002017-10-06 02:59:54 +020035
36struct vty;
37
38#define hexdump_buf(buf) osmo_hexdump_nospc((void*)buf, sizeof(buf))
39
Neels Hofmeyrd9b36062018-12-25 17:23:41 +010040static char *get_datestr(const time_t *t, char *buf, size_t bufsize)
Stefan Sperling5c14c9c2018-12-07 12:30:21 +010041{
Neels Hofmeyrd9b36062018-12-25 17:23:41 +010042 struct tm tm;
43 gmtime_r(t, &tm);
44 strftime(buf, bufsize, "%FT%T+00:00", &tm);
45 return buf;
Stefan Sperling5c14c9c2018-12-07 12:30:21 +010046}
47
Keith89fda302021-01-19 07:01:33 +010048static 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 +010049{
Oliver Smithedc27ef2019-11-25 13:28:23 +010050 uint32_t age;
Neels Hofmeyrd9b36062018-12-25 17:23:41 +010051 char datebuf[32];
Neels Hofmeyr07e16022019-11-20 02:36:35 +010052 if (!last_lu_seen)
53 return;
Keith89fda302021-01-19 07:01:33 +010054 if (!only_age)
55 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 +010056 if (!timestamp_age(&last_lu_seen, &age))
57 vty_out(vty, " (invalid timestamp)%s", VTY_NEWLINE);
Neels Hofmeyr26b49052019-11-27 16:47:50 +010058 else {
59 vty_out(vty, " (");
60#define UNIT_AGO(UNITNAME, UNITVAL) \
61 if (age >= (UNITVAL)) { \
62 vty_out(vty, "%u%s", age / (UNITVAL), UNITNAME); \
63 age = age % (UNITVAL); \
64 }
65 UNIT_AGO("d", 60*60*24);
66 UNIT_AGO("h", 60*60);
67 UNIT_AGO("m", 60);
68 UNIT_AGO("s", 1);
Keith89fda302021-01-19 07:01:33 +010069 if (!only_age)
70 vty_out(vty, " ago)%s", VTY_NEWLINE);
71 else
72 vty_out(vty, " ago)");
Neels Hofmeyr26b49052019-11-27 16:47:50 +010073#undef UNIT_AGO
74 }
Neels Hofmeyr07e16022019-11-20 02:36:35 +010075}
76
Neels Hofmeyr183e7002017-10-06 02:59:54 +020077static void subscr_dump_full_vty(struct vty *vty, struct hlr_subscriber *subscr)
78{
79 int rc;
80 struct osmo_sub_auth_data aud2g;
81 struct osmo_sub_auth_data aud3g;
82
83 vty_out(vty, " ID: %"PRIu64"%s", subscr->id, VTY_NEWLINE);
84
Neels Hofmeyr36bec872017-10-23 18:44:23 +020085 vty_out(vty, " IMSI: %s%s", *subscr->imsi ? subscr->imsi : "none", VTY_NEWLINE);
Neels Hofmeyr183e7002017-10-06 02:59:54 +020086 vty_out(vty, " MSISDN: %s%s", *subscr->msisdn ? subscr->msisdn : "none", VTY_NEWLINE);
Oliver Smith02078b72019-01-11 15:41:29 +010087
88 if (*subscr->imei) {
89 char checksum = osmo_luhn(subscr->imei, 14);
90 if (checksum == -EINVAL)
91 vty_out(vty, " IMEI: %s (INVALID LENGTH!)%s", subscr->imei, VTY_NEWLINE);
92 else
93 vty_out(vty, " IMEI: %s%c%s", subscr->imei, checksum, VTY_NEWLINE);
94 }
95
Neels Hofmeyr183e7002017-10-06 02:59:54 +020096 if (*subscr->vlr_number)
97 vty_out(vty, " VLR number: %s%s", subscr->vlr_number, VTY_NEWLINE);
98 if (*subscr->sgsn_number)
99 vty_out(vty, " SGSN number: %s%s", subscr->sgsn_number, VTY_NEWLINE);
100 if (*subscr->sgsn_address)
101 vty_out(vty, " SGSN address: %s%s", subscr->sgsn_address, VTY_NEWLINE);
102 if (subscr->periodic_lu_timer)
103 vty_out(vty, " Periodic LU timer: %u%s", subscr->periodic_lu_timer, VTY_NEWLINE);
104 if (subscr->periodic_rau_tau_timer)
105 vty_out(vty, " Periodic RAU/TAU timer: %u%s", subscr->periodic_rau_tau_timer, VTY_NEWLINE);
106 if (subscr->lmsi)
107 vty_out(vty, " LMSI: %x%s", subscr->lmsi, VTY_NEWLINE);
108 if (!subscr->nam_cs)
109 vty_out(vty, " CS disabled%s", VTY_NEWLINE);
110 if (subscr->ms_purged_cs)
111 vty_out(vty, " CS purged%s", VTY_NEWLINE);
112 if (!subscr->nam_ps)
113 vty_out(vty, " PS disabled%s", VTY_NEWLINE);
114 if (subscr->ms_purged_ps)
115 vty_out(vty, " PS purged%s", VTY_NEWLINE);
Keith89fda302021-01-19 07:01:33 +0100116 dump_last_lu_seen(vty, "CS", subscr->last_lu_seen, false);
117 dump_last_lu_seen(vty, "PS", subscr->last_lu_seen_ps, false);
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200118
119 if (!*subscr->imsi)
120 return;
121
122 OSMO_ASSERT(g_hlr);
123 rc = db_get_auth_data(g_hlr->dbc, subscr->imsi, &aud2g, &aud3g, NULL);
124
Neels Hofmeyrbd1dca02017-11-23 15:25:30 +0100125 switch (rc) {
126 case 0:
127 break;
128 case -ENOENT:
129 case -ENOKEY:
130 aud2g.algo = OSMO_AUTH_ALG_NONE;
131 aud3g.algo = OSMO_AUTH_ALG_NONE;
132 break;
133 default:
134 vty_out(vty, "%% Error retrieving data from database (%d)%s", rc, VTY_NEWLINE);
135 return;
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200136 }
137
138 if (aud2g.type != OSMO_AUTH_TYPE_NONE && aud2g.type != OSMO_AUTH_TYPE_GSM) {
139 vty_out(vty, "%% Error: 2G auth data is not of type 'GSM'%s", VTY_NEWLINE);
140 aud2g = (struct osmo_sub_auth_data){};
141 }
142
143 if (aud3g.type != OSMO_AUTH_TYPE_NONE && aud3g.type != OSMO_AUTH_TYPE_UMTS) {
144 vty_out(vty, "%% Error: 3G auth data is not of type 'UMTS'%s", VTY_NEWLINE);
145 aud3g = (struct osmo_sub_auth_data){};
146 }
147
148 if (aud2g.algo != OSMO_AUTH_ALG_NONE && aud2g.type != OSMO_AUTH_TYPE_NONE) {
149 vty_out(vty, " 2G auth: %s%s",
150 osmo_auth_alg_name(aud2g.algo), VTY_NEWLINE);
151 vty_out(vty, " KI=%s%s",
152 hexdump_buf(aud2g.u.gsm.ki), VTY_NEWLINE);
153 }
154
155 if (aud3g.algo != OSMO_AUTH_ALG_NONE && aud3g.type != OSMO_AUTH_TYPE_NONE) {
156 vty_out(vty, " 3G auth: %s%s", osmo_auth_alg_name(aud3g.algo), VTY_NEWLINE);
157 vty_out(vty, " K=%s%s", hexdump_buf(aud3g.u.umts.k), VTY_NEWLINE);
158 vty_out(vty, " %s=%s%s", aud3g.u.umts.opc_is_op? "OP" : "OPC",
159 hexdump_buf(aud3g.u.umts.opc), VTY_NEWLINE);
160 vty_out(vty, " IND-bitlen=%u", aud3g.u.umts.ind_bitlen);
161 if (aud3g.u.umts.sqn)
162 vty_out(vty, " last-SQN=%"PRIu64, aud3g.u.umts.sqn);
163 vty_out(vty, VTY_NEWLINE);
164 }
165}
166
Keith89fda302021-01-19 07:01:33 +0100167static void subscr_dump_summary_vty(struct hlr_subscriber *subscr, void *data)
168{
169 struct vty *vty = data;
170 vty_out(vty, "%-5"PRIu64" %-12s %-16s", subscr->id,
171 *subscr->msisdn ? subscr->msisdn : "none",
172 *subscr->imsi ? subscr->imsi : "none");
173
174 if (*subscr->imei) {
175 char checksum = osmo_luhn(subscr->imei, 14);
176 if (checksum == -EINVAL)
177 vty_out(vty, " %-14s (INVALID LENGTH!)", subscr->imei);
178 else
179 vty_out(vty, " %-14s%c", subscr->imei, checksum);
180 } else {
181 vty_out(vty," ------------- ");
182 }
183 vty_out(vty, " %-2s%-2s ", subscr->nam_cs ? "CS" : "", subscr->nam_ps ? "PS" : "");
184 if (subscr->last_lu_seen)
185 dump_last_lu_seen(vty, "CS", subscr->last_lu_seen, true);
186 vty_out_newline(vty);
187}
188
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200189static int get_subscr_by_argv(struct vty *vty, const char *type, const char *id, struct hlr_subscriber *subscr)
190{
Oliver Smith02078b72019-01-11 15:41:29 +0100191 char imei_buf[GSM23003_IMEI_NUM_DIGITS_NO_CHK+1];
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200192 int rc = -1;
193 if (strcmp(type, "imsi") == 0)
194 rc = db_subscr_get_by_imsi(g_hlr->dbc, id, subscr);
195 else if (strcmp(type, "msisdn") == 0)
196 rc = db_subscr_get_by_msisdn(g_hlr->dbc, id, subscr);
197 else if (strcmp(type, "id") == 0)
198 rc = db_subscr_get_by_id(g_hlr->dbc, atoll(id), subscr);
Oliver Smith02078b72019-01-11 15:41:29 +0100199 else if (strcmp(type, "imei") == 0) {
200 /* Verify IMEI with checksum digit */
201 if (osmo_imei_str_valid(id, true)) {
202 /* Cut the checksum off */
203 osmo_strlcpy(imei_buf, id, sizeof(imei_buf));
204 id = imei_buf;
205 vty_out(vty, "%% Checksum validated and stripped for search: imei = '%s'%s", id,
206 VTY_NEWLINE);
207 }
208 rc = db_subscr_get_by_imei(g_hlr->dbc, id, subscr);
209 }
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200210 if (rc)
211 vty_out(vty, "%% No subscriber for %s = '%s'%s",
212 type, id, VTY_NEWLINE);
213 return rc;
214}
215
Keith89fda302021-01-19 07:01:33 +0100216static void dump_summary_table_vty(struct vty *vty, bool header, bool show_ls)
217{
218 const char *texts = "ID MSISDN IMSI IMEI NAM";
219 const char *lines = "----- ------------ ---------------- ---------------- -----";
220 const char *ls_text = " LAST SEEN";
221 const char *ls_line = " ------------";
222 if (header) {
223 if (!show_ls)
224 vty_out(vty, "%s%s%s%s", texts, VTY_NEWLINE, lines, VTY_NEWLINE);
225 else
226 vty_out(vty, "%s%s%s%s%s%s", texts, ls_text, VTY_NEWLINE, lines, ls_line, VTY_NEWLINE);
227 } else {
228 if (!show_ls)
229 vty_out(vty, "%s%s%s%s", lines, VTY_NEWLINE, texts, VTY_NEWLINE);
230 else
231 vty_out(vty, "%s%s%s%s%s%s", lines, ls_line, VTY_NEWLINE, texts, ls_text, VTY_NEWLINE);
232 }
233}
234
235static int get_subscrs(struct vty *vty, const char *filter_type, const char *filter)
236{
237 int rc = -1;
238 int count = 0;
239 const char *err;
240 bool show_ls = (filter_type && strcmp(filter_type, "last_lu_seen") == 0);
241 dump_summary_table_vty(vty, true, show_ls);
242 rc = db_subscrs_get(g_hlr->dbc, filter_type, filter, subscr_dump_summary_vty, vty, &count, &err);
243 if (count > 40) {
244 dump_summary_table_vty(vty, false, show_ls);
245 }
246 if (count > 0)
247 vty_out(vty, " Subscribers Shown: %d%s", count, VTY_NEWLINE);
248 if (rc)
249 vty_out(vty, "%% %s%s", err, VTY_NEWLINE);
250 return rc;
251}
252
253
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200254#define SUBSCR_CMD "subscriber "
255#define SUBSCR_CMD_HELP "Subscriber management commands\n"
Keithcc90bfd2021-01-19 06:55:22 +0100256#define SUBSCR_SHOW_HELP "Show subscriber information\n"
Keith89fda302021-01-19 07:01:33 +0100257#define SUBSCRS_SHOW_HELP "Show all subscribers (with filter possibility)\n"
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200258
Oliver Smith02078b72019-01-11 15:41:29 +0100259#define SUBSCR_ID "(imsi|msisdn|id|imei) IDENT"
Keithca8e6ef2021-05-07 05:59:21 +0200260#define SUBSCR_FILTER "(imei|imsi|msisdn) FILTER"
Keith89fda302021-01-19 07:01:33 +0100261
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200262#define SUBSCR_ID_HELP \
263 "Identify subscriber by IMSI\n" \
264 "Identify subscriber by MSISDN (phone number)\n" \
265 "Identify subscriber by database ID\n" \
Oliver Smith02078b72019-01-11 15:41:29 +0100266 "Identify subscriber by IMEI\n" \
267 "IMSI/MSISDN/ID/IMEI of the subscriber\n"
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200268
Neels Hofmeyr8aa780b2018-12-02 18:52:49 +0100269#define SUBSCR SUBSCR_CMD SUBSCR_ID " "
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200270#define SUBSCR_HELP SUBSCR_CMD_HELP SUBSCR_ID_HELP
271
272#define SUBSCR_UPDATE SUBSCR "update "
273#define SUBSCR_UPDATE_HELP SUBSCR_HELP "Set or update subscriber data\n"
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100274#define SUBSCR_MSISDN_HELP "Set MSISDN (phone number) of the subscriber\n"
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200275
276DEFUN(subscriber_show,
277 subscriber_show_cmd,
278 SUBSCR "show",
Keithcc90bfd2021-01-19 06:55:22 +0100279 SUBSCR_HELP SUBSCR_SHOW_HELP)
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200280{
281 struct hlr_subscriber subscr;
282 const char *id_type = argv[0];
283 const char *id = argv[1];
284
285 if (get_subscr_by_argv(vty, id_type, id, &subscr))
286 return CMD_WARNING;
287
288 subscr_dump_full_vty(vty, &subscr);
289 return CMD_SUCCESS;
290}
291
Neels Hofmeyr8aa780b2018-12-02 18:52:49 +0100292ALIAS(subscriber_show, show_subscriber_cmd,
293 "show " SUBSCR_CMD SUBSCR_ID,
Keithcc90bfd2021-01-19 06:55:22 +0100294 SHOW_STR SUBSCR_SHOW_HELP SUBSCR_ID_HELP);
Neels Hofmeyr8aa780b2018-12-02 18:52:49 +0100295
Keith89fda302021-01-19 07:01:33 +0100296DEFUN(show_subscriber_all,
297 show_subscriber_all_cmd,
298 "show subscribers all",
299 SHOW_STR SUBSCRS_SHOW_HELP "Show summary of all subscribers\n")
300{
301 if (get_subscrs(vty, NULL, NULL))
302 return CMD_WARNING;
303
304 return CMD_SUCCESS;
305}
306
307DEFUN(show_subscriber_filtered,
308 show_subscriber_filtered_cmd,
309 "show subscribers " SUBSCR_FILTER,
310 SHOW_STR SUBSCRS_SHOW_HELP
Keithca8e6ef2021-05-07 05:59:21 +0200311 "Filter Subscribers by IMEI\n" "Filter Subscribers by IMSI\n" "Filter Subscribers by MSISDN\n"
312 "String to match in imei, imsi or msisdn\n")
Keith89fda302021-01-19 07:01:33 +0100313{
314 const char *filter_type = argv[0];
315 const char *filter = argv[1];
316
317 if (get_subscrs(vty, filter_type, filter))
318 return CMD_WARNING;
319
320 return CMD_SUCCESS;
321}
322
323ALIAS(show_subscriber_filtered, show_subscriber_filtered_cmd2,
324 "show subscribers (cs|ps) (on|off)",
325 SHOW_STR SUBSCR_SHOW_HELP
326 "Filter Subscribers by CS Network Access Mode\n" "Filter Subscribers by PS Network Access Mode\n"
327 "Authorised\n" "Not Authorised\n");
328
329DEFUN(show_subscriber_order_last_seen, show_subscriber_order_last_seen_cmd,
330 "show subscribers last-seen",
331 SHOW_STR SUBSCR_SHOW_HELP "Show Subscribers Ordered by Last Seen Time\n")
332{
333 if (get_subscrs(vty, "last_lu_seen", NULL))
334 return CMD_WARNING;
335
336 return CMD_SUCCESS;
337}
338
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200339DEFUN(subscriber_create,
340 subscriber_create_cmd,
341 SUBSCR_CMD "imsi IDENT create",
342 SUBSCR_CMD_HELP
Vadim Yanitskiyf473c7b2018-07-30 14:29:39 +0700343 "Identify subscriber by IMSI\n"
344 "IMSI/MSISDN/ID of the subscriber\n"
345 "Create subscriber by IMSI\n")
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200346{
347 int rc;
348 struct hlr_subscriber subscr;
349 const char *imsi = argv[0];
Pau Espin Pedrolb74769f2022-06-20 16:48:12 +0200350
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200351 if (!osmo_imsi_str_valid(imsi)) {
352 vty_out(vty, "%% Not a valid IMSI: %s%s", imsi, VTY_NEWLINE);
353 return CMD_WARNING;
354 }
355
Oliver Smithcd2af5e2019-03-06 13:17:39 +0100356 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 +0200357
358 if (rc) {
359 if (rc == -EEXIST)
360 vty_out(vty, "%% Subscriber already exists for IMSI = %s%s",
361 imsi, VTY_NEWLINE);
362 else
363 vty_out(vty, "%% Error (rc=%d): cannot create subscriber for IMSI = %s%s",
364 rc, imsi, VTY_NEWLINE);
365 return CMD_WARNING;
366 }
367
368 rc = db_subscr_get_by_imsi(g_hlr->dbc, imsi, &subscr);
369 vty_out(vty, "%% Created subscriber %s%s", imsi, VTY_NEWLINE);
370
371 subscr_dump_full_vty(vty, &subscr);
372
373 return CMD_SUCCESS;
374}
375
376DEFUN(subscriber_delete,
377 subscriber_delete_cmd,
378 SUBSCR "delete",
379 SUBSCR_HELP "Delete subscriber from database\n")
380{
381 struct hlr_subscriber subscr;
382 int rc;
383 const char *id_type = argv[0];
384 const char *id = argv[1];
385
386 /* Find out the IMSI regardless of which way the caller decided to
387 * identify the subscriber by. */
388 if (get_subscr_by_argv(vty, id_type, id, &subscr))
389 return CMD_WARNING;
390
391 rc = db_subscr_delete_by_id(g_hlr->dbc, subscr.id);
392 if (rc) {
393 vty_out(vty, "%% Error: Failed to remove subscriber for IMSI '%s'%s",
394 subscr.imsi, VTY_NEWLINE);
395 return CMD_WARNING;
396 }
397
398 vty_out(vty, "%% Deleted subscriber for IMSI '%s'%s", subscr.imsi, VTY_NEWLINE);
399 return CMD_SUCCESS;
400}
401
402DEFUN(subscriber_msisdn,
403 subscriber_msisdn_cmd,
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100404 SUBSCR_UPDATE "msisdn (none|MSISDN)",
405 SUBSCR_UPDATE_HELP SUBSCR_MSISDN_HELP
406 "Remove MSISDN (phone number)\n"
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200407 "New MSISDN (phone number)\n")
408{
409 struct hlr_subscriber subscr;
410 const char *id_type = argv[0];
411 const char *id = argv[1];
412 const char *msisdn = argv[2];
413
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100414 if (strcmp(msisdn, "none") == 0)
415 msisdn = NULL;
416 else {
417 if (strlen(msisdn) > sizeof(subscr.msisdn) - 1) {
418 vty_out(vty, "%% MSISDN is too long, max. %zu characters are allowed%s",
419 sizeof(subscr.msisdn)-1, VTY_NEWLINE);
420 return CMD_WARNING;
421 }
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200422
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100423 if (!osmo_msisdn_str_valid(msisdn)) {
424 vty_out(vty, "%% MSISDN invalid: '%s'%s", msisdn, VTY_NEWLINE);
425 return CMD_WARNING;
426 }
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200427 }
428
429 if (get_subscr_by_argv(vty, id_type, id, &subscr))
430 return CMD_WARNING;
431
432 if (db_subscr_update_msisdn_by_imsi(g_hlr->dbc, subscr.imsi, msisdn)) {
433 vty_out(vty, "%% Error: cannot update MSISDN for subscriber IMSI='%s'%s",
434 subscr.imsi, VTY_NEWLINE);
435 return CMD_WARNING;
436 }
437
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100438 if (msisdn) {
439 vty_out(vty, "%% Updated subscriber IMSI='%s' to MSISDN='%s'%s",
440 subscr.imsi, msisdn, VTY_NEWLINE);
Stefan Sperlingf1622522018-04-09 11:39:16 +0200441
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100442 if (db_subscr_get_by_msisdn(g_hlr->dbc, msisdn, &subscr) == 0)
443 osmo_hlr_subscriber_update_notify(&subscr);
444 } else {
445 vty_out(vty, "%% Updated subscriber IMSI='%s': removed MSISDN%s",
446 subscr.imsi, VTY_NEWLINE);
447
Stefan Sperlingf1622522018-04-09 11:39:16 +0200448 osmo_hlr_subscriber_update_notify(&subscr);
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100449 }
Stefan Sperlingf1622522018-04-09 11:39:16 +0200450
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200451 return CMD_SUCCESS;
452}
453
454static bool is_hexkey_valid(struct vty *vty, const char *label,
455 const char *hex_str, int minlen, int maxlen)
456{
457 if (osmo_is_hexstr(hex_str, minlen * 2, maxlen * 2, true))
458 return true;
459 vty_out(vty, "%% Invalid value for %s: '%s'%s", label, hex_str, VTY_NEWLINE);
460 return false;
461}
462
463#define AUTH_ALG_TYPES_2G "(comp128v1|comp128v2|comp128v3|xor)"
464#define AUTH_ALG_TYPES_2G_HELP \
465 "Use COMP128v1 algorithm\n" \
466 "Use COMP128v2 algorithm\n" \
467 "Use COMP128v3 algorithm\n" \
468 "Use XOR algorithm\n"
469
470#define AUTH_ALG_TYPES_3G "milenage"
471#define AUTH_ALG_TYPES_3G_HELP \
472 "Use Milenage algorithm\n"
473
Pau Espin Pedrol1d0a0302022-06-20 16:48:45 +0200474bool auth_algo_parse(const char *alg_str, enum osmo_auth_algo *algo,
475 int *minlen, int *maxlen)
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200476{
477 if (!strcasecmp(alg_str, "none")) {
478 *algo = OSMO_AUTH_ALG_NONE;
479 *minlen = *maxlen = 0;
480 } else if (!strcasecmp(alg_str, "comp128v1")) {
481 *algo = OSMO_AUTH_ALG_COMP128v1;
482 *minlen = *maxlen = A38_COMP128_KEY_LEN;
483 } else if (!strcasecmp(alg_str, "comp128v2")) {
484 *algo = OSMO_AUTH_ALG_COMP128v2;
485 *minlen = *maxlen = A38_COMP128_KEY_LEN;
486 } else if (!strcasecmp(alg_str, "comp128v3")) {
487 *algo = OSMO_AUTH_ALG_COMP128v3;
488 *minlen = *maxlen = A38_COMP128_KEY_LEN;
489 } else if (!strcasecmp(alg_str, "xor")) {
490 *algo = OSMO_AUTH_ALG_XOR;
491 *minlen = A38_XOR_MIN_KEY_LEN;
492 *maxlen = A38_XOR_MAX_KEY_LEN;
493 } else if (!strcasecmp(alg_str, "milenage")) {
494 *algo = OSMO_AUTH_ALG_MILENAGE;
495 *minlen = *maxlen = MILENAGE_KEY_LEN;
496 } else
497 return false;
498 return true;
499}
500
501DEFUN(subscriber_no_aud2g,
502 subscriber_no_aud2g_cmd,
503 SUBSCR_UPDATE "aud2g none",
504 SUBSCR_UPDATE_HELP
505 "Set 2G authentication data\n"
506 "Delete 2G authentication data\n")
507{
508 struct hlr_subscriber subscr;
509 int rc;
510 const char *id_type = argv[0];
511 const char *id = argv[1];
512 struct sub_auth_data_str aud = {
513 .type = OSMO_AUTH_TYPE_GSM,
514 .algo = OSMO_AUTH_ALG_NONE,
515 };
516
517 if (get_subscr_by_argv(vty, id_type, id, &subscr))
518 return CMD_WARNING;
519
520 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud);
521
Harald Welte880a34d2018-03-01 21:32:01 +0100522 if (rc && rc != -ENOENT) {
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200523 vty_out(vty, "%% Error: cannot disable 2G auth data for IMSI='%s'%s",
524 subscr.imsi, VTY_NEWLINE);
525 return CMD_WARNING;
526 }
527 return CMD_SUCCESS;
528}
529
530DEFUN(subscriber_aud2g,
531 subscriber_aud2g_cmd,
532 SUBSCR_UPDATE "aud2g " AUTH_ALG_TYPES_2G " ki KI",
533 SUBSCR_UPDATE_HELP
534 "Set 2G authentication data\n"
535 AUTH_ALG_TYPES_2G_HELP
536 "Set Ki Encryption Key\n" "Ki as 32 hexadecimal characters\n")
537{
538 struct hlr_subscriber subscr;
539 int rc;
540 int minlen = 0;
541 int maxlen = 0;
542 const char *id_type = argv[0];
543 const char *id = argv[1];
544 const char *alg_type = argv[2];
545 const char *ki = argv[3];
546 struct sub_auth_data_str aud2g = {
547 .type = OSMO_AUTH_TYPE_GSM,
548 .u.gsm.ki = ki,
549 };
550
551 if (!auth_algo_parse(alg_type, &aud2g.algo, &minlen, &maxlen)) {
552 vty_out(vty, "%% Unknown auth algorithm: '%s'%s", alg_type, VTY_NEWLINE);
553 return CMD_WARNING;
554 }
555
556 if (!is_hexkey_valid(vty, "KI", aud2g.u.gsm.ki, minlen, maxlen))
557 return CMD_WARNING;
558
559 if (get_subscr_by_argv(vty, id_type, id, &subscr))
560 return CMD_WARNING;
561
562 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud2g);
563
564 if (rc) {
565 vty_out(vty, "%% Error: cannot set 2G auth data for IMSI='%s'%s",
566 subscr.imsi, VTY_NEWLINE);
567 return CMD_WARNING;
568 }
569 return CMD_SUCCESS;
570}
571
572DEFUN(subscriber_no_aud3g,
573 subscriber_no_aud3g_cmd,
574 SUBSCR_UPDATE "aud3g none",
575 SUBSCR_UPDATE_HELP
576 "Set UMTS authentication data (3G, and 2G with UMTS AKA)\n"
577 "Delete 3G authentication data\n")
578{
579 struct hlr_subscriber subscr;
580 int rc;
581 const char *id_type = argv[0];
582 const char *id = argv[1];
583 struct sub_auth_data_str aud = {
584 .type = OSMO_AUTH_TYPE_UMTS,
585 .algo = OSMO_AUTH_ALG_NONE,
586 };
587
588 if (get_subscr_by_argv(vty, id_type, id, &subscr))
589 return CMD_WARNING;
590
591 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud);
592
Harald Welte880a34d2018-03-01 21:32:01 +0100593 if (rc && rc != -ENOENT) {
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200594 vty_out(vty, "%% Error: cannot disable 3G auth data for IMSI='%s'%s",
595 subscr.imsi, VTY_NEWLINE);
596 return CMD_WARNING;
597 }
598 return CMD_SUCCESS;
599}
600
601DEFUN(subscriber_aud3g,
602 subscriber_aud3g_cmd,
603 SUBSCR_UPDATE "aud3g " AUTH_ALG_TYPES_3G
604 " k K"
605 " (op|opc) OP_C"
606 " [ind-bitlen] [<0-28>]",
607 SUBSCR_UPDATE_HELP
608 "Set UMTS authentication data (3G, and 2G with UMTS AKA)\n"
609 AUTH_ALG_TYPES_3G_HELP
610 "Set Encryption Key K\n" "K as 32 hexadecimal characters\n"
611 "Set OP key\n" "Set OPC key\n" "OP or OPC as 32 hexadecimal characters\n"
612 "Set IND bit length\n" "IND bit length value (default: 5)\n")
613{
614 struct hlr_subscriber subscr;
615 int minlen = 0;
616 int maxlen = 0;
617 int rc;
618 const char *id_type = argv[0];
619 const char *id = argv[1];
620 const char *alg_type = AUTH_ALG_TYPES_3G;
621 const char *k = argv[2];
622 bool opc_is_op = (strcasecmp("op", argv[3]) == 0);
623 const char *op_opc = argv[4];
624 int ind_bitlen = argc > 6? atoi(argv[6]) : 5;
625 struct sub_auth_data_str aud3g = {
626 .type = OSMO_AUTH_TYPE_UMTS,
627 .u.umts = {
628 .k = k,
629 .opc_is_op = opc_is_op,
630 .opc = op_opc,
631 .ind_bitlen = ind_bitlen,
632 },
633 };
Pau Espin Pedrolb74769f2022-06-20 16:48:12 +0200634
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200635 if (!auth_algo_parse(alg_type, &aud3g.algo, &minlen, &maxlen)) {
636 vty_out(vty, "%% Unknown auth algorithm: '%s'%s", alg_type, VTY_NEWLINE);
637 return CMD_WARNING;
638 }
639
640 if (!is_hexkey_valid(vty, "K", aud3g.u.umts.k, minlen, maxlen))
641 return CMD_WARNING;
642
643 if (!is_hexkey_valid(vty, opc_is_op ? "OP" : "OPC", aud3g.u.umts.opc,
644 MILENAGE_KEY_LEN, MILENAGE_KEY_LEN))
645 return CMD_WARNING;
646
647 if (get_subscr_by_argv(vty, id_type, id, &subscr))
648 return CMD_WARNING;
649
650 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud3g);
651
652 if (rc) {
653 vty_out(vty, "%% Error: cannot set 3G auth data for IMSI='%s'%s",
654 subscr.imsi, VTY_NEWLINE);
655 return CMD_WARNING;
656 }
657 return CMD_SUCCESS;
658}
659
Harald Welte6e237d32020-12-28 01:01:31 +0100660DEFUN(subscriber_aud3g_xor,
661 subscriber_aud3g_xor_cmd,
662 SUBSCR_UPDATE "aud3g xor k K"
663 " [ind-bitlen] [<0-28>]",
664 SUBSCR_UPDATE_HELP
665 "Set UMTS authentication data (3G, and 2G with UMTS AKA)\n"
666 "Use XOR algorithm\n"
667 "Set Encryption Key K\n" "K as 32 hexadecimal characters\n"
668 "Set IND bit length\n" "IND bit length value (default: 5)\n")
669{
670 struct hlr_subscriber subscr;
671 int minlen = 0;
672 int maxlen = 0;
673 int rc;
674 const char *id_type = argv[0];
675 const char *id = argv[1];
676 const char *k = argv[2];
677 int ind_bitlen = argc > 4? atoi(argv[4]) : 5;
678 struct sub_auth_data_str aud3g = {
679 .type = OSMO_AUTH_TYPE_UMTS,
680 .u.umts = {
681 .k = k,
682 .opc_is_op = 0,
683 .opc = "00000000000000000000000000000000",
684 .ind_bitlen = ind_bitlen,
685 },
686 };
687
688 if (!auth_algo_parse("xor", &aud3g.algo, &minlen, &maxlen)) {
689 vty_out(vty, "%% Unknown auth algorithm: '%s'%s", "xor", VTY_NEWLINE);
690 return CMD_WARNING;
691 }
692
693 if (!is_hexkey_valid(vty, "K", aud3g.u.umts.k, minlen, maxlen))
694 return CMD_WARNING;
695
696 if (get_subscr_by_argv(vty, id_type, id, &subscr))
697 return CMD_WARNING;
698
699 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud3g);
700
701 if (rc) {
702 vty_out(vty, "%% Error: cannot set 3G auth data for IMSI='%s'%s",
703 subscr.imsi, VTY_NEWLINE);
704 return CMD_WARNING;
705 }
706 return CMD_SUCCESS;
707}
708
Oliver Smith02078b72019-01-11 15:41:29 +0100709DEFUN(subscriber_imei,
710 subscriber_imei_cmd,
711 SUBSCR_UPDATE "imei (none|IMEI)",
712 SUBSCR_UPDATE_HELP
713 "Set IMEI of the subscriber (normally populated from MSC, no need to set this manually)\n"
714 "Forget IMEI\n"
715 "Set IMEI (use for debug only!)\n")
716{
717 struct hlr_subscriber subscr;
718 const char *id_type = argv[0];
719 const char *id = argv[1];
720 const char *imei = argv[2];
721 char imei_buf[GSM23003_IMEI_NUM_DIGITS_NO_CHK+1];
722
723 if (strcmp(imei, "none") == 0)
724 imei = NULL;
725 else {
726 /* Verify IMEI with checksum digit */
727 if (osmo_imei_str_valid(imei, true)) {
728 /* Cut the checksum off */
729 osmo_strlcpy(imei_buf, imei, sizeof(imei_buf));
730 imei = imei_buf;
731 } else if (!osmo_imei_str_valid(imei, false)) {
732 vty_out(vty, "%% IMEI invalid: '%s'%s", imei, VTY_NEWLINE);
733 return CMD_WARNING;
734 }
735 }
736
737 if (get_subscr_by_argv(vty, id_type, id, &subscr))
738 return CMD_WARNING;
739
740 if (db_subscr_update_imei_by_imsi(g_hlr->dbc, subscr.imsi, imei)) {
741 vty_out(vty, "%% Error: cannot update IMEI for subscriber IMSI='%s'%s",
742 subscr.imsi, VTY_NEWLINE);
743 return CMD_WARNING;
744 }
745
746 if (imei)
747 vty_out(vty, "%% Updated subscriber IMSI='%s' to IMEI='%s'%s",
748 subscr.imsi, imei, VTY_NEWLINE);
749 else
750 vty_out(vty, "%% Updated subscriber IMSI='%s': removed IMEI%s",
751 subscr.imsi, VTY_NEWLINE);
752
753 return CMD_SUCCESS;
754}
755
Oliver Smith3b33b012019-07-15 10:35:22 +0200756DEFUN(subscriber_nam,
757 subscriber_nam_cmd,
758 SUBSCR_UPDATE "network-access-mode (none|cs|ps|cs+ps)",
759 SUBSCR_UPDATE_HELP
760 "Set Network Access Mode (NAM) of the subscriber\n"
761 "Do not allow access to circuit switched or packet switched services\n"
762 "Allow access to circuit switched services only\n"
763 "Allow access to packet switched services only\n"
764 "Allow access to both circuit and packet switched services\n")
765{
766 struct hlr_subscriber subscr;
767 const char *id_type = argv[0];
768 const char *id = argv[1];
769 bool nam_cs = strstr(argv[2], "cs");
770 bool nam_ps = strstr(argv[2], "ps");
771
772 if (get_subscr_by_argv(vty, id_type, id, &subscr))
773 return CMD_WARNING;
774
775 if (nam_cs != subscr.nam_cs)
776 hlr_subscr_nam(g_hlr, &subscr, nam_cs, 0);
777 if (nam_ps != subscr.nam_ps)
778 hlr_subscr_nam(g_hlr, &subscr, nam_ps, 1);
779
780 return CMD_SUCCESS;
781}
782
Oliver Smith02078b72019-01-11 15:41:29 +0100783
Harald Welted5807b82018-07-29 12:27:41 +0200784void hlr_vty_subscriber_init(void)
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200785{
Keith89fda302021-01-19 07:01:33 +0100786 install_element_ve(&show_subscriber_all_cmd);
787 install_element_ve(&show_subscriber_filtered_cmd);
788 install_element_ve(&show_subscriber_filtered_cmd2);
789 install_element_ve(&show_subscriber_order_last_seen_cmd);
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200790 install_element_ve(&subscriber_show_cmd);
Neels Hofmeyr8aa780b2018-12-02 18:52:49 +0100791 install_element_ve(&show_subscriber_cmd);
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200792 install_element(ENABLE_NODE, &subscriber_create_cmd);
793 install_element(ENABLE_NODE, &subscriber_delete_cmd);
794 install_element(ENABLE_NODE, &subscriber_msisdn_cmd);
795 install_element(ENABLE_NODE, &subscriber_no_aud2g_cmd);
796 install_element(ENABLE_NODE, &subscriber_aud2g_cmd);
797 install_element(ENABLE_NODE, &subscriber_no_aud3g_cmd);
798 install_element(ENABLE_NODE, &subscriber_aud3g_cmd);
Harald Welte6e237d32020-12-28 01:01:31 +0100799 install_element(ENABLE_NODE, &subscriber_aud3g_xor_cmd);
Oliver Smith02078b72019-01-11 15:41:29 +0100800 install_element(ENABLE_NODE, &subscriber_imei_cmd);
Oliver Smith3b33b012019-07-15 10:35:22 +0200801 install_element(ENABLE_NODE, &subscriber_nam_cmd);
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200802}