Merge branch 'zecke/ussd-side-channel'
diff --git a/openbsc/include/openbsc/bsc_nat.h b/openbsc/include/openbsc/bsc_nat.h
index b6d0404..acecba7 100644
--- a/openbsc/include/openbsc/bsc_nat.h
+++ b/openbsc/include/openbsc/bsc_nat.h
@@ -43,12 +43,14 @@
 struct sccp_connections;
 struct bsc_nat_parsed;
 struct bsc_nat;
+struct bsc_nat_ussd_con;
 
 enum {
 	NAT_CON_TYPE_NONE,
 	NAT_CON_TYPE_LU,
 	NAT_CON_TYPE_CM_SERV_REQ,
 	NAT_CON_TYPE_PAG_RESP,
+	NAT_CON_TYPE_SSA,
 	NAT_CON_TYPE_LOCAL_REJECT,
 	NAT_CON_TYPE_OTHER,
 };
@@ -102,6 +104,7 @@
 	BCFG_CTR_CON_TYPE_LU,
 	BCFG_CTR_CON_CMSERV_RQ,
 	BCFG_CTR_CON_PAG_RESP,
+	BCFG_CTR_CON_SSA,
 	BCFG_CTR_CON_OTHER,
 };
 
@@ -163,6 +166,10 @@
 	struct {
 		struct counter *reconn;
 	} msc;
+
+	struct {
+		struct counter *reconn;
+	} ussd;
 };
 
 enum bsc_nat_acc_ctr {
@@ -230,6 +237,14 @@
 	/* filter */
 	char *acc_lst_name;
 
+	/* USSD messages  we want to match */
+	char *ussd_lst_name;
+	char *ussd_query;
+	char *ussd_token;
+	char *ussd_local;
+	struct bsc_fd ussd_listen;
+	struct bsc_nat_ussd_con *ussd_con;
+
 	/* statistics */
 	struct bsc_nat_statistics stats;
 };
@@ -311,6 +326,7 @@
 void bsc_nat_acc_lst_delete(struct bsc_nat_acc_lst *lst);
 
 struct bsc_nat_acc_lst_entry *bsc_nat_acc_lst_entry_create(struct bsc_nat_acc_lst *);
+int bsc_nat_lst_check_allow(struct bsc_nat_acc_lst *lst, const char *imsi);
 
 int bsc_nat_msc_is_connected(struct bsc_nat *nat);
 
@@ -318,4 +334,8 @@
 
 struct gsm48_hdr *bsc_unpack_dtap(struct bsc_nat_parsed *parsed, struct msgb *msg, uint32_t *len);
 
+/** USSD filtering */
+int bsc_ussd_init(struct bsc_nat *nat);
+int bsc_check_ussd(struct sccp_connections *con, struct bsc_nat_parsed *parsed, struct msgb *msg);
+
 #endif
diff --git a/openbsc/include/openbsc/ipaccess.h b/openbsc/include/openbsc/ipaccess.h
index b36811c..b370220 100644
--- a/openbsc/include/openbsc/ipaccess.h
+++ b/openbsc/include/openbsc/ipaccess.h
@@ -2,6 +2,7 @@
 #define _IPACCESS_H
 
 #include "e1_input.h"
+#include "gsm_subscriber.h"
 #include <osmocore/linuxlist.h>
 
 #define IPA_TCP_PORT_OML	3002
@@ -29,6 +30,9 @@
 	IPAC_MSGT_ID_GET	= 0x04,
 	IPAC_MSGT_ID_RESP	= 0x05,
 	IPAC_MSGT_ID_ACK	= 0x06,
+
+	/* OpenBSC extension */
+	IPAC_MSGT_SCCP_STATE	= 0xff,
 };
 
 enum ipaccess_id_tags {
@@ -43,6 +47,14 @@
 	IPAC_IDTAG_UNIT			= 0x08,
 };
 
