Merge branch 'zecke/imsi-filter'
diff --git a/openbsc/include/openbsc/bsc_nat.h b/openbsc/include/openbsc/bsc_nat.h
index 0c9af91..6b2a099 100644
--- a/openbsc/include/openbsc/bsc_nat.h
+++ b/openbsc/include/openbsc/bsc_nat.h
@@ -243,6 +243,8 @@
  */
 int bsc_nat_filter_sccp_cr(struct bsc_connection *bsc, struct msgb *msg,
 			   struct bsc_nat_parsed *, int *con_type);
+int bsc_nat_filter_dt(struct bsc_connection *bsc, struct msgb *msg,
+		      struct sccp_connections *con, struct bsc_nat_parsed *parsed);
 
 /**
  * SCCP patching and handling
diff --git a/openbsc/include/openbsc/bsc_nat_sccp.h b/openbsc/include/openbsc/bsc_nat_sccp.h
index 4f1afcb..c4f756e 100644
--- a/openbsc/include/openbsc/bsc_nat_sccp.h
+++ b/openbsc/include/openbsc/bsc_nat_sccp.h
@@ -78,6 +78,7 @@
 	/* status */
 	int con_type;
 	int con_local;
+	int imsi_checked;
 
 	/*
 	 * audio handling. Remember if we have ever send a CRCX,
diff --git a/openbsc/src/nat/bsc_nat.c b/openbsc/src/nat/bsc_nat.c
index 6485102..738b7ef 100644
--- a/openbsc/src/nat/bsc_nat.c
+++ b/openbsc/src/nat/bsc_nat.c
@@ -303,6 +303,63 @@
 	bsc_write(bsc, msg, proto);
 }
 
+/*
+ * Release an established connection. We will have to release it to the BSC
+ * and to the network and we do it the following way.
+ * 1.) Give up on the MSC side
+ *  1.1) Send a RLSD message, it is a bit non standard but should work, we
+ *       ignore the RLC... we might complain about it. Other options would
+ *       be to send a Release Request, handle the Release Complete..
+ *  1.2) Mark the data structure to be con_local and wait for 2nd
+ *
+ * 2.) Give up on the BSC side
+ *  2.1) Depending on the con type reject the service, or just close it
+ */
+static void bsc_send_con_release(struct bsc_connection *bsc, struct sccp_connections *con)
+{
+	struct msgb *rlsd;
+	/* 1. release the network */
+	rlsd = sccp_create_rlsd(&con->patched_ref, &con->remote_ref,
+				SCCP_RELEASE_CAUSE_END_USER_ORIGINATED);
+	if (!rlsd)
+		LOGP(DNAT, LOGL_ERROR, "Failed to create RLSD message.\n");
+	else {
+		ipaccess_prepend_header(rlsd, IPAC_PROTO_SCCP);
+		queue_for_msc(con->msc_con, rlsd);
+	}
+	con->con_local = 1;
+
+	/* 2. release the BSC side */
+	if (con->con_type == NAT_CON_TYPE_LU) {
+		struct msgb *payload, *udt;
+		payload = gsm48_create_loc_upd_rej(GSM48_REJECT_PLMN_NOT_ALLOWED);
+
+		if (payload) {
+			gsm0808_prepend_dtap_header(payload, 0);
+			udt = sccp_create_dt1(&con->real_ref, payload->data, payload->len);
+			if (udt)
+				bsc_write(bsc, udt, IPAC_PROTO_SCCP);
+			else
+				LOGP(DNAT, LOGL_ERROR, "Failed to create DT1\n");
+
+			msgb_free(payload);
+		} else {
+			LOGP(DNAT, LOGL_ERROR, "Failed to allocate LU Reject.\n");
+		}
+	}
+
+	rlsd = sccp_create_rlsd(&con->remote_ref, &con->real_ref,
+				SCCP_RELEASE_CAUSE_END_USER_ORIGINATED);
+	if (!rlsd) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate RLSD for the BSC.\n");
+		sccp_connection_destroy(con);
+		return;
+	}
+
+	con->con_type = NAT_CON_TYPE_LOCAL_REJECT;
+	bsc_write(bsc, rlsd, IPAC_PROTO_SCCP);
+}
+
 static void bsc_send_con_refuse(struct bsc_connection *bsc,
 				struct bsc_nat_parsed *parsed, int con_type)
 {
@@ -708,10 +765,12 @@
 
 	/* modify the SCCP entries */
 	if (parsed->ipa_proto == IPAC_PROTO_SCCP) {
+		int filter;
 		struct sccp_connections *con;
 		switch (parsed->sccp_type) {
 		case SCCP_MSG_TYPE_CR:
-			if (bsc_nat_filter_sccp_cr(bsc, msg, parsed, &con_type) != 0)
+			filter = bsc_nat_filter_sccp_cr(bsc, msg, parsed, &con_type);
+			if (filter < 0)
 				goto exit3;
 			if (!create_sccp_src_ref(bsc, parsed))
 				goto exit2;
@@ -719,6 +778,7 @@
 			con->msc_con = bsc->nat->msc_con;
 			con_msc = con->msc_con;
 			con->con_type = con_type;
+			con->imsi_checked = filter;
 			con_bsc = con->bsc;
 			break;
 		case SCCP_MSG_TYPE_RLSD:
@@ -728,9 +788,16 @@
 		case SCCP_MSG_TYPE_IT:
 			con = patch_sccp_src_ref_to_msc(msg, parsed, bsc);
 			if (con) {
-				con_bsc = con->bsc;
-				con_msc = con->msc_con;
-				con_filter = con->con_local;
+				filter = bsc_nat_filter_dt(bsc, msg, con, parsed);
+				if (filter == 0) {
+					con_bsc = con->bsc;
+					con_msc = con->msc_con;
+					con_filter = con->con_local;
+				} else {
+					bsc_send_con_release(bsc, con);
+					con = NULL;
+					goto exit2;
+				}
 			}
 			break;
 		case SCCP_MSG_TYPE_RLC:
@@ -766,15 +833,18 @@
 		goto exit2;
 	}
 
-	if (!con_msc) {
-		LOGP(DNAT, LOGL_ERROR, "No connection found, dropping data.\n");
-		goto exit2;
-	}
-
 	/* do not forward messages to the MSC */
 	if (con_filter)
 		goto exit2;
 
+	if (!con_msc) {
+		LOGP(DNAT, LOGL_ERROR, "Not forwarding data bsc_nr: %d ipa: %d type: 0x%x\n",
+			bsc->cfg->nr,
+			parsed ? parsed->ipa_proto : -1,
+			parsed ? parsed->sccp_type : -1);
+		goto exit2;
+	}
+
 	/* send the non-filtered but maybe modified msg */
 	queue_for_msc(con_msc, msg);
 	talloc_free(parsed);
diff --git a/openbsc/src/nat/bsc_nat_utils.c b/openbsc/src/nat/bsc_nat_utils.c
index 0582513..3933f1b 100644
--- a/openbsc/src/nat/bsc_nat_utils.c
+++ b/openbsc/src/nat/bsc_nat_utils.c
@@ -271,7 +271,7 @@
 
 		/* 2. BSC allow */
 		if (lst_check_allow(bsc_lst, mi_string) == 0)
-			return 0;
+			return 1;
 	}
 
 	/* 3. NAT deny */
@@ -283,7 +283,7 @@
 		}
 	}
 
