blob: 59a27c193256f2f4fa3936ccf6d43ef86662f876 [file] [log] [blame]
Neels Hofmeyr183e7002017-10-06 02:59:54 +02001/* OsmoHLR subscriber management VTY implementation */
Harald Weltea854b482023-05-30 17:27:32 +02002/* (C) 2017-2023 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
Neels Hofmeyr183e7002017-10-06 02:59:54 +02003 * 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;
Harald Weltea854b482023-05-30 17:27:32 +020080 struct osmo_sub_auth_data2 aud2g;
81 struct osmo_sub_auth_data2 aud3g;
Neels Hofmeyr183e7002017-10-06 02:59:54 +020082
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);
Harald Weltea854b482023-05-30 17:27:32 +0200140 aud2g = (struct osmo_sub_auth_data2){};
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200141 }
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);
Harald Weltea854b482023-05-30 17:27:32 +0200145 aud3g = (struct osmo_sub_auth_data2){};
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200146 }
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);
Harald Weltea854b482023-05-30 17:27:32 +0200157 vty_out(vty, " K=%s%s",
158 osmo_hexdump_nospc(aud3g.u.umts.k, aud3g.u.umts.k_len), VTY_NEWLINE);
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200159 vty_out(vty, " %s=%s%s", aud3g.u.umts.opc_is_op? "OP" : "OPC",
Harald Weltea854b482023-05-30 17:27:32 +0200160 osmo_hexdump_nospc(aud3g.u.umts.opc, aud3g.u.umts.opc_len), VTY_NEWLINE);
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200161 vty_out(vty, " IND-bitlen=%u", aud3g.u.umts.ind_bitlen);
162 if (aud3g.u.umts.sqn)
163 vty_out(vty, " last-SQN=%"PRIu64, aud3g.u.umts.sqn);
164 vty_out(vty, VTY_NEWLINE);
165 }
166}
167
Keith89fda302021-01-19 07:01:33 +0100168static void subscr_dump_summary_vty(struct hlr_subscriber *subscr, void *data)
169{
170 struct vty *vty = data;
171 vty_out(vty, "%-5"PRIu64" %-12s %-16s", subscr->id,
172 *subscr->msisdn ? subscr->msisdn : "none",
173 *subscr->imsi ? subscr->imsi : "none");
174
175 if (*subscr->imei) {
176 char checksum = osmo_luhn(subscr->imei, 14);
177 if (checksum == -EINVAL)
178 vty_out(vty, " %-14s (INVALID LENGTH!)", subscr->imei);
179 else
180 vty_out(vty, " %-14s%c", subscr->imei, checksum);
181 } else {
182 vty_out(vty," ------------- ");
183 }
184 vty_out(vty, " %-2s%-2s ", subscr->nam_cs ? "CS" : "", subscr->nam_ps ? "PS" : "");
185 if (subscr->last_lu_seen)
186 dump_last_lu_seen(vty, "CS", subscr->last_lu_seen, true);
187 vty_out_newline(vty);
188}
189
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200190static int get_subscr_by_argv(struct vty *vty, const char *type, const char *id, struct hlr_subscriber *subscr)
191{
Oliver Smith02078b72019-01-11 15:41:29 +0100192 char imei_buf[GSM23003_IMEI_NUM_DIGITS_NO_CHK+1];
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200193 int rc = -1;
194 if (strcmp(type, "imsi") == 0)
195 rc = db_subscr_get_by_imsi(g_hlr->dbc, id, subscr);
196 else if (strcmp(type, "msisdn") == 0)
197 rc = db_subscr_get_by_msisdn(g_hlr->dbc, id, subscr);
198 else if (strcmp(type, "id") == 0)
199 rc = db_subscr_get_by_id(g_hlr->dbc, atoll(id), subscr);
Oliver Smith02078b72019-01-11 15:41:29 +0100200 else if (strcmp(type, "imei") == 0) {
201 /* Verify IMEI with checksum digit */
202 if (osmo_imei_str_valid(id, true)) {
203 /* Cut the checksum off */
204 osmo_strlcpy(imei_buf, id, sizeof(imei_buf));
205 id = imei_buf;
206 vty_out(vty, "%% Checksum validated and stripped for search: imei = '%s'%s", id,
207 VTY_NEWLINE);
208 }
209 rc = db_subscr_get_by_imei(g_hlr->dbc, id, subscr);
210 }
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200211 if (rc)
212 vty_out(vty, "%% No subscriber for %s = '%s'%s",
213 type, id, VTY_NEWLINE);
214 return rc;
215}
216
Keith89fda302021-01-19 07:01:33 +0100217static void dump_summary_table_vty(struct vty *vty, bool header, bool show_ls)
218{
219 const char *texts = "ID MSISDN IMSI IMEI NAM";
220 const char *lines = "----- ------------ ---------------- ---------------- -----";
221 const char *ls_text = " LAST SEEN";
222 const char *ls_line = " ------------";
223 if (header) {
224 if (!show_ls)
225 vty_out(vty, "%s%s%s%s", texts, VTY_NEWLINE, lines, VTY_NEWLINE);
226 else
227 vty_out(vty, "%s%s%s%s%s%s", texts, ls_text, VTY_NEWLINE, lines, ls_line, VTY_NEWLINE);
228 } else {
229 if (!show_ls)
230 vty_out(vty, "%s%s%s%s", lines, VTY_NEWLINE, texts, VTY_NEWLINE);
231 else
232 vty_out(vty, "%s%s%s%s%s%s", lines, ls_line, VTY_NEWLINE, texts, ls_text, VTY_NEWLINE);
233 }
234}
235
236static int get_subscrs(struct vty *vty, const char *filter_type, const char *filter)
237{
238 int rc = -1;
239 int count = 0;
240 const char *err;
241 bool show_ls = (filter_type && strcmp(filter_type, "last_lu_seen") == 0);
242 dump_summary_table_vty(vty, true, show_ls);
243 rc = db_subscrs_get(g_hlr->dbc, filter_type, filter, subscr_dump_summary_vty, vty, &count, &err);
244 if (count > 40) {
245 dump_summary_table_vty(vty, false, show_ls);
246 }
247 if (count > 0)
248 vty_out(vty, " Subscribers Shown: %d%s", count, VTY_NEWLINE);
249 if (rc)
250 vty_out(vty, "%% %s%s", err, VTY_NEWLINE);
251 return rc;
252}
253
254
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200255#define SUBSCR_CMD "subscriber "
256#define SUBSCR_CMD_HELP "Subscriber management commands\n"
Keithcc90bfd2021-01-19 06:55:22 +0100257#define SUBSCR_SHOW_HELP "Show subscriber information\n"
Keith89fda302021-01-19 07:01:33 +0100258#define SUBSCRS_SHOW_HELP "Show all subscribers (with filter possibility)\n"
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200259
Oliver Smith02078b72019-01-11 15:41:29 +0100260#define SUBSCR_ID "(imsi|msisdn|id|imei) IDENT"
Keithca8e6ef2021-05-07 05:59:21 +0200261#define SUBSCR_FILTER "(imei|imsi|msisdn) FILTER"
Keith89fda302021-01-19 07:01:33 +0100262
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200263#define SUBSCR_ID_HELP \
264 "Identify subscriber by IMSI\n" \
265 "Identify subscriber by MSISDN (phone number)\n" \
266 "Identify subscriber by database ID\n" \
Oliver Smith02078b72019-01-11 15:41:29 +0100267 "Identify subscriber by IMEI\n" \
268 "IMSI/MSISDN/ID/IMEI of the subscriber\n"
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200269
Neels Hofmeyr8aa780b2018-12-02 18:52:49 +0100270#define SUBSCR SUBSCR_CMD SUBSCR_ID " "
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200271#define SUBSCR_HELP SUBSCR_CMD_HELP SUBSCR_ID_HELP
272
273#define SUBSCR_UPDATE SUBSCR "update "
274#define SUBSCR_UPDATE_HELP SUBSCR_HELP "Set or update subscriber data\n"
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100275#define SUBSCR_MSISDN_HELP "Set MSISDN (phone number) of the subscriber\n"
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200276
277DEFUN(subscriber_show,
278 subscriber_show_cmd,
279 SUBSCR "show",
Keithcc90bfd2021-01-19 06:55:22 +0100280 SUBSCR_HELP SUBSCR_SHOW_HELP)
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200281{
282 struct hlr_subscriber subscr;
283 const char *id_type = argv[0];
284 const char *id = argv[1];
285
286 if (get_subscr_by_argv(vty, id_type, id, &subscr))
287 return CMD_WARNING;
288
289 subscr_dump_full_vty(vty, &subscr);
290 return CMD_SUCCESS;
291}
292
Neels Hofmeyr8aa780b2018-12-02 18:52:49 +0100293ALIAS(subscriber_show, show_subscriber_cmd,
294 "show " SUBSCR_CMD SUBSCR_ID,
Keithcc90bfd2021-01-19 06:55:22 +0100295 SHOW_STR SUBSCR_SHOW_HELP SUBSCR_ID_HELP);
Neels Hofmeyr8aa780b2018-12-02 18:52:49 +0100296
Keith89fda302021-01-19 07:01:33 +0100297DEFUN(show_subscriber_all,
298 show_subscriber_all_cmd,
299 "show subscribers all",
300 SHOW_STR SUBSCRS_SHOW_HELP "Show summary of all subscribers\n")
301{
302 if (get_subscrs(vty, NULL, NULL))
303 return CMD_WARNING;
304
305 return CMD_SUCCESS;
306}
307
308DEFUN(show_subscriber_filtered,
309 show_subscriber_filtered_cmd,
310 "show subscribers " SUBSCR_FILTER,
311 SHOW_STR SUBSCRS_SHOW_HELP
Keithca8e6ef2021-05-07 05:59:21 +0200312 "Filter Subscribers by IMEI\n" "Filter Subscribers by IMSI\n" "Filter Subscribers by MSISDN\n"
313 "String to match in imei, imsi or msisdn\n")
Keith89fda302021-01-19 07:01:33 +0100314{
315 const char *filter_type = argv[0];
316 const char *filter = argv[1];
317
318 if (get_subscrs(vty, filter_type, filter))
319 return CMD_WARNING;
320
321 return CMD_SUCCESS;
322}
323
324ALIAS(show_subscriber_filtered, show_subscriber_filtered_cmd2,
325 "show subscribers (cs|ps) (on|off)",
326 SHOW_STR SUBSCR_SHOW_HELP
327 "Filter Subscribers by CS Network Access Mode\n" "Filter Subscribers by PS Network Access Mode\n"
328 "Authorised\n" "Not Authorised\n");
329
330DEFUN(show_subscriber_order_last_seen, show_subscriber_order_last_seen_cmd,
331 "show subscribers last-seen",
332 SHOW_STR SUBSCR_SHOW_HELP "Show Subscribers Ordered by Last Seen Time\n")
333{
334 if (get_subscrs(vty, "last_lu_seen", NULL))
335 return CMD_WARNING;
336
337 return CMD_SUCCESS;
338}
339
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200340DEFUN(subscriber_create,
341 subscriber_create_cmd,
342 SUBSCR_CMD "imsi IDENT create",
343 SUBSCR_CMD_HELP
Vadim Yanitskiyf473c7b2018-07-30 14:29:39 +0700344 "Identify subscriber by IMSI\n"
345 "IMSI/MSISDN/ID of the subscriber\n"
346 "Create subscriber by IMSI\n")
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200347{
348 int rc;
349 struct hlr_subscriber subscr;
350 const char *imsi = argv[0];
Pau Espin Pedrolb74769f2022-06-20 16:48:12 +0200351
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200352 if (!osmo_imsi_str_valid(imsi)) {
353 vty_out(vty, "%% Not a valid IMSI: %s%s", imsi, VTY_NEWLINE);
354 return CMD_WARNING;
355 }
356
Oliver Smithcd2af5e2019-03-06 13:17:39 +0100357 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 +0200358
359 if (rc) {
360 if (rc == -EEXIST)
361 vty_out(vty, "%% Subscriber already exists for IMSI = %s%s",
362 imsi, VTY_NEWLINE);
363 else
364 vty_out(vty, "%% Error (rc=%d): cannot create subscriber for IMSI = %s%s",
365 rc, imsi, VTY_NEWLINE);
366 return CMD_WARNING;
367 }
368
369 rc = db_subscr_get_by_imsi(g_hlr->dbc, imsi, &subscr);
370 vty_out(vty, "%% Created subscriber %s%s", imsi, VTY_NEWLINE);
371
372 subscr_dump_full_vty(vty, &subscr);
373
374 return CMD_SUCCESS;
375}
376
377DEFUN(subscriber_delete,
378 subscriber_delete_cmd,
379 SUBSCR "delete",
380 SUBSCR_HELP "Delete subscriber from database\n")
381{
382 struct hlr_subscriber subscr;
383 int rc;
384 const char *id_type = argv[0];
385 const char *id = argv[1];
386
387 /* Find out the IMSI regardless of which way the caller decided to
388 * identify the subscriber by. */
389 if (get_subscr_by_argv(vty, id_type, id, &subscr))
390 return CMD_WARNING;
391
392 rc = db_subscr_delete_by_id(g_hlr->dbc, subscr.id);
393 if (rc) {
394 vty_out(vty, "%% Error: Failed to remove subscriber for IMSI '%s'%s",
395 subscr.imsi, VTY_NEWLINE);
396 return CMD_WARNING;
397 }
398
399 vty_out(vty, "%% Deleted subscriber for IMSI '%s'%s", subscr.imsi, VTY_NEWLINE);
400 return CMD_SUCCESS;
401}
402
403DEFUN(subscriber_msisdn,
404 subscriber_msisdn_cmd,
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100405 SUBSCR_UPDATE "msisdn (none|MSISDN)",
406 SUBSCR_UPDATE_HELP SUBSCR_MSISDN_HELP
407 "Remove MSISDN (phone number)\n"
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200408 "New MSISDN (phone number)\n")
409{
410 struct hlr_subscriber subscr;
411 const char *id_type = argv[0];
412 const char *id = argv[1];
413 const char *msisdn = argv[2];
414
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100415 if (strcmp(msisdn, "none") == 0)
416 msisdn = NULL;
417 else {
418 if (strlen(msisdn) > sizeof(subscr.msisdn) - 1) {
419 vty_out(vty, "%% MSISDN is too long, max. %zu characters are allowed%s",
420 sizeof(subscr.msisdn)-1, VTY_NEWLINE);
421 return CMD_WARNING;
422 }
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200423
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100424 if (!osmo_msisdn_str_valid(msisdn)) {
425 vty_out(vty, "%% MSISDN invalid: '%s'%s", msisdn, VTY_NEWLINE);
426 return CMD_WARNING;
427 }
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200428 }
429
430 if (get_subscr_by_argv(vty, id_type, id, &subscr))
431 return CMD_WARNING;
432
433 if (db_subscr_update_msisdn_by_imsi(g_hlr->dbc, subscr.imsi, msisdn)) {
434 vty_out(vty, "%% Error: cannot update MSISDN for subscriber IMSI='%s'%s",
435 subscr.imsi, VTY_NEWLINE);
436 return CMD_WARNING;
437 }
438
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100439 if (msisdn) {
440 vty_out(vty, "%% Updated subscriber IMSI='%s' to MSISDN='%s'%s",
441 subscr.imsi, msisdn, VTY_NEWLINE);
Stefan Sperlingf1622522018-04-09 11:39:16 +0200442
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100443 if (db_subscr_get_by_msisdn(g_hlr->dbc, msisdn, &subscr) == 0)
444 osmo_hlr_subscriber_update_notify(&subscr);
445 } else {
446 vty_out(vty, "%% Updated subscriber IMSI='%s': removed MSISDN%s",
447 subscr.imsi, VTY_NEWLINE);
448
Stefan Sperlingf1622522018-04-09 11:39:16 +0200449 osmo_hlr_subscriber_update_notify(&subscr);
Neels Hofmeyra820ea12018-12-02 19:46:46 +0100450 }
Stefan Sperlingf1622522018-04-09 11:39:16 +0200451
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200452 return CMD_SUCCESS;
453}
454
455static bool is_hexkey_valid(struct vty *vty, const char *label,
456 const char *hex_str, int minlen, int maxlen)
457{
458 if (osmo_is_hexstr(hex_str, minlen * 2, maxlen * 2, true))
459 return true;
460 vty_out(vty, "%% Invalid value for %s: '%s'%s", label, hex_str, VTY_NEWLINE);
461 return false;
462}
463
Harald Welte829713a2023-05-30 16:57:27 +0200464#define AUTH_ALG_TYPES_2G "(comp128v1|comp128v2|comp128v3|xor-2g)"
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200465#define AUTH_ALG_TYPES_2G_HELP \
466 "Use COMP128v1 algorithm\n" \
467 "Use COMP128v2 algorithm\n" \
468 "Use COMP128v3 algorithm\n" \
Harald Welte829713a2023-05-30 16:57:27 +0200469 "Use XOR-2G algorithm\n"
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200470
Harald Welte7a763aa2023-05-30 18:55:20 +0200471#define AUTH_ALG_TYPES_3G "(milenage|tuak)"
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200472#define AUTH_ALG_TYPES_3G_HELP \
Harald Welte7a763aa2023-05-30 18:55:20 +0200473 "Use Milenage algorithm\n" \
474 "Use TUAK algorithm\n"
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200475
Pau Espin Pedrol1d0a0302022-06-20 16:48:45 +0200476bool auth_algo_parse(const char *alg_str, enum osmo_auth_algo *algo,
Harald Welte626f5eb2023-05-30 18:28:15 +0200477 int *minlen, int *maxlen, int *minlen_opc, int *maxlen_opc)
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200478{
Harald Welte626f5eb2023-05-30 18:28:15 +0200479 /* Default: no OP[c]. True for all 2G algorithms, and 3G-XOR. Overridden below for real 3G AKA algorithms. */
480 if (minlen_opc)
481 *minlen_opc = 0;
482 if (maxlen_opc)
483 *maxlen_opc = 0;
484
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200485 if (!strcasecmp(alg_str, "none")) {
486 *algo = OSMO_AUTH_ALG_NONE;
487 *minlen = *maxlen = 0;
488 } else if (!strcasecmp(alg_str, "comp128v1")) {
489 *algo = OSMO_AUTH_ALG_COMP128v1;
490 *minlen = *maxlen = A38_COMP128_KEY_LEN;
491 } else if (!strcasecmp(alg_str, "comp128v2")) {
492 *algo = OSMO_AUTH_ALG_COMP128v2;
493 *minlen = *maxlen = A38_COMP128_KEY_LEN;
494 } else if (!strcasecmp(alg_str, "comp128v3")) {
495 *algo = OSMO_AUTH_ALG_COMP128v3;
496 *minlen = *maxlen = A38_COMP128_KEY_LEN;
Harald Welte829713a2023-05-30 16:57:27 +0200497 } else if (!strcasecmp(alg_str, "xor-3g")) {
498 *algo = OSMO_AUTH_ALG_XOR_3G;
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200499 *minlen = A38_XOR_MIN_KEY_LEN;
500 *maxlen = A38_XOR_MAX_KEY_LEN;
Harald Welte829713a2023-05-30 16:57:27 +0200501 } else if (!strcasecmp(alg_str, "xor-2g")) {
502 *algo = OSMO_AUTH_ALG_XOR_2G;
503 *minlen = *maxlen = A38_XOR2G_KEY_LEN;
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200504 } else if (!strcasecmp(alg_str, "milenage")) {
505 *algo = OSMO_AUTH_ALG_MILENAGE;
506 *minlen = *maxlen = MILENAGE_KEY_LEN;
Harald Welte626f5eb2023-05-30 18:28:15 +0200507 if (minlen_opc)
508 *minlen_opc = MILENAGE_KEY_LEN;
509 if (maxlen_opc)
510 *maxlen_opc = MILENAGE_KEY_LEN;
Harald Welte7a763aa2023-05-30 18:55:20 +0200511 } else if (!strcasecmp(alg_str, "tuak")) {
512 *algo = OSMO_AUTH_ALG_TUAK;
513 *minlen = 16;
514 *maxlen = 32;
515 if (minlen_opc)
516 *minlen_opc = 32;
517 if (maxlen_opc)
518 *maxlen_opc = 32;
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200519 } else
520 return false;
521 return true;
522}
523
524DEFUN(subscriber_no_aud2g,
525 subscriber_no_aud2g_cmd,
526 SUBSCR_UPDATE "aud2g none",
527 SUBSCR_UPDATE_HELP
528 "Set 2G authentication data\n"
529 "Delete 2G authentication data\n")
530{
531 struct hlr_subscriber subscr;
532 int rc;
533 const char *id_type = argv[0];
534 const char *id = argv[1];
535 struct sub_auth_data_str aud = {
536 .type = OSMO_AUTH_TYPE_GSM,
537 .algo = OSMO_AUTH_ALG_NONE,
538 };
539
540 if (get_subscr_by_argv(vty, id_type, id, &subscr))
541 return CMD_WARNING;
542
543 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud);
544
Harald Welte880a34d2018-03-01 21:32:01 +0100545 if (rc && rc != -ENOENT) {
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200546 vty_out(vty, "%% Error: cannot disable 2G auth data for IMSI='%s'%s",
547 subscr.imsi, VTY_NEWLINE);
548 return CMD_WARNING;
549 }
550 return CMD_SUCCESS;
551}
552
553DEFUN(subscriber_aud2g,
554 subscriber_aud2g_cmd,
555 SUBSCR_UPDATE "aud2g " AUTH_ALG_TYPES_2G " ki KI",
556 SUBSCR_UPDATE_HELP
557 "Set 2G authentication data\n"
558 AUTH_ALG_TYPES_2G_HELP
559 "Set Ki Encryption Key\n" "Ki as 32 hexadecimal characters\n")
560{
561 struct hlr_subscriber subscr;
562 int rc;
563 int minlen = 0;
564 int maxlen = 0;
565 const char *id_type = argv[0];
566 const char *id = argv[1];
567 const char *alg_type = argv[2];
568 const char *ki = argv[3];
569 struct sub_auth_data_str aud2g = {
570 .type = OSMO_AUTH_TYPE_GSM,
571 .u.gsm.ki = ki,
572 };
573
Harald Welte626f5eb2023-05-30 18:28:15 +0200574 if (!auth_algo_parse(alg_type, &aud2g.algo, &minlen, &maxlen, NULL, NULL)) {
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200575 vty_out(vty, "%% Unknown auth algorithm: '%s'%s", alg_type, VTY_NEWLINE);
576 return CMD_WARNING;
577 }
578
579 if (!is_hexkey_valid(vty, "KI", aud2g.u.gsm.ki, minlen, maxlen))
580 return CMD_WARNING;
581
582 if (get_subscr_by_argv(vty, id_type, id, &subscr))
583 return CMD_WARNING;
584
585 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud2g);
586
587 if (rc) {
588 vty_out(vty, "%% Error: cannot set 2G auth data for IMSI='%s'%s",
589 subscr.imsi, VTY_NEWLINE);
590 return CMD_WARNING;
591 }
592 return CMD_SUCCESS;
593}
594
595DEFUN(subscriber_no_aud3g,
596 subscriber_no_aud3g_cmd,
597 SUBSCR_UPDATE "aud3g none",
598 SUBSCR_UPDATE_HELP
599 "Set UMTS authentication data (3G, and 2G with UMTS AKA)\n"
600 "Delete 3G authentication data\n")
601{
602 struct hlr_subscriber subscr;
603 int rc;
604 const char *id_type = argv[0];
605 const char *id = argv[1];
606 struct sub_auth_data_str aud = {
607 .type = OSMO_AUTH_TYPE_UMTS,
608 .algo = OSMO_AUTH_ALG_NONE,
609 };
610
611 if (get_subscr_by_argv(vty, id_type, id, &subscr))
612 return CMD_WARNING;
613
614 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud);
615
Harald Welte880a34d2018-03-01 21:32:01 +0100616 if (rc && rc != -ENOENT) {
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200617 vty_out(vty, "%% Error: cannot disable 3G auth data for IMSI='%s'%s",
618 subscr.imsi, VTY_NEWLINE);
619 return CMD_WARNING;
620 }
621 return CMD_SUCCESS;
622}
623
624DEFUN(subscriber_aud3g,
625 subscriber_aud3g_cmd,
626 SUBSCR_UPDATE "aud3g " AUTH_ALG_TYPES_3G
627 " k K"
628 " (op|opc) OP_C"
629 " [ind-bitlen] [<0-28>]",
630 SUBSCR_UPDATE_HELP
631 "Set UMTS authentication data (3G, and 2G with UMTS AKA)\n"
632 AUTH_ALG_TYPES_3G_HELP
Harald Welte626f5eb2023-05-30 18:28:15 +0200633 "Set Encryption Key K\n" "K as 32/64 hexadecimal characters\n"
634 "Set OP key\n" "Set OPC key\n" "OP or OPC as 32/64 hexadecimal characters\n"
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200635 "Set IND bit length\n" "IND bit length value (default: 5)\n")
636{
637 struct hlr_subscriber subscr;
Harald Welte626f5eb2023-05-30 18:28:15 +0200638 int minlen = 0, minlen_opc = 0;
639 int maxlen = 0, maxlen_opc = 0;
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200640 int rc;
641 const char *id_type = argv[0];
642 const char *id = argv[1];
Harald Welte7a763aa2023-05-30 18:55:20 +0200643 const char *alg_type = argv[2];
644 const char *k = argv[3];
645 bool opc_is_op = (strcasecmp("op", argv[4]) == 0);
646 const char *op_opc = argv[5];
647 int ind_bitlen = argc > 7 ? atoi(argv[7]) : 5;
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200648 struct sub_auth_data_str aud3g = {
649 .type = OSMO_AUTH_TYPE_UMTS,
650 .u.umts = {
651 .k = k,
652 .opc_is_op = opc_is_op,
653 .opc = op_opc,
654 .ind_bitlen = ind_bitlen,
655 },
656 };
Pau Espin Pedrolb74769f2022-06-20 16:48:12 +0200657
Harald Welte626f5eb2023-05-30 18:28:15 +0200658 if (!auth_algo_parse(alg_type, &aud3g.algo, &minlen, &maxlen, &minlen_opc, &maxlen_opc)) {
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200659 vty_out(vty, "%% Unknown auth algorithm: '%s'%s", alg_type, VTY_NEWLINE);
660 return CMD_WARNING;
661 }
662
663 if (!is_hexkey_valid(vty, "K", aud3g.u.umts.k, minlen, maxlen))
664 return CMD_WARNING;
665
Harald Welte626f5eb2023-05-30 18:28:15 +0200666 if (!is_hexkey_valid(vty, opc_is_op ? "OP" : "OPC", aud3g.u.umts.opc, minlen_opc, maxlen_opc))
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200667 return CMD_WARNING;
668
669 if (get_subscr_by_argv(vty, id_type, id, &subscr))
670 return CMD_WARNING;
671
672 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud3g);
673
674 if (rc) {
675 vty_out(vty, "%% Error: cannot set 3G auth data for IMSI='%s'%s",
676 subscr.imsi, VTY_NEWLINE);
677 return CMD_WARNING;
678 }
679 return CMD_SUCCESS;
680}
681
Harald Welte6e237d32020-12-28 01:01:31 +0100682DEFUN(subscriber_aud3g_xor,
683 subscriber_aud3g_xor_cmd,
Harald Welte829713a2023-05-30 16:57:27 +0200684 SUBSCR_UPDATE "aud3g xor-3g k K"
Harald Welte6e237d32020-12-28 01:01:31 +0100685 " [ind-bitlen] [<0-28>]",
686 SUBSCR_UPDATE_HELP
687 "Set UMTS authentication data (3G, and 2G with UMTS AKA)\n"
Harald Welte829713a2023-05-30 16:57:27 +0200688 "Use XOR-3G algorithm\n"
Harald Welte6e237d32020-12-28 01:01:31 +0100689 "Set Encryption Key K\n" "K as 32 hexadecimal characters\n"
690 "Set IND bit length\n" "IND bit length value (default: 5)\n")
691{
692 struct hlr_subscriber subscr;
693 int minlen = 0;
694 int maxlen = 0;
695 int rc;
696 const char *id_type = argv[0];
697 const char *id = argv[1];
698 const char *k = argv[2];
699 int ind_bitlen = argc > 4? atoi(argv[4]) : 5;
700 struct sub_auth_data_str aud3g = {
701 .type = OSMO_AUTH_TYPE_UMTS,
702 .u.umts = {
703 .k = k,
704 .opc_is_op = 0,
705 .opc = "00000000000000000000000000000000",
706 .ind_bitlen = ind_bitlen,
707 },
708 };
709
Harald Welte626f5eb2023-05-30 18:28:15 +0200710 if (!auth_algo_parse("xor-3g", &aud3g.algo, &minlen, &maxlen, NULL, NULL)) {
Harald Welte829713a2023-05-30 16:57:27 +0200711 vty_out(vty, "%% Unknown auth algorithm: '%s'%s", "xor-3g", VTY_NEWLINE);
Harald Welte6e237d32020-12-28 01:01:31 +0100712 return CMD_WARNING;
713 }
714
715 if (!is_hexkey_valid(vty, "K", aud3g.u.umts.k, minlen, maxlen))
716 return CMD_WARNING;
717
718 if (get_subscr_by_argv(vty, id_type, id, &subscr))
719 return CMD_WARNING;
720
721 rc = db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud3g);
722
723 if (rc) {
724 vty_out(vty, "%% Error: cannot set 3G auth data for IMSI='%s'%s",
725 subscr.imsi, VTY_NEWLINE);
726 return CMD_WARNING;
727 }
728 return CMD_SUCCESS;
729}
730
Oliver Smith02078b72019-01-11 15:41:29 +0100731DEFUN(subscriber_imei,
732 subscriber_imei_cmd,
733 SUBSCR_UPDATE "imei (none|IMEI)",
734 SUBSCR_UPDATE_HELP
735 "Set IMEI of the subscriber (normally populated from MSC, no need to set this manually)\n"
736 "Forget IMEI\n"
737 "Set IMEI (use for debug only!)\n")
738{
739 struct hlr_subscriber subscr;
740 const char *id_type = argv[0];
741 const char *id = argv[1];
742 const char *imei = argv[2];
743 char imei_buf[GSM23003_IMEI_NUM_DIGITS_NO_CHK+1];
744
745 if (strcmp(imei, "none") == 0)
746 imei = NULL;
747 else {
748 /* Verify IMEI with checksum digit */
749 if (osmo_imei_str_valid(imei, true)) {
750 /* Cut the checksum off */
751 osmo_strlcpy(imei_buf, imei, sizeof(imei_buf));
752 imei = imei_buf;
753 } else if (!osmo_imei_str_valid(imei, false)) {
754 vty_out(vty, "%% IMEI invalid: '%s'%s", imei, VTY_NEWLINE);
755 return CMD_WARNING;
756 }
757 }
758
759 if (get_subscr_by_argv(vty, id_type, id, &subscr))
760 return CMD_WARNING;
761
762 if (db_subscr_update_imei_by_imsi(g_hlr->dbc, subscr.imsi, imei)) {
763 vty_out(vty, "%% Error: cannot update IMEI for subscriber IMSI='%s'%s",
764 subscr.imsi, VTY_NEWLINE);
765 return CMD_WARNING;
766 }
767
768 if (imei)
769 vty_out(vty, "%% Updated subscriber IMSI='%s' to IMEI='%s'%s",
770 subscr.imsi, imei, VTY_NEWLINE);
771 else
772 vty_out(vty, "%% Updated subscriber IMSI='%s': removed IMEI%s",
773 subscr.imsi, VTY_NEWLINE);
774
775 return CMD_SUCCESS;
776}
777
Oliver Smith3b33b012019-07-15 10:35:22 +0200778DEFUN(subscriber_nam,
779 subscriber_nam_cmd,
780 SUBSCR_UPDATE "network-access-mode (none|cs|ps|cs+ps)",
781 SUBSCR_UPDATE_HELP
782 "Set Network Access Mode (NAM) of the subscriber\n"
783 "Do not allow access to circuit switched or packet switched services\n"
784 "Allow access to circuit switched services only\n"
785 "Allow access to packet switched services only\n"
786 "Allow access to both circuit and packet switched services\n")
787{
788 struct hlr_subscriber subscr;
789 const char *id_type = argv[0];
790 const char *id = argv[1];
791 bool nam_cs = strstr(argv[2], "cs");
792 bool nam_ps = strstr(argv[2], "ps");
793
794 if (get_subscr_by_argv(vty, id_type, id, &subscr))
795 return CMD_WARNING;
796
797 if (nam_cs != subscr.nam_cs)
798 hlr_subscr_nam(g_hlr, &subscr, nam_cs, 0);
799 if (nam_ps != subscr.nam_ps)
800 hlr_subscr_nam(g_hlr, &subscr, nam_ps, 1);
801
802 return CMD_SUCCESS;
803}
804
Oliver Smith02078b72019-01-11 15:41:29 +0100805
Harald Welted5807b82018-07-29 12:27:41 +0200806void hlr_vty_subscriber_init(void)
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200807{
Keith89fda302021-01-19 07:01:33 +0100808 install_element_ve(&show_subscriber_all_cmd);
809 install_element_ve(&show_subscriber_filtered_cmd);
810 install_element_ve(&show_subscriber_filtered_cmd2);
811 install_element_ve(&show_subscriber_order_last_seen_cmd);
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200812 install_element_ve(&subscriber_show_cmd);
Neels Hofmeyr8aa780b2018-12-02 18:52:49 +0100813 install_element_ve(&show_subscriber_cmd);
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200814 install_element(ENABLE_NODE, &subscriber_create_cmd);
815 install_element(ENABLE_NODE, &subscriber_delete_cmd);
816 install_element(ENABLE_NODE, &subscriber_msisdn_cmd);
817 install_element(ENABLE_NODE, &subscriber_no_aud2g_cmd);
818 install_element(ENABLE_NODE, &subscriber_aud2g_cmd);
819 install_element(ENABLE_NODE, &subscriber_no_aud3g_cmd);
820 install_element(ENABLE_NODE, &subscriber_aud3g_cmd);
Harald Welte6e237d32020-12-28 01:01:31 +0100821 install_element(ENABLE_NODE, &subscriber_aud3g_xor_cmd);
Oliver Smith02078b72019-01-11 15:41:29 +0100822 install_element(ENABLE_NODE, &subscriber_imei_cmd);
Oliver Smith3b33b012019-07-15 10:35:22 +0200823 install_element(ENABLE_NODE, &subscriber_nam_cmd);
Neels Hofmeyr183e7002017-10-06 02:59:54 +0200824}