+struct ipac_msgt_sccp_state {
+	uint8_t	src_ref[3];
+	uint8_t	dst_ref[3];
+	uint8_t trans_id;
+	uint8_t invoke_id;
+	char		imsi[GSM_IMSI_LENGTH];
+} __attribute__((packed));
+
 int ipaccess_connect(struct e1inp_line *line, struct sockaddr_in *sa);
 
 /*
diff --git a/openbsc/src/nat/Makefile.am b/openbsc/src/nat/Makefile.am
index 2f37f5c..c7c4b0e 100644
--- a/openbsc/src/nat/Makefile.am
+++ b/openbsc/src/nat/Makefile.am
@@ -6,7 +6,7 @@
 
 
 bsc_nat_SOURCES = bsc_filter.c bsc_mgcp_utils.c bsc_nat.c bsc_nat_utils.c \
-		  bsc_nat_vty.c bsc_sccp.c \
+		  bsc_nat_vty.c bsc_sccp.c bsc_ussd.c \
 		$(top_srcdir)/src/debug.c $(top_srcdir)/src/bsc_msc.c
 bsc_nat_LDADD = $(top_builddir)/src/libvty.a \
 		$(top_builddir)/src/libmgcp.a $(top_builddir)/src/libbsc.a \
diff --git a/openbsc/src/nat/bsc_nat.c b/openbsc/src/nat/bsc_nat.c
index b82c4ef..daabf09 100644
--- a/openbsc/src/nat/bsc_nat.c
+++ b/openbsc/src/nat/bsc_nat.c
@@ -854,12 +854,17 @@
 						con = NULL;
 						goto exit2;
 					}
+
+					/* hand data to a side channel */
+					if (bsc_check_ussd(con, parsed, msg) == 1) 
+						con->con_local = 2;
 				}
 
 				con_bsc = con->bsc;
 				con_msc = con->msc_con;
 				con_filter = con->con_local;
 			}
+
 			break;
 		case SCCP_MSG_TYPE_RLC:
 			con = patch_sccp_src_ref_to_msc(msg, parsed, bsc);
@@ -1272,6 +1277,12 @@
 		exit(1);
 	}
 
+	rc = bsc_ussd_init(nat);
+	if (rc != 0) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to bind the USSD socket.\n");
+		exit(1);
+	}
+
 	signal(SIGABRT, &signal_handler);
 	signal(SIGUSR1, &signal_handler);
 	signal(SIGPIPE, SIG_IGN);
diff --git a/openbsc/src/nat/bsc_nat_utils.c b/openbsc/src/nat/bsc_nat_utils.c
index dc18cff..0cef01b 100644
--- a/openbsc/src/nat/bsc_nat_utils.c
+++ b/openbsc/src/nat/bsc_nat_utils.c
@@ -54,6 +54,7 @@
 	[BCFG_CTR_CON_TYPE_LU]   = { "conn.lu",        "Conn Location Update     "},
 	[BCFG_CTR_CON_CMSERV_RQ] = { "conn.rq",        "Conn CM Service Req      "},
 	[BCFG_CTR_CON_PAG_RESP]  = { "conn.pag",       "Conn Paging Response     "},
+	[BCFG_CTR_CON_SSA]       = { "conn.ssa",       "Conn USSD                "},
 	[BCFG_CTR_CON_OTHER]     = { "conn.other",     "Conn Other               "},
 };
 
@@ -92,6 +93,7 @@
 	nat->stats.bsc.reconn = counter_alloc("nat.bsc.conn");
 	nat->stats.bsc.auth_fail = counter_alloc("nat.bsc.auth_fail");
 	nat->stats.msc.reconn = counter_alloc("nat.msc.conn");
+	nat->stats.ussd.reconn = counter_alloc("nat.ussd.conn");
 	nat->msc_ip = talloc_strdup(nat, "127.0.0.1");
 	nat->msc_port = 5000;
 	nat->auth_timeout = 2;
@@ -287,7 +289,7 @@
 	return 0;
 }
 