-	return 0;
+	return 1;
 }
 
 static int _cr_check_loc_upd(struct bsc_connection *bsc, uint8_t *data, unsigned int length)
@@ -369,6 +369,35 @@
 	return auth_imsi(bsc, mi_string);
 }
 
+static int _dt_check_id_resp(struct bsc_connection *bsc,
+			     uint8_t *data, unsigned int length,
+			     struct sccp_connections *con)
+{
+	char mi_string[GSM48_MI_SIZE];
+	uint8_t mi_type;
+	int ret;
+
+	if (length < 2) {
+		LOGP(DNAT, LOGL_ERROR, "mi does not fit.\n");
+		return -1;
+	}
+
+	if (data[0] < length - 1) {
+		LOGP(DNAT, LOGL_ERROR, "mi length too big.\n");
+		return -2;
+	}
+
+	mi_type = data[1] & GSM_MI_TYPE_MASK;
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), &data[1], data[0]);
+
+	if (mi_type != GSM_MI_TYPE_IMSI)
+		return 0;
+
+	ret = auth_imsi(bsc, mi_string);
+	con->imsi_checked = 1;
+	return ret;
+}
+
 /* Filter out CR data... */
 int bsc_nat_filter_sccp_cr(struct bsc_connection *bsc, struct msgb *msg, struct bsc_nat_parsed *parsed, int *con_type)
 {
@@ -376,6 +405,7 @@
 	struct gsm48_hdr *hdr48;
 	int hdr48_len;
 	int len;
+	uint8_t msg_type;
 
 	*con_type = NAT_CON_TYPE_NONE;
 
@@ -412,16 +442,17 @@
 
 	hdr48 = (struct gsm48_hdr *) TLVP_VAL(&tp, GSM0808_IE_LAYER_3_INFORMATION);
 
+	msg_type = hdr48->msg_type & 0xbf;
 	if (hdr48->proto_discr == GSM48_PDISC_MM &&
-	    hdr48->msg_type == GSM48_MT_MM_LOC_UPD_REQUEST) {
+	    msg_type == GSM48_MT_MM_LOC_UPD_REQUEST) {
 		*con_type = NAT_CON_TYPE_LU;
 		return _cr_check_loc_upd(bsc, &hdr48->data[0], hdr48_len - sizeof(*hdr48));
 	} else if (hdr48->proto_discr == GSM48_PDISC_MM &&
-		  hdr48->msg_type == GSM48_MT_MM_CM_SERV_REQ) {
+		  msg_type == GSM48_MT_MM_CM_SERV_REQ) {
 		*con_type = NAT_CON_TYPE_CM_SERV_REQ;
 		return _cr_check_cm_serv_req(bsc, &hdr48->data[0], hdr48_len - sizeof(*hdr48));
 	} else if (hdr48->proto_discr == GSM48_PDISC_RR &&
-		   hdr48->msg_type == GSM48_MT_RR_PAG_RESP) {
+		   msg_type == GSM48_MT_RR_PAG_RESP) {
 		*con_type = NAT_CON_TYPE_PAG_RESP;
 		return _cr_check_pag_resp(bsc, &hdr48->data[0], hdr48_len - sizeof(*hdr48));
 	} else {
@@ -431,6 +462,44 @@
 	}
 }
 
