gbproxy: Test and fix IMSI/TMSI matching

This adds a test for gbprox_set_patch_filter() and
gbprox_check_imsi().

It also fixes the masking of the type field when IMSIs are checked by
using GSM_MI_TYPE_MASK (0x07) instead of 0x0f.

Sponsored-by: On-Waves ehf
diff --git a/openbsc/include/openbsc/gb_proxy.h b/openbsc/include/openbsc/gb_proxy.h
index 5a580cd..9cd1abb 100644
--- a/openbsc/include/openbsc/gb_proxy.h
+++ b/openbsc/include/openbsc/gb_proxy.h
@@ -130,4 +130,6 @@
 struct gbproxy_peer *gbproxy_peer_alloc(struct gbproxy_config *cfg, uint16_t bvci);
 void gbproxy_peer_free(struct gbproxy_peer *peer);
 
+int gbprox_check_imsi(struct gbproxy_peer *peer,
+		const uint8_t *imsi, size_t imsi_len);
 #endif
diff --git a/openbsc/src/gprs/gb_proxy.c b/openbsc/src/gprs/gb_proxy.c
index 103ecb5..e797549 100644
--- a/openbsc/src/gprs/gb_proxy.c
+++ b/openbsc/src/gprs/gb_proxy.c
@@ -354,7 +354,7 @@
 	if (value_len != GSM48_TMSI_LEN)
 		return 0;
 
-	if (!value || (value[0] & 0x0f) != GSM_MI_TYPE_TMSI)
+	if (!value || (value[0] & GSM_MI_TYPE_MASK) != GSM_MI_TYPE_TMSI)
 		return 0;
 
 	return 1;
@@ -366,7 +366,7 @@
 	if (value_len == 0)
 		return 0;
 
-	if (!value || (value[0] & 0x0f) != GSM_MI_TYPE_IMSI)
+	if (!value || (value[0] & GSM_MI_TYPE_MASK) != GSM_MI_TYPE_IMSI)
 		return 0;
 
 	return 1;
@@ -507,8 +507,8 @@
 	return -1;
 }
 