-static int lst_check_allow(struct bsc_nat_acc_lst *lst, const char *mi_string)
+int bsc_nat_lst_check_allow(struct bsc_nat_acc_lst *lst, const char *mi_string)
 {
 	struct bsc_nat_acc_lst_entry *entry;
 
@@ -334,7 +336,7 @@
 
 	if (bsc_lst) {
 		/* 1. BSC allow */
-		if (lst_check_allow(bsc_lst, mi_string) == 0)
+		if (bsc_nat_lst_check_allow(bsc_lst, mi_string) == 0)
 			return 1;
 
 		/* 2. BSC deny */
@@ -391,7 +393,7 @@
 
 static int _cr_check_cm_serv_req(struct bsc_connection *bsc,
 				 uint8_t *data, unsigned int length,
-				 char **imsi)
+				 int *con_type, char **imsi)
 {
 	static const uint32_t classmark_offset =
 				offsetof(struct gsm48_service_request, classmark);
@@ -410,6 +412,8 @@
 	}
 
 	req = (struct gsm48_service_request *) data;
+	if (req->cm_service_type == 0x8)
+		*con_type = NAT_CON_TYPE_SSA;
 	rc = gsm48_extract_mi((uint8_t *) &req->classmark,
 			      length - classmark_offset, mi_string, &mi_type);
 	if (rc < 0) {
@@ -537,7 +541,9 @@
 	} else if (hdr48->proto_discr == GSM48_PDISC_MM &&
 		  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), imsi);
+		return _cr_check_cm_serv_req(bsc, &hdr48->data[0],
+					     hdr48_len - sizeof(*hdr48),
+					     con_type, imsi);
 	} else if (hdr48->proto_discr == GSM48_PDISC_RR &&
 		   msg_type == GSM48_MT_RR_PAG_RESP) {
 		*con_type = NAT_CON_TYPE_PAG_RESP;
@@ -614,6 +620,7 @@
 	[NAT_CON_TYPE_LU] = "Location Update",
 	[NAT_CON_TYPE_CM_SERV_REQ] = "CM Serv Req",
 	[NAT_CON_TYPE_PAG_RESP] = "Paging Response",
+	[NAT_CON_TYPE_SSA] = "Supplementar Service Activation",
 	[NAT_CON_TYPE_LOCAL_REJECT] = "Local Reject",
 	[NAT_CON_TYPE_OTHER] = "Other",
 };
@@ -693,6 +700,7 @@
 	[NAT_CON_TYPE_LU]		= BCFG_CTR_CON_TYPE_LU,
 	[NAT_CON_TYPE_CM_SERV_REQ]	= BCFG_CTR_CON_CMSERV_RQ,
 	[NAT_CON_TYPE_PAG_RESP]		= BCFG_CTR_CON_PAG_RESP,
+	[NAT_CON_TYPE_SSA]		= BCFG_CTR_CON_SSA,
 	[NAT_CON_TYPE_LOCAL_REJECT]	= -1,
 	[NAT_CON_TYPE_OTHER]		= BCFG_CTR_CON_OTHER,
 };
diff --git a/openbsc/src/nat/bsc_nat_vty.c b/openbsc/src/nat/bsc_nat_vty.c
index 9822e5c..9eb8ebc 100644
--- a/openbsc/src/nat/bsc_nat_vty.c
+++ b/openbsc/src/nat/bsc_nat_vty.c
@@ -78,6 +78,14 @@
 	vty_out(vty, " ip-dscp %d%s", _nat->bsc_ip_dscp, VTY_NEWLINE);
 	if (_nat->acc_lst_name)
 		vty_out(vty, " access-list-name %s%s", _nat->acc_lst_name, VTY_NEWLINE);