+int bsc_nat_filter_dt(struct bsc_connection *bsc, struct msgb *msg,
+		      struct sccp_connections *con, struct bsc_nat_parsed *parsed)
+{
+	uint32_t len;
+	uint8_t msg_type;
+	struct gsm48_hdr *hdr48;
+
+	if (con->imsi_checked)
+		return 0;
+
+	/* only care about DTAP messages */
+	if (parsed->bssap != BSSAP_MSG_DTAP)
+		return 0;
+
+	/* gsm_type is actually the size of the dtap */
+	len = parsed->gsm_type;
+	if (len < msgb_l3len(msg) - 3) {
+		LOGP(DNAT, LOGL_ERROR, "Not enough space for DTAP.\n");
+		return -1;
+	}
+
+	if (len < sizeof(*hdr48)) {
+		LOGP(DNAT, LOGL_ERROR, "GSM48 header does not fit.\n");
+		return -1;
+	}
+
+	msg->l4h = &msg->l3h[3];
+	hdr48 = (struct gsm48_hdr *) msg->l4h;
+
+	msg_type = hdr48->msg_type & 0xbf;
+	if (hdr48->proto_discr == GSM48_PDISC_MM &&
+	    msg_type == GSM48_MT_MM_ID_RESP) {
+		return _dt_check_id_resp(bsc, &hdr48->data[0], len - sizeof(*hdr48), con);
+	} else {
+		return 0;
+	}
+}
+
 void bsc_parse_reg(void *ctx, regex_t *reg, char **imsi, int argc, const char **argv)
 {
 	if (*imsi) {
diff --git a/openbsc/tests/bsc-nat/bsc_data.c b/openbsc/tests/bsc-nat/bsc_data.c
index a323979..1bc15c8 100644
--- a/openbsc/tests/bsc-nat/bsc_data.c
+++ b/openbsc/tests/bsc-nat/bsc_data.c
@@ -88,6 +88,14 @@
 0x01, 0x0b, 0x03, 0x01, 0x0a, 0x11, 0x01, 0x00,
 0x15 };
 
+/* identity response */
+static const uint8_t id_resp[] = {
+0x00, 0x15, 0xfd, 0x06, 0x01, 0x1c, 0xdc,
+0x00, 0x01, 0x0e, 0x01, 0x00, 0x0b, 0x05, 0x59,
+0x08, 0x29, 0x40, 0x21, 0x03, 0x07, 0x48, 0x66,
+0x31
+};
+
 /*
  * MGCP messages
  */
diff --git a/openbsc/tests/bsc-nat/bsc_nat_test.c b/openbsc/tests/bsc-nat/bsc_nat_test.c
index 8f4e2d1..926c97c 100644
--- a/openbsc/tests/bsc-nat/bsc_nat_test.c
+++ b/openbsc/tests/bsc-nat/bsc_nat_test.c
@@ -31,6 +31,7 @@
 #include <osmocore/talloc.h>
 
 #include <osmocom/sccp/sccp.h>
+#include <osmocore/protocol/gsm_08_08.h>
 
 #include <stdio.h>
 
@@ -582,19 +583,19 @@
 	{
 		.data = bssmap_cr,
 		.length = sizeof(bssmap_cr),
-		.result = 0,
+		.result = 1,
 		.contype = NAT_CON_TYPE_CM_SERV_REQ,
 	},
 	{
 		.data = bss_lu,
 		.length = sizeof(bss_lu),
-		.result = 0,
+		.result = 1,
 		.contype = NAT_CON_TYPE_LU,
 	},
 	{
 		.data = pag_resp,
 		.length = sizeof(pag_resp),
-		.result = 0,
+		.result = 1,
 		.contype = NAT_CON_TYPE_PAG_RESP,
 	},
 	{
@@ -609,7 +610,7 @@
 		/* BSC allow is before NAT deny */
 		.data = bss_lu,
 		.length = sizeof(bss_lu),
-		.result = 0,
+		.result = 1,
 		.nat_imsi_deny = "[0-9]*",
 		.bsc_imsi_allow = "2440[0-9]*",
 		.contype = NAT_CON_TYPE_LU,
@@ -618,7 +619,7 @@
 		/* BSC allow is before NAT deny */
 		.data = bss_lu,
 		.length = sizeof(bss_lu),
-		.result = 0,
+		.result = 1,
 		.bsc_imsi_allow = "[0-9]*",
 		.nat_imsi_deny = "[0-9]*",
 		.contype = NAT_CON_TYPE_LU,
@@ -696,6 +697,58 @@
 	msgb_free(msg);
 }
 
+static void test_dt_filter()
+{
+	int i;
+	struct msgb *msg = msgb_alloc(4096, "test_dt_filter");
+	struct bsc_nat_parsed *parsed;
+
+	struct bsc_nat *nat = bsc_nat_alloc();
+	struct bsc_connection *bsc = bsc_connection_alloc(nat);
+	struct sccp_connections *con = talloc_zero(0, struct sccp_connections);
+
+	bsc->cfg = bsc_config_alloc(nat, "foo", 23);
+	con->bsc = bsc;
+
+	msgb_reset(msg);
+	copy_to_msg(msg, id_resp, ARRAY_SIZE(id_resp));
+
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		fprintf(stderr, "FAIL: Could not parse ID resp\n");
+		abort();
+	}
+
+	if (parsed->bssap != BSSAP_MSG_DTAP) {
+		fprintf(stderr, "FAIL: It should be dtap\n");
+		abort();
+	}
+
+	/* gsm_type is actually the size of the dtap */
+	if (parsed->gsm_type < msgb_l3len(msg) - 3) {
+		fprintf(stderr, "FAIL: Not enough space for the content\n");
+		abort();
+	}
+
+	if (bsc_nat_filter_dt(bsc, msg, con, parsed) != 1) {
+		fprintf(stderr, "FAIL: Should have passed..\n");
+		abort();
+	}
+
+	/* just some basic length checking... */
+	for (i = ARRAY_SIZE(id_resp); i >= 0; --i) {
+		msgb_reset(msg);
+		copy_to_msg(msg, id_resp, ARRAY_SIZE(id_resp));
+
+		parsed = bsc_nat_parse(msg);
+		if (!parsed)
+			continue;
+
+		con->imsi_checked = 0;
+		bsc_nat_filter_dt(bsc, msg, con, parsed);
+	}
+}
+
 int main(int argc, char **argv)
 {
 	struct log_target *stderr_target;
@@ -714,6 +767,7 @@
 	test_mgcp_rewrite();
 	test_mgcp_parse();
 	test_cr_filter();
+	test_dt_filter();
 	return 0;
 }