-static int gbprox_check_imsi(struct gbproxy_peer *peer,
-			     const uint8_t *imsi, size_t imsi_len)
+int gbprox_check_imsi(struct gbproxy_peer *peer,
+		      const uint8_t *imsi, size_t imsi_len)
 {
 	char mi_buf[200];
 	int rc;
@@ -516,8 +516,10 @@
 	if (!peer->cfg->check_imsi)
 		return 1;
 
-	rc = gsm48_mi_to_string(mi_buf, sizeof(mi_buf), imsi, imsi_len);
-	if (rc < 1) {
+	rc = is_mi_imsi(imsi, imsi_len);
+	if (rc > 0)
+		rc = gsm48_mi_to_string(mi_buf, sizeof(mi_buf), imsi, imsi_len);
+	if (rc <= 0) {
 		LOGP(DGPRS, LOGL_NOTICE, "Invalid IMSI %s\n",
 		     osmo_hexdump(imsi, imsi_len));
 		return -1;
diff --git a/openbsc/tests/gbproxy/gbproxy_test.c b/openbsc/tests/gbproxy/gbproxy_test.c
index 229d2d4..bb2b1aa 100644
--- a/openbsc/tests/gbproxy/gbproxy_test.c
+++ b/openbsc/tests/gbproxy/gbproxy_test.c
@@ -1474,6 +1474,77 @@
 	}
 }
 
+static void test_gbproxy_imsi_matching(void)
+{
+	struct gbproxy_config cfg = {0};
+	struct gbproxy_peer *peer;
+	const char *err_msg = NULL;
+	const uint8_t imsi1[] = { GSM_MI_TYPE_IMSI | 0x10, 0x32, 0x54, 0xf6 };
+	const uint8_t imsi2[] = { GSM_MI_TYPE_IMSI | GSM_MI_ODD | 0x10, 0x32, 0x54, 0x76 };
+	const uint8_t imsi3_bad[] = { GSM_MI_TYPE_IMSI | 0x10, 0xee, 0x54, 0xff };
+	const uint8_t tmsi1[] = { GSM_MI_TYPE_TMSI | 0xf0, 0x11, 0x22, 0x33, 0x44 };
+	const uint8_t tmsi2_bad[] = { GSM_MI_TYPE_TMSI | 0xf0, 0x11, 0x22 };
+	const uint8_t imei1[] = { GSM_MI_TYPE_IMEI | 0x10, 0x32, 0x54, 0xf6 };
+	const uint8_t imei2[] = { GSM_MI_TYPE_IMEI | GSM_MI_ODD | 0x10, 0x32, 0x54, 0x76 };
+	const char *filter_re1 = ".*";
+	const char *filter_re2 = "^1234";
+	const char *filter_re3 = "^4321";
+	const char *filter_re4_bad = "^12[";
+
+	printf("=== Test IMSI/TMSI matching ===\n\n");
+
+	gbproxy_init_config(&cfg);
+	OSMO_ASSERT(cfg.check_imsi == 0);
+
+	OSMO_ASSERT(gbprox_set_patch_filter(&cfg, filter_re1, &err_msg) == 0);
+	OSMO_ASSERT(cfg.check_imsi == 1);
+
+	OSMO_ASSERT(gbprox_set_patch_filter(&cfg, filter_re2, &err_msg) == 0);
+	OSMO_ASSERT(cfg.check_imsi == 1);
+
+	err_msg = NULL;
+	OSMO_ASSERT(gbprox_set_patch_filter(&cfg, filter_re4_bad, &err_msg) == -1);
+	OSMO_ASSERT(err_msg != NULL);
+	OSMO_ASSERT(cfg.check_imsi == 0);
+
+	OSMO_ASSERT(gbprox_set_patch_filter(&cfg, filter_re2, &err_msg) == 0);
+	OSMO_ASSERT(cfg.check_imsi == 1);
+
+	OSMO_ASSERT(gbprox_set_patch_filter(&cfg, NULL, &err_msg) == 0);
+	OSMO_ASSERT(cfg.check_imsi == 0);
+
+	peer = gbproxy_peer_alloc(&cfg, 20);
+
+	OSMO_ASSERT(gbprox_set_patch_filter(&cfg, filter_re2, &err_msg) == 0);
+	OSMO_ASSERT(cfg.check_imsi == 1);
+
+	OSMO_ASSERT(gbprox_check_imsi(peer, imsi1, ARRAY_SIZE(imsi1)) == 1);
+	OSMO_ASSERT(gbprox_check_imsi(peer, imsi2, ARRAY_SIZE(imsi2)) == 1);
+	/* imsi3_bad contains 0xE and 0xF digits, but the conversion function
+	 * doesn't complain, so gbprox_check_imsi() doesn't return -1 in this
+	 * case. */
+	OSMO_ASSERT(gbprox_check_imsi(peer, imsi3_bad, ARRAY_SIZE(imsi3_bad)) == 0);
+	OSMO_ASSERT(gbprox_check_imsi(peer, tmsi1, ARRAY_SIZE(tmsi1)) == -1);
+	OSMO_ASSERT(gbprox_check_imsi(peer, tmsi2_bad, ARRAY_SIZE(tmsi2_bad)) == -1);
+	OSMO_ASSERT(gbprox_check_imsi(peer, imei1, ARRAY_SIZE(imei1)) == -1);
+	OSMO_ASSERT(gbprox_check_imsi(peer, imei2, ARRAY_SIZE(imei2)) == -1);
+
+	OSMO_ASSERT(gbprox_set_patch_filter(&cfg, filter_re3, &err_msg) == 0);
+	OSMO_ASSERT(cfg.check_imsi == 1);
+
+	OSMO_ASSERT(gbprox_check_imsi(peer, imsi1, ARRAY_SIZE(imsi1)) == 0);
+	OSMO_ASSERT(gbprox_check_imsi(peer, imsi2, ARRAY_SIZE(imsi2)) == 0);
+	OSMO_ASSERT(gbprox_check_imsi(peer, imsi3_bad, ARRAY_SIZE(imsi3_bad)) == 0);
+	OSMO_ASSERT(gbprox_check_imsi(peer, tmsi1, ARRAY_SIZE(tmsi1)) == -1);
+	OSMO_ASSERT(gbprox_check_imsi(peer, tmsi2_bad, ARRAY_SIZE(tmsi2_bad)) == -1);
+	OSMO_ASSERT(gbprox_check_imsi(peer, imei1, ARRAY_SIZE(imei1)) == -1);
+	OSMO_ASSERT(gbprox_check_imsi(peer, imei2, ARRAY_SIZE(imei2)) == -1);
+
+	/* TODO: Check correct length but wrong type with is_mi_tmsi */
+
+	gbproxy_peer_free(peer);
+}
+
 static struct log_info_cat gprs_categories[] = {
 	[DGPRS] = {
 		.name = "DGPRS",
@@ -1517,6 +1588,7 @@
 	test_tlv_shift_functions();
 	test_gbproxy();
 	test_gbproxy_ident_changes();
+	test_gbproxy_imsi_matching();
 	test_gbproxy_ra_patching();
 	test_gbproxy_tlli_expire();
 	printf("===== GbProxy test END\n\n");
diff --git a/openbsc/tests/gbproxy/gbproxy_test.ok b/openbsc/tests/gbproxy/gbproxy_test.ok
index 4553648..c893ff3 100644
--- a/openbsc/tests/gbproxy/gbproxy_test.ok
+++ b/openbsc/tests/gbproxy/gbproxy_test.ok
@@ -1479,6 +1479,8 @@
   NSEI 8192, BVCI 4098, not blocked, RAI 112-332-16464-96
     NSEI mismatch                   : 1
     TLLI-Cache: 0
+=== Test IMSI/TMSI matching ===
+
 === test_gbproxy_ra_patching ===
 --- Initialise SGSN ---