+	if (_nat->ussd_lst_name)
+		vty_out(vty, " ussd-list-name %s%s", _nat->ussd_lst_name, VTY_NEWLINE);
+	if (_nat->ussd_query)
+		vty_out(vty, " ussd-query %s%s", _nat->ussd_query, VTY_NEWLINE);
+	if (_nat->ussd_token)
+		vty_out(vty, " ussd-token %s%s", _nat->ussd_token, VTY_NEWLINE);
+	if (_nat->ussd_local)
+		vty_out(vty, " ussd-local-ip %s%s", _nat->ussd_local, VTY_NEWLINE);
 
 	llist_for_each_entry(lst, &_nat->access_lists, list) {
 		write_acc_lst(vty, lst);
@@ -395,6 +403,44 @@
 	return CMD_SUCCESS;
 }
 
+DEFUN(cfg_nat_ussd_lst_name,
+      cfg_nat_ussd_lst_name_cmd,
+      "ussd-list-name NAME",
+      "Set the name of the access list to check for IMSIs for USSD message\n"
+      "The name of the access list for HLR USSD handling")
+{
+	bsc_replace_string(_nat, &_nat->ussd_lst_name, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_ussd_query,
+      cfg_nat_ussd_query_cmd,
+      "ussd-query QUERY",
+      "Set the USSD query to match with the ussd-list-name\n"
+      "The query to match")
+{
+	bsc_replace_string(_nat, &_nat->ussd_query, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_ussd_token,
+      cfg_nat_ussd_token_cmd,
+      "ussd-token TOKEN",
+      "Set the token used to identify the USSD module\n" "Secret key\n")
+{
+	bsc_replace_string(_nat, &_nat->ussd_token, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nat_ussd_local,
+      cfg_nat_ussd_local_cmd,
+      "ussd-local-ip A.B.C.D",
+      "Set the IP to listen for the USSD Provider\n" "IP Address\n")
+{
+	bsc_replace_string(_nat, &_nat->ussd_local, argv[0]);
+	return CMD_SUCCESS;
+}
+
 /* per BSC configuration */
 DEFUN(cfg_bsc, cfg_bsc_cmd, "bsc BSC_NR", "Select a BSC to configure")
 {
@@ -632,6 +678,10 @@
 	install_element(NAT_NODE, &cfg_nat_bsc_ip_dscp_cmd);
 	install_element(NAT_NODE, &cfg_nat_bsc_ip_tos_cmd);
 	install_element(NAT_NODE, &cfg_nat_acc_lst_name_cmd);
+	install_element(NAT_NODE, &cfg_nat_ussd_lst_name_cmd);
+	install_element(NAT_NODE, &cfg_nat_ussd_query_cmd);
+	install_element(NAT_NODE, &cfg_nat_ussd_token_cmd);
+	install_element(NAT_NODE, &cfg_nat_ussd_local_cmd);
 
 	/* access-list */
 	install_element(NAT_NODE, &cfg_lst_imsi_allow_cmd);
diff --git a/openbsc/src/nat/bsc_ussd.c b/openbsc/src/nat/bsc_ussd.c
new file mode 100644
index 0000000..3729d91
--- /dev/null
+++ b/openbsc/src/nat/bsc_ussd.c
@@ -0,0 +1,361 @@
+/* USSD Filter Code */
+
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <openbsc/bsc_nat.h>
+#include <openbsc/bsc_nat_sccp.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/socket.h>
+
+#include <osmocore/protocol/gsm_08_08.h>
+#include <osmocore/gsm0480.h>
+#include <osmocore/talloc.h>
+#include <osmocore/tlv.h>
+
+#include <osmocom/sccp/sccp.h>
+
+#include <sys/socket.h>
+#include <string.h>
+#include <unistd.h>
+
+struct bsc_nat_ussd_con {
+	struct write_queue queue;
+	struct bsc_nat *nat;
+	int authorized;
+
+	struct timer_list auth_timeout;
+};
+
+static void ussd_auth_con(struct tlv_parsed *, struct bsc_nat_ussd_con *);
+
+static struct bsc_nat_ussd_con *bsc_nat_ussd_alloc(struct bsc_nat *nat)
+{
+	struct bsc_nat_ussd_con *con;
+
+	con = talloc_zero(nat, struct bsc_nat_ussd_con);
+	if (!con)
+		return NULL;
+
+	con->nat = nat;
+	return con;
+}
+
+static void bsc_nat_ussd_destroy(struct bsc_nat_ussd_con *con)
+{
+	if (con->nat->ussd_con == con)
+		con->nat->ussd_con = NULL;
+	close(con->queue.bfd.fd);
+	bsc_unregister_fd(&con->queue.bfd);
+	bsc_del_timer(&con->auth_timeout);
+	write_queue_clear(&con->queue);
+	talloc_free(con);
+}
+
+static int forward_sccp(struct bsc_nat *nat, struct msgb *msg)
+{
+	struct sccp_connections *con;
+	struct bsc_nat_parsed *parsed;
+
+
+	parsed = bsc_nat_parse(msg);
+	if (!parsed) {
+		LOGP(DNAT, LOGL_ERROR, "Can not parse msg from USSD.\n");
+		msgb_free(msg);
+		return -1;
+	}
+
+	if (!parsed->dest_local_ref) {
+		LOGP(DNAT, LOGL_ERROR, "No destination local reference.\n");
+		msgb_free(msg);
+		return -1;
+	}
+
+	con = bsc_nat_find_con_by_bsc(nat, parsed->dest_local_ref);
+	if (!con || !con->bsc) {
+		LOGP(DNAT, LOGL_ERROR, "No active connection found.\n");
+		msgb_free(msg);
+		return -1;
+	}
+
+	talloc_free(parsed);
+	bsc_write_msg(&con->bsc->write_queue, msg);
+	return 0;
+}
+
+static int ussd_read_cb(struct bsc_fd *bfd)
+{
+	int error;
+	struct bsc_nat_ussd_con *conn = bfd->data;
+	struct msgb *msg = ipaccess_read_msg(bfd, &error);
+	struct ipaccess_head *hh;
+
+	if (!msg) {
+		LOGP(DNAT, LOGL_ERROR, "USSD Connection was lost.\n");
+		bsc_nat_ussd_destroy(conn);
+		return -1;
+	}
+
+	LOGP(DNAT, LOGL_NOTICE, "MSG from USSD: %s proto: %d\n",
+		hexdump(msg->data, msg->len), msg->l2h[0]);
+	hh = (struct ipaccess_head *) msg->data;
+
+	if (hh->proto == IPAC_PROTO_IPACCESS) {
+		if (msg->l2h[0] == IPAC_MSGT_ID_RESP) {
+			struct tlv_parsed tvp;
+			ipaccess_idtag_parse(&tvp,
+					     (unsigned char *) msg->l2h + 2,
+					     msgb_l2len(msg) - 2);
+			if (TLVP_PRESENT(&tvp, IPAC_IDTAG_UNITNAME))
+				ussd_auth_con(&tvp, conn);
+		}
+
+		msgb_free(msg);
+	} else if (hh->proto == IPAC_PROTO_SCCP) {
+		forward_sccp(conn->nat, msg);
+	} else {
+		msgb_free(msg);
+	}
+
+	return 0;
+}
+
+static void ussd_auth_cb(void *_data)
+{
+	LOGP(DNAT, LOGL_ERROR, "USSD module didn't authenticate\n");
+	bsc_nat_ussd_destroy((struct bsc_nat_ussd_con *) _data);
+}
+
+static void ussd_auth_con(struct tlv_parsed *tvp, struct bsc_nat_ussd_con *conn)
+{
+	const char *token;
+	int len;
+	if (!conn->nat->ussd_token) {
+		LOGP(DNAT, LOGL_ERROR, "No USSD token set. Closing\n");
+		bsc_nat_ussd_destroy(conn);
+		return;
+	}
+
+	token = (const char *) TLVP_VAL(tvp, IPAC_IDTAG_UNITNAME);
+ 	len = TLVP_LEN(tvp, IPAC_IDTAG_UNITNAME);
+	if (strncmp(conn->nat->ussd_token, token, len) != 0) {
+		LOGP(DNAT, LOGL_ERROR, "Wrong USSD token by client: %d\n",
+			conn->queue.bfd.fd);
+		bsc_nat_ussd_destroy(conn);
+		return;
+	}
+
+	/* it is authenticated now */
+	if (conn->nat->ussd_con && conn->nat->ussd_con != conn)
+		bsc_nat_ussd_destroy(conn->nat->ussd_con);
+
+	LOGP(DNAT, LOGL_ERROR, "USSD token specified. USSD provider is connected.\n");
+	bsc_del_timer(&conn->auth_timeout);
+	conn->authorized = 1;
+	conn->nat->ussd_con = conn;
+}
+
+static void ussd_start_auth(struct bsc_nat_ussd_con *conn)
+{
+	struct msgb *msg;
+
+	conn->auth_timeout.data = conn;
+	conn->auth_timeout.cb = ussd_auth_cb;
+	bsc_schedule_timer(&conn->auth_timeout, conn->nat->auth_timeout, 0);
+
+	msg = msgb_alloc_headroom(4096, 128, "auth message");
+	if (!msg) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate auth msg\n");
+		return;
+	}
+
+	msgb_v_put(msg, IPAC_MSGT_ID_GET);
+	bsc_do_write(&conn->queue, msg, IPAC_PROTO_IPACCESS);
+}
+
+static int ussd_listen_cb(struct bsc_fd *bfd, unsigned int what)
+{
+	struct bsc_nat_ussd_con *conn;
+	struct bsc_nat *nat;
+	struct sockaddr_in sa;
+	socklen_t sa_len = sizeof(sa);
+	int fd;
+
+	if (!(what & BSC_FD_READ))
+		return 0;
+
+	fd = accept(bfd->fd, (struct sockaddr *) &sa, &sa_len);
+	if (fd < 0) {
+		perror("accept");
+		return fd;
+	}
+
+	nat = (struct bsc_nat *) bfd->data;
+	counter_inc(nat->stats.ussd.reconn);
+
+	conn = bsc_nat_ussd_alloc(nat);
+	if (!conn) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to allocate USSD con struct.\n");
+		close(fd);
+		return -1;
+	}
+
+	write_queue_init(&conn->queue, 10);
+	conn->queue.bfd.data = conn;
+	conn->queue.bfd.fd = fd;
+	conn->queue.bfd.when = BSC_FD_READ;
+	conn->queue.read_cb = ussd_read_cb;
+	conn->queue.write_cb = bsc_write_cb;
+
+	if (bsc_register_fd(&conn->queue.bfd) < 0) {
+		LOGP(DNAT, LOGL_ERROR, "Failed to register USSD fd.\n");
+		bsc_nat_ussd_destroy(conn);
+		return -1;
+	}
+
+	LOGP(DNAT, LOGL_NOTICE, "USSD Connection on %d with IP: %s\n",
+	     fd, inet_ntoa(sa.sin_addr));
+
+	/* do authentication */
+	ussd_start_auth(conn);
+	return 0;
+}
+
+int bsc_ussd_init(struct bsc_nat *nat)
+{
+	struct in_addr addr;
+
+	addr.s_addr = INADDR_ANY;
+	if (nat->ussd_local)
+		inet_aton(nat->ussd_local, &addr);
+
+	nat->ussd_listen.data = nat;
+	return make_sock(&nat->ussd_listen, IPPROTO_TCP,
+			 ntohl(addr.s_addr), 5001, ussd_listen_cb);
+}
+
+static int forward_ussd(struct sccp_connections *con, const struct ussd_request *req,
+			struct msgb *input)
+{
+	struct msgb *msg, *copy;
+	struct ipac_msgt_sccp_state *state;
+	struct bsc_nat_ussd_con *ussd;
+
+	if (!con->bsc->nat->ussd_con)
+		return -1;
+
+	msg = msgb_alloc_headroom(4096, 128, "forward ussd");
+	if (!msg) {
+		LOGP(DNAT, LOGL_ERROR, "Allocation failed, not forwarding.\n");
+		return -1;
+	}
+
+	copy = msgb_alloc_headroom(4096, 128, "forward bts");
+	if (!copy) {
+		LOGP(DNAT, LOGL_ERROR, "Allocation failed, not forwarding.\n");
+		msgb_free(msg);
+		return -1;
+	}
+
+	copy->l2h = msgb_put(copy, msgb_l2len(input));
+	memcpy(copy->l2h, input->l2h, msgb_l2len(input));
+
+	msg->l2h = msgb_put(msg, 1);
+	msg->l2h[0] = IPAC_MSGT_SCCP_STATE;
+
+	/* fill out the data */
+	state = (struct ipac_msgt_sccp_state *) msgb_put(msg, sizeof(*state));
+	state->trans_id = req->transaction_id;
+	state->invoke_id = req->invoke_id;
+	memcpy(&state->src_ref, &con->remote_ref, sizeof(con->remote_ref));
+	memcpy(&state->dst_ref, &con->real_ref, sizeof(con->real_ref));
+	memcpy(state->imsi, con->imsi, strlen(con->imsi));
+
+	ussd = con->bsc->nat->ussd_con;
+	bsc_do_write(&ussd->queue, msg, IPAC_PROTO_IPACCESS);
+	bsc_do_write(&ussd->queue, copy, IPAC_PROTO_SCCP);
+
+	return 0;
+}
+
+int bsc_check_ussd(struct sccp_connections *con, struct bsc_nat_parsed *parsed,
+		   struct msgb *msg)
+{
+	uint32_t len;
+	uint8_t msg_type;
+	struct gsm48_hdr *hdr48;
+	struct bsc_nat_acc_lst *lst;
+	struct ussd_request req;
+
+	/*
+	 * various checks to avoid the decoding work. Right now we only want to
+	 * decode if the connection was created for USSD, we do have a USSD access
+	 * list, a query, a IMSI and such...
+	 */
+	if (con->con_type != NAT_CON_TYPE_SSA)
+		return 0;
+
+	if (!con->imsi)
+		return 0;
+
+	if (!con->bsc->nat->ussd_lst_name)
+		return 0;
+	if (!con->bsc->nat->ussd_query)
+		return 0;
+
+	if (parsed->bssap != BSSAP_MSG_DTAP)
+		return 0;
+
+	if (strlen(con->imsi) > GSM_IMSI_LENGTH)
+		return 0;
+
+	hdr48 = bsc_unpack_dtap(parsed, msg, &len);
+	if (!hdr48)
+		return 0;
+
+	msg_type = hdr48->msg_type & 0xbf;
+	if (hdr48->proto_discr != GSM48_PDISC_NC_SS || msg_type != GSM0480_MTYPE_REGISTER)
+		return 0;
+
+	/* now check if it is a IMSI we care about */
+	lst = bsc_nat_acc_lst_find(con->bsc->nat, con->bsc->nat->ussd_lst_name);
+	if (!lst)
+		return 0;
+
+	if (bsc_nat_lst_check_allow(lst, con->imsi) != 0)
+		return 0;
+
+	/* now decode the message and see if we really want to handle it */
+	memset(&req, 0, sizeof(req));
+	if (gsm0480_decode_ussd_request(hdr48, len, &req) != 1)
+		return 0;
+	if (req.text[0] == 0xff)
+		return 0;
+
+	if (strcmp(req.text, con->bsc->nat->ussd_query) != 0)
+		return 0;
+
+	/* found a USSD query for our subscriber */
+	LOGP(DNAT, LOGL_NOTICE, "Found USSD query for %s\n", con->imsi);
+	if (forward_ussd(con, &req, msg) != 0)
+		return 0;
+	return 1;
+}