re-structure the OpenBSC directory layout

The new structure divides the code into a number of libraries
for the BSC core functionality, MSC core functionality, Abis transport,
TRAU and other bits.

This doesn't introduce any functional code change but simply moves
around files and alters Makefile.am accordingly.

Next step would be to disentangle a lot of the inter-library
dependencies and make the individual bits of code more independent.
diff --git a/openbsc/src/msc/Makefile.am b/openbsc/src/msc/Makefile.am
new file mode 100644
index 0000000..7d895c3
--- /dev/null
+++ b/openbsc/src/msc/Makefile.am
@@ -0,0 +1,19 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)
+AM_CFLAGS=-Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(COVERAGE_LDFLAGS)
+
+noinst_LIBRARIES = libmsc.a
+
+libmsc_a_SOURCES =	auth.c \
+			db.c \
+			gsm_04_08.c gsm_04_11.c gsm_04_80.c \
+			gsm_subscriber.c \
+			mncc.c mncc_builtin.c mncc_sock.c \
+			rrlp.c \
+			silent_call.c \
+			sms_queue.c \
+			token_auth.c \
+			ussd.c \
+			vty_interface_layer3.c \
+			osmo_msc.c
+
diff --git a/openbsc/src/msc/a3a8.c b/openbsc/src/msc/a3a8.c
new file mode 100644
index 0000000..04470ba
--- /dev/null
+++ b/openbsc/src/msc/a3a8.c
@@ -0,0 +1,269 @@
+/* An implementation of the GSM A3A8 algorithm.  (Specifically, COMP128.) 
+ */
+
+/* Copyright 1998, Marc Briceno, Ian Goldberg, and David Wagner.
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ *  * Neither the name of the authors nor the names of the contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Coded in C merely because C is a much more precise, concise form of
+ * expression for these purposes.  See Judge Patel if you have any problems
+ * with this...
+ * Of course, it's only authentication, so it should be exportable for the
+ * usual boring reasons.
+ */
+
+typedef unsigned char Byte;
+
+#include <stdio.h>
+/* #define TEST */
+ 
+/*
+ * rand[0..15]: the challenge from the base station
+ * key[0..15]: the SIM's A3/A8 long-term key Ki
+ * simoutput[0..11]: what you'd get back if you fed rand and key to a real
+ * SIM.
+ *
+ *   The GSM spec states that simoutput[0..3] is SRES,
+ *   and simoutput[4..11] is Kc (the A5 session key).
+ *   (See GSM 11.11, Section 8.16.  See also the leaked document
+ *   referenced below.)
+ *   Note that Kc is bits 74..127 of the COMP128 output, followed by 10
+ *   zeros.
+ *   In other words, A5 is keyed with only 54 bits of entropy. This
+ *   represents a deliberate weakening of the key used for voice privacy
+ *   by a factor of over 1000.
+ * 
+ * Verified with a Pacific Bell Schlumberger SIM.  Your mileage may vary.
+ *
+ * Marc Briceno <marc@scard.org>, Ian Goldberg <iang@cs.berkeley.edu>,
+ * and David Wagner <daw@cs.berkeley.edu>
+ */
+
+void A3A8(/* in */ Byte rand[16], /* in */ Byte key[16],
+	/* out */ Byte simoutput[12]);
+
+/* The compression tables. */
+static const Byte table_0[512] = {
+        102,177,186,162,  2,156,112, 75, 55, 25,  8, 12,251,193,246,188,
+        109,213,151, 53, 42, 79,191,115,233,242,164,223,209,148,108,161,
+        252, 37,244, 47, 64,211,  6,237,185,160,139,113, 76,138, 59, 70,
+         67, 26, 13,157, 63,179,221, 30,214, 36,166, 69,152,124,207,116,
+        247,194, 41, 84, 71,  1, 49, 14, 95, 35,169, 21, 96, 78,215,225,
+        182,243, 28, 92,201,118,  4, 74,248,128, 17, 11,146,132,245, 48,
+        149, 90,120, 39, 87,230,106,232,175, 19,126,190,202,141,137,176,
+        250, 27,101, 40,219,227, 58, 20, 51,178, 98,216,140, 22, 32,121,
+         61,103,203, 72, 29,110, 85,212,180,204,150,183, 15, 66,172,196,
+         56,197,158,  0,100, 45,153,  7,144,222,163,167, 60,135,210,231,
+        174,165, 38,249,224, 34,220,229,217,208,241, 68,206,189,125,255,
+        239, 54,168, 89,123,122, 73,145,117,234,143, 99,129,200,192, 82,
+        104,170,136,235, 93, 81,205,173,236, 94,105, 52, 46,228,198,  5,
+         57,254, 97,155,142,133,199,171,187, 50, 65,181,127,107,147,226,
+        184,218,131, 33, 77, 86, 31, 44, 88, 62,238, 18, 24, 43,154, 23,
+         80,159,134,111,  9,114,  3, 91, 16,130, 83, 10,195,240,253,119,
+        177,102,162,186,156,  2, 75,112, 25, 55, 12,  8,193,251,188,246,
+        213,109, 53,151, 79, 42,115,191,242,233,223,164,148,209,161,108,
+         37,252, 47,244,211, 64,237,  6,160,185,113,139,138, 76, 70, 59,
+         26, 67,157, 13,179, 63, 30,221, 36,214, 69,166,124,152,116,207,
+        194,247, 84, 41,  1, 71, 14, 49, 35, 95, 21,169, 78, 96,225,215,
+        243,182, 92, 28,118,201, 74,  4,128,248, 11, 17,132,146, 48,245,
+         90,149, 39,120,230, 87,232,106, 19,175,190,126,141,202,176,137,
+         27,250, 40,101,227,219, 20, 58,178, 51,216, 98, 22,140,121, 32,
+        103, 61, 72,203,110, 29,212, 85,204,180,183,150, 66, 15,196,172,
+        197, 56,  0,158, 45,100,  7,153,222,144,167,163,135, 60,231,210,
+        165,174,249, 38, 34,224,229,220,208,217, 68,241,189,206,255,125,
+         54,239, 89,168,122,123,145, 73,234,117, 99,143,200,129, 82,192,
+        170,104,235,136, 81, 93,173,205, 94,236, 52,105,228, 46,  5,198,
+        254, 57,155, 97,133,142,171,199, 50,187,181, 65,107,127,226,147,
+        218,184, 33,131, 86, 77, 44, 31, 62, 88, 18,238, 43, 24, 23,154,
+        159, 80,111,134,114,  9, 91,  3,130, 16, 10, 83,240,195,119,253
+    }, table_1[256] = {
+         19, 11, 80,114, 43,  1, 69, 94, 39, 18,127,117, 97,  3, 85, 43,
+         27,124, 70, 83, 47, 71, 63, 10, 47, 89, 79,  4, 14, 59, 11,  5,
+         35,107,103, 68, 21, 86, 36, 91, 85,126, 32, 50,109, 94,120,  6,
+         53, 79, 28, 45, 99, 95, 41, 34, 88, 68, 93, 55,110,125,105, 20,
+         90, 80, 76, 96, 23, 60, 89, 64,121, 56, 14, 74,101,  8, 19, 78,
+         76, 66,104, 46,111, 50, 32,  3, 39,  0, 58, 25, 92, 22, 18, 51,
+         57, 65,119,116, 22,109,  7, 86, 59, 93, 62,110, 78, 99, 77, 67,
+         12,113, 87, 98,102,  5, 88, 33, 38, 56, 23,  8, 75, 45, 13, 75,
+         95, 63, 28, 49,123,120, 20,112, 44, 30, 15, 98,106,  2,103, 29,
+         82,107, 42,124, 24, 30, 41, 16,108,100,117, 40, 73, 40,  7,114,
+         82,115, 36,112, 12,102,100, 84, 92, 48, 72, 97,  9, 54, 55, 74,
+        113,123, 17, 26, 53, 58,  4,  9, 69,122, 21,118, 42, 60, 27, 73,
+        118,125, 34, 15, 65,115, 84, 64, 62, 81, 70,  1, 24,111,121, 83,
+        104, 81, 49,127, 48,105, 31, 10,  6, 91, 87, 37, 16, 54,116,126,
+         31, 38, 13,  0, 72,106, 77, 61, 26, 67, 46, 29, 96, 37, 61, 52,
+        101, 17, 44,108, 71, 52, 66, 57, 33, 51, 25, 90,  2,119,122, 35
+    }, table_2[128] = {
+         52, 50, 44,  6, 21, 49, 41, 59, 39, 51, 25, 32, 51, 47, 52, 43,
+         37,  4, 40, 34, 61, 12, 28,  4, 58, 23,  8, 15, 12, 22,  9, 18,
+         55, 10, 33, 35, 50,  1, 43,  3, 57, 13, 62, 14,  7, 42, 44, 59,
+         62, 57, 27,  6,  8, 31, 26, 54, 41, 22, 45, 20, 39,  3, 16, 56,
+         48,  2, 21, 28, 36, 42, 60, 33, 34, 18,  0, 11, 24, 10, 17, 61,
+         29, 14, 45, 26, 55, 46, 11, 17, 54, 46,  9, 24, 30, 60, 32,  0,
+         20, 38,  2, 30, 58, 35,  1, 16, 56, 40, 23, 48, 13, 19, 19, 27,
+         31, 53, 47, 38, 63, 15, 49,  5, 37, 53, 25, 36, 63, 29,  5,  7
+    }, table_3[64] = {
+          1,  5, 29,  6, 25,  1, 18, 23, 17, 19,  0,  9, 24, 25,  6, 31,
+         28, 20, 24, 30,  4, 27,  3, 13, 15, 16, 14, 18,  4,  3,  8,  9,
+         20,  0, 12, 26, 21,  8, 28,  2, 29,  2, 15,  7, 11, 22, 14, 10,
+         17, 21, 12, 30, 26, 27, 16, 31, 11,  7, 13, 23, 10,  5, 22, 19
+    }, table_4[32] = {
+         15, 12, 10,  4,  1, 14, 11,  7,  5,  0, 14,  7,  1,  2, 13,  8,
+         10,  3,  4,  9,  6,  0,  3,  2,  5,  6,  8,  9, 11, 13, 15, 12
+    }, *table[5] = { table_0, table_1, table_2, table_3, table_4 };
+
+/*
+ * This code derived from a leaked document from the GSM standards.
+ * Some missing pieces were filled in by reverse-engineering a working SIM.
+ * We have verified that this is the correct COMP128 algorithm.
+ * 
+ * The first page of the document identifies it as
+ * 	_Technical Information: GSM System Security Study_.
+ * 	10-1617-01, 10th June 1988.
+ * The bottom of the title page is marked
+ * 	Racal Research Ltd.
+ * 	Worton Drive, Worton Grange Industrial Estate,
+ * 	Reading, Berks. RG2 0SB, England.
+ * 	Telephone: Reading (0734) 868601   Telex: 847152
+ * The relevant bits are in Part I, Section 20 (pages 66--67).  Enjoy!
+ * 
+ * Note: There are three typos in the spec (discovered by
+ * reverse-engineering).
+ * First, "z = (2 * x[n] + x[n]) mod 2^(9-j)" should clearly read
+ * "z = (2 * x[m] + x[n]) mod 2^(9-j)".
+ * Second, the "k" loop in the "Form bits from bytes" section is severely
+ * botched: the k index should run only from 0 to 3, and clearly the range
+ * on "the (8-k)th bit of byte j" is also off (should be 0..7, not 1..8,
+ * to be consistent with the subsequent section).
+ * Third, SRES is taken from the first 8 nibbles of x[], not the last 8 as
+ * claimed in the document.  (And the document doesn't specify how Kc is
+ * derived, but that was also easily discovered with reverse engineering.)
+ * All of these typos have been corrected in the following code.
+ */
+
+void A3A8(/* in */ Byte rand[16], /* in */ Byte key[16],
+	/* out */ Byte simoutput[12])
+{
+	Byte x[32], bit[128];
+	int i, j, k, l, m, n, y, z, next_bit;
+
+	/* ( Load RAND into last 16 bytes of input ) */
+	for (i=16; i<32; i++)
+		x[i] = rand[i-16];
+
+	/* ( Loop eight times ) */
+	for (i=1; i<9; i++) {
+		/* ( Load key into first 16 bytes of input ) */
+		for (j=0; j<16; j++)
+			x[j] = key[j];
+		/* ( Perform substitutions ) */
+		for (j=0; j<5; j++)
+			for (k=0; k<(1<<j); k++)
+				for (l=0; l<(1<<(4-j)); l++) {
+					m = l + k*(1<<(5-j));
+					n = m + (1<<(4-j));
+					y = (x[m]+2*x[n]) % (1<<(9-j));
+					z = (2*x[m]+x[n]) % (1<<(9-j));
+					x[m] = table[j][y];
+					x[n] = table[j][z];
+				}
+		/* ( Form bits from bytes ) */
+		for (j=0; j<32; j++)
+			for (k=0; k<4; k++)
+				bit[4*j+k] = (x[j]>>(3-k)) & 1;
+		/* ( Permutation but not on the last loop ) */
+		if (i < 8)
+			for (j=0; j<16; j++) {
+				x[j+16] = 0;
+				for (k=0; k<8; k++) {
+					next_bit = ((8*j + k)*17) % 128;
+					x[j+16] |= bit[next_bit] << (7-k);
+				}
+			}
+	}
+
+	/*
+	 * ( At this stage the vector x[] consists of 32 nibbles.
+	 *   The first 8 of these are taken as the output SRES. )
+	 */
+
+	/* The remainder of the code is not given explicitly in the
+	 * standard, but was derived by reverse-engineering.
+	 */
+
+	for (i=0; i<4; i++)
+		simoutput[i] = (x[2*i]<<4) | x[2*i+1];
+	for (i=0; i<6; i++)
+		simoutput[4+i] = (x[2*i+18]<<6) | (x[2*i+18+1]<<2)
+				| (x[2*i+18+2]>>2);
+	simoutput[4+6] = (x[2*6+18]<<6) | (x[2*6+18+1]<<2);
+	simoutput[4+7] = 0;
+}
+
+
+#ifdef TEST
+int hextoint(char x)
+{
+	x = toupper(x);
+	if (x >= 'A' && x <= 'F')
+		return x-'A'+10;
+	else if (x >= '0' && x <= '9')
+		return x-'0';
+	fprintf(stderr, "bad input.\n");
+	exit(1);
+}
+
+int main(int argc, char **argv)
+{
+	Byte key[16], rand[16], simoutput[12];
+	int i;
+
+	if (argc != 3 || strlen(argv[1]) != 34 || strlen(argv[2]) != 34
+			|| strncmp(argv[1], "0x", 2) != 0
+			|| strncmp(argv[2], "0x", 2) != 0) {
+		fprintf(stderr, "Usage: %s 0x<key> 0x<rand>\n", argv[0]);
+		exit(1);
+	}
+
+	for (i=0; i<16; i++)
+		key[i] = (hextoint(argv[1][2*i+2])<<4)
+			| hextoint(argv[1][2*i+3]);
+	for (i=0; i<16; i++)
+		rand[i] = (hextoint(argv[2][2*i+2])<<4)
+			 | hextoint(argv[2][2*i+3]);
+	A3A8(rand, key, simoutput);
+	printf("simoutput: ");
+	for (i=0; i<12; i++)
+		printf("%02X", simoutput[i]);
+	printf("\n");
+	return 0;
+}
+#endif
+
diff --git a/openbsc/src/msc/auth.c b/openbsc/src/msc/auth.c
new file mode 100644
index 0000000..e09bde5
--- /dev/null
+++ b/openbsc/src/msc/auth.c
@@ -0,0 +1,132 @@
+/* Authentication related functions */
+
+/*
+ * (C) 2010 by Sylvain Munaut <tnt@246tNt.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/db.h>
+#include <openbsc/debug.h>
+#include <openbsc/auth.h>
+#include <openbsc/gsm_data.h>
+
+#include <osmocore/comp128.h>
+
+#include <stdlib.h>
+
+
+static int
+_use_xor(struct gsm_auth_info *ainfo, struct gsm_auth_tuple *atuple)
+{
+	int i, l = ainfo->a3a8_ki_len;
+
+	if ((l > A38_XOR_MAX_KEY_LEN) || (l < A38_XOR_MIN_KEY_LEN)) {
+		LOGP(DMM, LOGL_ERROR, "Invalid XOR key (len=%d) %s\n",
+			ainfo->a3a8_ki_len,
+			hexdump(ainfo->a3a8_ki, ainfo->a3a8_ki_len));
+		return -1;
+	}
+
+	for (i=0; i<4; i++)
+		atuple->sres[i] = atuple->rand[i] ^ ainfo->a3a8_ki[i];
+	for (i=4; i<12; i++)
+		atuple->kc[i-4] = atuple->rand[i] ^ ainfo->a3a8_ki[i];
+
+	return 0;
+}
+
+static int
+_use_comp128_v1(struct gsm_auth_info *ainfo, struct gsm_auth_tuple *atuple)
+{
+	if (ainfo->a3a8_ki_len != A38_COMP128_KEY_LEN) {
+		LOGP(DMM, LOGL_ERROR, "Invalid COMP128v1 key (len=%d) %s\n",
+			ainfo->a3a8_ki_len,
+			hexdump(ainfo->a3a8_ki, ainfo->a3a8_ki_len));
+		return -1;
+	}
+
+	comp128(ainfo->a3a8_ki, atuple->rand, atuple->sres, atuple->kc);
+
+	return 0;
+}
+
+/* Return values 
+ *  -1 -> Internal error
+ *   0 -> Not available
+ *   1 -> Tuple returned, need to do auth, then enable cipher
+ *   2 -> Tuple returned, need to enable cipher
+ */
+int auth_get_tuple_for_subscr(struct gsm_auth_tuple *atuple,
+                              struct gsm_subscriber *subscr, int key_seq)
+{
+	struct gsm_auth_info ainfo;
+	int i, rc;
+
+	/* Get subscriber info (if any) */
+	rc = db_get_authinfo_for_subscr(&ainfo, subscr);
+	if (rc < 0) {
+		LOGP(DMM, LOGL_NOTICE,
+			"No retrievable Ki for subscriber, skipping auth\n");
+		return rc == -ENOENT ? AUTH_NOT_AVAIL : -1;
+	}
+
+	/* If possible, re-use the last tuple and skip auth */
+	rc = db_get_lastauthtuple_for_subscr(atuple, subscr);
+	if ((rc == 0) &&
+	    (key_seq != GSM_KEY_SEQ_INVAL) &&
+	    (atuple->use_count < 3))
+	{
+		atuple->use_count++;
+		db_sync_lastauthtuple_for_subscr(atuple, subscr);
+		DEBUGP(DMM, "Auth tuple use < 3, just doing ciphering\n");
+		return AUTH_DO_CIPH;
+	}
+
+	/* Generate a new one */
+	atuple->use_count = 1;
+	atuple->key_seq = (atuple->key_seq + 1) % 7;
+        for (i=0; i<sizeof(atuple->rand); i++)
+                atuple->rand[i] = random() & 0xff;
+
+	switch (ainfo.auth_algo) {
+	case AUTH_ALGO_NONE:
+		DEBUGP(DMM, "No authentication for subscriber\n");
+		return 0;
+
+	case AUTH_ALGO_XOR:
+		if (_use_xor(&ainfo, atuple))
+			return 0;
+		break;
+
+	case AUTH_ALGO_COMP128v1:
+		if (_use_comp128_v1(&ainfo, atuple))
+			return 0;
+		break;
+
+	default:
+		DEBUGP(DMM, "Unsupported auth type algo_id=%d\n",
+			ainfo.auth_algo);
+		return 0;
+	}
+
+        db_sync_lastauthtuple_for_subscr(atuple, subscr);
+
+	DEBUGP(DMM, "Need to do authentication and ciphering\n");
+	return AUTH_DO_AUTH_THAN_CIPH;
+}
+
diff --git a/openbsc/src/msc/db.c b/openbsc/src/msc/db.c
new file mode 100644
index 0000000..95a7d36
--- /dev/null
+++ b/openbsc/src/msc/db.c
@@ -0,0 +1,1303 @@
+/* Simple HLR/VLR database backend using dbi */
+/* (C) 2008 by Jan Luebbe <jluebbe@debian.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <inttypes.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <dbi/dbi.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/db.h>
+#include <openbsc/debug.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/statistics.h>
+#include <osmocore/rate_ctr.h>
+
+static char *db_basename = NULL;
+static char *db_dirname = NULL;
+static dbi_conn conn;
+
+static char *create_stmts[] = {
+	"CREATE TABLE IF NOT EXISTS Meta ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"key TEXT UNIQUE NOT NULL, "
+		"value TEXT NOT NULL"
+		")",
+	"INSERT OR IGNORE INTO Meta "
+		"(key, value) "
+		"VALUES "
+		"('revision', '2')",
+	"CREATE TABLE IF NOT EXISTS Subscriber ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"updated TIMESTAMP NOT NULL, "
+		"imsi NUMERIC UNIQUE NOT NULL, "
+		"name TEXT, "
+		"extension TEXT UNIQUE, "
+		"authorized INTEGER NOT NULL DEFAULT 0, "
+		"tmsi TEXT UNIQUE, "
+		"lac INTEGER NOT NULL DEFAULT 0"
+		")",
+	"CREATE TABLE IF NOT EXISTS AuthToken ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"subscriber_id INTEGER UNIQUE NOT NULL, "
+		"created TIMESTAMP NOT NULL, "
+		"token TEXT UNIQUE NOT NULL"
+		")",
+	"CREATE TABLE IF NOT EXISTS Equipment ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"updated TIMESTAMP NOT NULL, "
+		"name TEXT, "
+		"classmark1 NUMERIC, "
+		"classmark2 BLOB, "
+		"classmark3 BLOB, "
+		"imei NUMERIC UNIQUE NOT NULL"
+		")",
+	"CREATE TABLE IF NOT EXISTS EquipmentWatch ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"updated TIMESTAMP NOT NULL, "
+		"subscriber_id NUMERIC NOT NULL, "
+		"equipment_id NUMERIC NOT NULL, "
+		"UNIQUE (subscriber_id, equipment_id) "
+		")",
+	"CREATE TABLE IF NOT EXISTS SMS ("
+		/* metadata, not part of sms */
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"sent TIMESTAMP, "
+		"sender_id INTEGER NOT NULL, "
+		"receiver_id INTEGER NOT NULL, "
+		"deliver_attempts INTEGER NOT NULL DEFAULT 0, "
+		/* data directly copied/derived from SMS */
+		"valid_until TIMESTAMP, "
+		"reply_path_req INTEGER NOT NULL, "
+		"status_rep_req INTEGER NOT NULL, "
+		"protocol_id INTEGER NOT NULL, "
+		"data_coding_scheme INTEGER NOT NULL, "
+		"ud_hdr_ind INTEGER NOT NULL, "
+		"dest_addr TEXT, "
+		"user_data BLOB, "	/* TP-UD */
+		/* additional data, interpreted from SMS */
+		"header BLOB, "		/* UD Header */
+		"text TEXT "		/* decoded UD after UDH */
+		")",
+	"CREATE TABLE IF NOT EXISTS VLR ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"updated TIMESTAMP NOT NULL, "
+		"subscriber_id NUMERIC UNIQUE NOT NULL, "
+		"last_bts NUMERIC NOT NULL "
+		")",
+	"CREATE TABLE IF NOT EXISTS ApduBlobs ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"created TIMESTAMP NOT NULL, "
+		"apdu_id_flags INTEGER NOT NULL, "
+		"subscriber_id INTEGER NOT NULL, "
+		"apdu BLOB "
+		")",
+	"CREATE TABLE IF NOT EXISTS Counters ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"timestamp TIMESTAMP NOT NULL, "
+		"value INTEGER NOT NULL, "
+		"name TEXT NOT NULL "
+		")",
+	"CREATE TABLE IF NOT EXISTS RateCounters ("
+		"id INTEGER PRIMARY KEY AUTOINCREMENT, "
+		"timestamp TIMESTAMP NOT NULL, "
+		"value INTEGER NOT NULL, "
+		"name TEXT NOT NULL, "
+		"idx INTEGER NOT NULL "
+		")",
+	"CREATE TABLE IF NOT EXISTS AuthKeys ("
+		"subscriber_id INTEGER PRIMARY KEY, "
+		"algorithm_id INTEGER NOT NULL, "
+		"a3a8_ki BLOB "
+		")",
+	"CREATE TABLE IF NOT EXISTS AuthLastTuples ("
+		"subscriber_id INTEGER PRIMARY KEY, "
+		"issued TIMESTAMP NOT NULL, "
+		"use_count INTEGER NOT NULL DEFAULT 0, "
+		"key_seq INTEGER NOT NULL, "
+		"rand BLOB NOT NULL, "
+		"sres BLOB NOT NULL, "
+		"kc BLOB NOT NULL "
+		")",
+};
+
+void db_error_func(dbi_conn conn, void *data)
+{
+	const char *msg;
+	dbi_conn_error(conn, &msg);
+	LOGP(DDB, LOGL_ERROR, "DBI: %s\n", msg);
+}
+
+static int check_db_revision(void)
+{
+	dbi_result result;
+	const char *rev;
+
+	result = dbi_conn_query(conn,
+				"SELECT value FROM Meta WHERE key='revision'");
+	if (!result)
+		return -EINVAL;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return -EINVAL;
+	}
+	rev = dbi_result_get_string(result, "value");
+	if (!rev || atoi(rev) != 2) {
+		dbi_result_free(result);
+		return -EINVAL;
+	}
+
+	dbi_result_free(result);
+	return 0;
+}
+
+int db_init(const char *name)
+{
+	dbi_initialize(NULL);
+
+	conn = dbi_conn_new("sqlite3");
+	if (conn == NULL) {
+		LOGP(DDB, LOGL_FATAL, "Failed to create connection.\n");
+		return 1;
+	}
+
+	dbi_conn_error_handler( conn, db_error_func, NULL );
+
+	/* MySQL
+	dbi_conn_set_option(conn, "host", "localhost");
+	dbi_conn_set_option(conn, "username", "your_name");
+	dbi_conn_set_option(conn, "password", "your_password");
+	dbi_conn_set_option(conn, "dbname", "your_dbname");
+	dbi_conn_set_option(conn, "encoding", "UTF-8");
+	*/
+
+	/* SqLite 3 */
+	db_basename = strdup(name);
+	db_dirname = strdup(name);
+	dbi_conn_set_option(conn, "sqlite3_dbdir", dirname(db_dirname));
+	dbi_conn_set_option(conn, "dbname", basename(db_basename));
+
+	if (dbi_conn_connect(conn) < 0)
+		goto out_err;
+
+	return 0;
+
+out_err:
+	free(db_dirname);
+	free(db_basename);
+	db_dirname = db_basename = NULL;
+	return -1;
+}
+
+
+int db_prepare()
+{
+	dbi_result result;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(create_stmts); i++) {
+		result = dbi_conn_query(conn, create_stmts[i]);
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR,
+			     "Failed to create some table.\n");
+			return 1;
+		}
+		dbi_result_free(result);
+	}
+
+	if (check_db_revision() < 0) {
+		LOGP(DDB, LOGL_FATAL, "Database schema revision invalid, "
+			"please update your database schema\n");
+                return -1;
+	}
+
+	return 0;
+}
+
+int db_fini()
+{
+	dbi_conn_close(conn);
+	dbi_shutdown();
+
+	if (db_dirname)
+	    free(db_dirname);
+	if (db_basename)
+	    free(db_basename);
+	return 0;
+}
+
+struct gsm_subscriber *db_create_subscriber(struct gsm_network *net, char *imsi)
+{
+	dbi_result result;
+	struct gsm_subscriber *subscr;
+
+	/* Is this subscriber known in the db? */
+	subscr = db_get_subscriber(net, GSM_SUBSCRIBER_IMSI, imsi);
+	if (subscr) {
+		result = dbi_conn_queryf(conn,
+                         "UPDATE Subscriber set updated = datetime('now') "
+                         "WHERE imsi = %s " , imsi);
+		if (!result)
+			LOGP(DDB, LOGL_ERROR, "failed to update timestamp\n");
+		else
+			dbi_result_free(result);
+		return subscr;
+	}
+
+	subscr = subscr_alloc();
+	subscr->flags |= GSM_SUBSCRIBER_FIRST_CONTACT;
+	if (!subscr)
+		return NULL;
+	result = dbi_conn_queryf(conn,
+		"INSERT INTO Subscriber "
+		"(imsi, created, updated) "
+		"VALUES "
+		"(%s, datetime('now'), datetime('now')) ",
+		imsi
+	);
+	if (!result)
+		LOGP(DDB, LOGL_ERROR, "Failed to create Subscriber by IMSI.\n");
+	subscr->net = net;
+	subscr->id = dbi_conn_sequence_last(conn, NULL);
+	strncpy(subscr->imsi, imsi, GSM_IMSI_LENGTH-1);
+	dbi_result_free(result);
+	LOGP(DDB, LOGL_INFO, "New Subscriber: ID %llu, IMSI %s\n", subscr->id, subscr->imsi);
+	db_subscriber_alloc_exten(subscr);
+	return subscr;
+}
+
+static_assert(sizeof(unsigned char) == sizeof(struct gsm48_classmark1), classmark1_size);
+
+static int get_equipment_by_subscr(struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	const char *string;
+	unsigned char cm1;
+	const unsigned char *cm2, *cm3;
+	struct gsm_equipment *equip = &subscr->equipment;
+
+	result = dbi_conn_queryf(conn,
+		"SELECT Equipment.* "
+			"FROM Equipment JOIN EquipmentWatch ON "
+				"EquipmentWatch.equipment_id=Equipment.id "
+			"WHERE EquipmentWatch.subscriber_id = %llu "
+			"ORDER BY EquipmentWatch.updated DESC", subscr->id);
+	if (!result)
+		return -EIO;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return -ENOENT;
+	}
+
+	equip->id = dbi_result_get_ulonglong(result, "id");
+
+	string = dbi_result_get_string(result, "imei");
+	if (string)
+		strncpy(equip->imei, string, sizeof(equip->imei));
+
+	string = dbi_result_get_string(result, "classmark1");
+	if (string) {
+		cm1 = atoi(string) & 0xff;
+		memcpy(&equip->classmark1, &cm1, sizeof(equip->classmark1));
+	}
+
+	equip->classmark2_len = dbi_result_get_field_length(result, "classmark2");
+	cm2 = dbi_result_get_binary(result, "classmark2");
+	if (equip->classmark2_len > sizeof(equip->classmark2))
+		equip->classmark2_len = sizeof(equip->classmark2);
+	memcpy(equip->classmark2, cm2, equip->classmark2_len);
+
+	equip->classmark3_len = dbi_result_get_field_length(result, "classmark3");
+	cm3 = dbi_result_get_binary(result, "classmark3");
+	if (equip->classmark3_len > sizeof(equip->classmark3))
+		equip->classmark3_len = sizeof(equip->classmark3);
+	memcpy(equip->classmark3, cm3, equip->classmark3_len);
+
+	dbi_result_free(result);
+
+	return 0;
+}
+
+int db_get_authinfo_for_subscr(struct gsm_auth_info *ainfo,
+                               struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	const unsigned char *a3a8_ki;
+
+	result = dbi_conn_queryf(conn,
+			"SELECT * FROM AuthKeys WHERE subscriber_id=%llu",
+			 subscr->id);
+	if (!result)
+		return -EIO;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return -ENOENT;
+	}
+
+	ainfo->auth_algo = dbi_result_get_ulonglong(result, "algorithm_id");
+	ainfo->a3a8_ki_len = dbi_result_get_field_length(result, "a3a8_ki");
+	a3a8_ki = dbi_result_get_binary(result, "a3a8_ki");
+	if (ainfo->a3a8_ki_len > sizeof(ainfo->a3a8_ki))
+		ainfo->a3a8_ki_len = sizeof(ainfo->a3a8_ki_len);
+	memcpy(ainfo->a3a8_ki, a3a8_ki, ainfo->a3a8_ki_len);
+
+	dbi_result_free(result);
+
+	return 0;
+}
+
+int db_sync_authinfo_for_subscr(struct gsm_auth_info *ainfo,
+                                struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	struct gsm_auth_info ainfo_old;
+	int rc, upd;
+	unsigned char *ki_str;
+
+	/* Deletion ? */
+	if (ainfo == NULL) {
+		result = dbi_conn_queryf(conn,
+			"DELETE FROM AuthKeys WHERE subscriber_id=%llu",
+			subscr->id);
+
+		if (!result)
+			return -EIO;
+
+		dbi_result_free(result);
+
+		return 0;
+	}
+
+	/* Check if already existing */
+	rc = db_get_authinfo_for_subscr(&ainfo_old, subscr);
+	if (rc && rc != -ENOENT)
+		return rc;
+	upd = rc ? 0 : 1;
+
+	/* Update / Insert */
+	dbi_conn_quote_binary_copy(conn,
+		ainfo->a3a8_ki, ainfo->a3a8_ki_len, &ki_str);
+
+	if (!upd) {
+		result = dbi_conn_queryf(conn,
+				"INSERT INTO AuthKeys "
+				"(subscriber_id, algorithm_id, a3a8_ki) "
+				"VALUES (%llu, %u, %s)",
+				subscr->id, ainfo->auth_algo, ki_str);
+	} else {
+		result = dbi_conn_queryf(conn,
+				"UPDATE AuthKeys "
+				"SET algorithm_id=%u, a3a8_ki=%s "
+				"WHERE subscriber_id=%llu",
+				ainfo->auth_algo, ki_str, subscr->id);
+	}
+
+	free(ki_str);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+
+	return 0;
+}
+
+int db_get_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple,
+                                    struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	int len;
+	const unsigned char *blob;
+
+	result = dbi_conn_queryf(conn,
+			"SELECT * FROM AuthLastTuples WHERE subscriber_id=%llu",
+			subscr->id);
+	if (!result)
+		return -EIO;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return -ENOENT;
+	}
+
+	memset(atuple, 0, sizeof(atuple));
+
+	atuple->use_count = dbi_result_get_ulonglong(result, "use_count");
+	atuple->key_seq = dbi_result_get_ulonglong(result, "key_seq");
+
+	len = dbi_result_get_field_length(result, "rand");
+	if (len != sizeof(atuple->rand))
+		goto err_size;
+
+	blob = dbi_result_get_binary(result, "rand");
+	memcpy(atuple->rand, blob, len);
+
+	len = dbi_result_get_field_length(result, "sres");
+	if (len != sizeof(atuple->sres))
+		goto err_size;
+
+	blob = dbi_result_get_binary(result, "sres");
+	memcpy(atuple->sres, blob, len);
+
+	len = dbi_result_get_field_length(result, "kc");
+	if (len != sizeof(atuple->kc))
+		goto err_size;
+
+	blob = dbi_result_get_binary(result, "kc");
+	memcpy(atuple->kc, blob, len);
+
+	dbi_result_free(result);
+
+	return 0;
+
+err_size:
+	dbi_result_free(result);
+	return -EIO;
+}
+
+int db_sync_lastauthtuple_for_subscr(struct gsm_auth_tuple *atuple,
+                                     struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	int rc, upd;
+	struct gsm_auth_tuple atuple_old;
+	unsigned char *rand_str, *sres_str, *kc_str;
+
+	/* Deletion ? */
+	if (atuple == NULL) {
+		result = dbi_conn_queryf(conn,
+			"DELETE FROM AuthLastTuples WHERE subscriber_id=%llu",
+			subscr->id);
+
+		if (!result)
+			return -EIO;
+
+		dbi_result_free(result);
+
+		return 0;
+	}
+
+	/* Check if already existing */
+	rc = db_get_lastauthtuple_for_subscr(&atuple_old, subscr);
+	if (rc && rc != -ENOENT)
+		return rc;
+	upd = rc ? 0 : 1;
+
+	/* Update / Insert */
+	dbi_conn_quote_binary_copy(conn,
+		atuple->rand, sizeof(atuple->rand), &rand_str);
+	dbi_conn_quote_binary_copy(conn,
+		atuple->sres, sizeof(atuple->sres), &sres_str);
+	dbi_conn_quote_binary_copy(conn,
+		atuple->kc, sizeof(atuple->kc), &kc_str);
+
+	if (!upd) {
+		result = dbi_conn_queryf(conn,
+				"INSERT INTO AuthLastTuples "
+				"(subscriber_id, issued, use_count, "
+				 "key_seq, rand, sres, kc) "
+				"VALUES (%llu, datetime('now'), %u, "
+				 "%u, %s, %s, %s ) ",
+				subscr->id, atuple->use_count, atuple->key_seq,
+				rand_str, sres_str, kc_str);
+	} else {
+		char *issued = atuple->key_seq == atuple_old.key_seq ?
+					"issued" : "datetime('now')";
+		result = dbi_conn_queryf(conn,
+				"UPDATE AuthLastTuples "
+				"SET issued=%s, use_count=%u, "
+				 "key_seq=%u, rand=%s, sres=%s, kc=%s "
+				"WHERE subscriber_id = %llu",
+				issued, atuple->use_count, atuple->key_seq,
+				rand_str, sres_str, kc_str, subscr->id);
+	}
+
+	free(rand_str);
+	free(sres_str);
+	free(kc_str);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+
+	return 0;
+}
+
+static void db_set_from_query(struct gsm_subscriber *subscr, dbi_conn result)
+{
+	const char *string;
+	string = dbi_result_get_string(result, "imsi");
+	if (string)
+		strncpy(subscr->imsi, string, GSM_IMSI_LENGTH);
+
+	string = dbi_result_get_string(result, "tmsi");
+	if (string)
+		subscr->tmsi = tmsi_from_string(string);
+
+	string = dbi_result_get_string(result, "name");
+	if (string)
+		strncpy(subscr->name, string, GSM_NAME_LENGTH);
+
+	string = dbi_result_get_string(result, "extension");
+	if (string)
+		strncpy(subscr->extension, string, GSM_EXTENSION_LENGTH);
+
+	subscr->lac = dbi_result_get_uint(result, "lac");
+	subscr->authorized = dbi_result_get_uint(result, "authorized");
+}
+
+#define BASE_QUERY "SELECT * FROM Subscriber "
+struct gsm_subscriber *db_get_subscriber(struct gsm_network *net,
+					 enum gsm_subscriber_field field,
+					 const char *id)
+{
+	dbi_result result;
+	char *quoted;
+	struct gsm_subscriber *subscr;
+
+	switch (field) {
+	case GSM_SUBSCRIBER_IMSI:
+		dbi_conn_quote_string_copy(conn, id, &quoted);
+		result = dbi_conn_queryf(conn,
+			BASE_QUERY
+			"WHERE imsi = %s ",
+			quoted
+		);
+		free(quoted);
+		break;
+	case GSM_SUBSCRIBER_TMSI:
+		dbi_conn_quote_string_copy(conn, id, &quoted);
+		result = dbi_conn_queryf(conn,
+			BASE_QUERY
+			"WHERE tmsi = %s ",
+			quoted
+		);
+		free(quoted);
+		break;
+	case GSM_SUBSCRIBER_EXTENSION:
+		dbi_conn_quote_string_copy(conn, id, &quoted);
+		result = dbi_conn_queryf(conn,
+			BASE_QUERY
+			"WHERE extension = %s ",
+			quoted
+		);
+		free(quoted);
+		break;
+	case GSM_SUBSCRIBER_ID:
+		dbi_conn_quote_string_copy(conn, id, &quoted);
+		result = dbi_conn_queryf(conn,
+			BASE_QUERY
+			"WHERE id = %s ", quoted);
+		free(quoted);
+		break;
+	default:
+		LOGP(DDB, LOGL_NOTICE, "Unknown query selector for Subscriber.\n");
+		return NULL;
+	}
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber.\n");
+		return NULL;
+	}
+	if (!dbi_result_next_row(result)) {
+		DEBUGP(DDB, "Failed to find the Subscriber. '%u' '%s'\n",
+			field, id);
+		dbi_result_free(result);
+		return NULL;
+	}
+
+	subscr = subscr_alloc();
+	subscr->net = net;
+	subscr->id = dbi_result_get_ulonglong(result, "id");
+
+	db_set_from_query(subscr, result);
+	DEBUGP(DDB, "Found Subscriber: ID %llu, IMSI %s, NAME '%s', TMSI %u, EXTEN '%s', LAC %hu, AUTH %u\n",
+		subscr->id, subscr->imsi, subscr->name, subscr->tmsi, subscr->extension,
+		subscr->lac, subscr->authorized);
+	dbi_result_free(result);
+
+	get_equipment_by_subscr(subscr);
+
+	return subscr;
+}
+
+int db_subscriber_update(struct gsm_subscriber *subscr)
+{
+	char buf[32];
+	dbi_result result;
+
+	/* Copy the id to a string as queryf with %llu is failing */
+	sprintf(buf, "%llu", subscr->id);
+	result = dbi_conn_queryf(conn,
+			BASE_QUERY
+			"WHERE id = %s", buf);
+
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber: %llu\n", subscr->id);
+		return -EIO;
+	}
+	if (!dbi_result_next_row(result)) {
+		DEBUGP(DDB, "Failed to find the Subscriber. %llu\n",
+			subscr->id);
+		dbi_result_free(result);
+		return -EIO;
+	}
+
+	db_set_from_query(subscr, result);
+	dbi_result_free(result);
+	get_equipment_by_subscr(subscr);
+
+	return 0;
+}
+
+int db_sync_subscriber(struct gsm_subscriber *subscriber)
+{
+	dbi_result result;
+	char tmsi[14];
+	char *q_tmsi, *q_name, *q_extension;
+
+	dbi_conn_quote_string_copy(conn, 
+				   subscriber->name, &q_name);
+	dbi_conn_quote_string_copy(conn, 
+				   subscriber->extension, &q_extension);
+	
+	if (subscriber->tmsi != GSM_RESERVED_TMSI) {
+		sprintf(tmsi, "%u", subscriber->tmsi);
+		dbi_conn_quote_string_copy(conn,
+				   tmsi,
+				   &q_tmsi);
+	} else
+		q_tmsi = strdup("NULL");
+
+	result = dbi_conn_queryf(conn,
+		"UPDATE Subscriber "
+		"SET updated = datetime('now'), "
+		"name = %s, "
+		"extension = %s, "
+		"authorized = %i, "
+		"tmsi = %s, "
+		"lac = %i "
+		"WHERE imsi = %s ",
+		q_name,
+		q_extension,
+		subscriber->authorized,
+		q_tmsi,
+		subscriber->lac,
+		subscriber->imsi);
+
+	free(q_tmsi);
+	free(q_name);
+	free(q_extension);
+
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to update Subscriber (by IMSI).\n");
+		return 1;
+	}
+
+	dbi_result_free(result);
+
+	return 0;
+}
+
+int db_sync_equipment(struct gsm_equipment *equip)
+{
+	dbi_result result;
+	unsigned char *cm2, *cm3;
+	char *q_imei;
+	u_int8_t classmark1;
+
+	memcpy(&classmark1, &equip->classmark1, sizeof(classmark1));
+	DEBUGP(DDB, "Sync Equipment IMEI=%s, classmark1=%02x",
+		equip->imei, classmark1);
+	if (equip->classmark2_len)
+		DEBUGPC(DDB, ", classmark2=%s",
+			hexdump(equip->classmark2, equip->classmark2_len));
+	if (equip->classmark3_len)
+		DEBUGPC(DDB, ", classmark3=%s",
+			hexdump(equip->classmark3, equip->classmark3_len));
+	DEBUGPC(DDB, "\n");
+
+	dbi_conn_quote_binary_copy(conn, equip->classmark2,
+				   equip->classmark2_len, &cm2);
+	dbi_conn_quote_binary_copy(conn, equip->classmark3,
+				   equip->classmark3_len, &cm3);
+	dbi_conn_quote_string_copy(conn, equip->imei, &q_imei);
+
+	result = dbi_conn_queryf(conn,
+		"UPDATE Equipment SET "
+			"updated = datetime('now'), "
+			"classmark1 = %u, "
+			"classmark2 = %s, "
+			"classmark3 = %s "
+		"WHERE imei = %s ",
+		classmark1, cm2, cm3, q_imei);
+
+	free(cm2);
+	free(cm3);
+	free(q_imei);
+
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to update Equipment\n");
+		return -EIO;
+	}
+
+	dbi_result_free(result);
+	return 0;
+}
+
+int db_subscriber_alloc_tmsi(struct gsm_subscriber *subscriber)
+{
+	dbi_result result = NULL;
+	char tmsi[14];
+	char *tmsi_quoted;
+
+	for (;;) {
+		subscriber->tmsi = rand();
+		if (subscriber->tmsi == GSM_RESERVED_TMSI)
+			continue;
+
+		sprintf(tmsi, "%u", subscriber->tmsi);
+		dbi_conn_quote_string_copy(conn, tmsi, &tmsi_quoted);
+		result = dbi_conn_queryf(conn,
+			"SELECT * FROM Subscriber "
+			"WHERE tmsi = %s ",
+			tmsi_quoted);
+
+		free(tmsi_quoted);
+
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber "
+				"while allocating new TMSI.\n");
+			return 1;
+		}
+		if (dbi_result_get_numrows(result)) {
+			dbi_result_free(result);
+			continue;
+		}
+		if (!dbi_result_next_row(result)) {
+			dbi_result_free(result);
+			DEBUGP(DDB, "Allocated TMSI %u for IMSI %s.\n",
+				subscriber->tmsi, subscriber->imsi);
+			return db_sync_subscriber(subscriber);
+		}
+		dbi_result_free(result);
+	}
+	return 0;
+}
+
+int db_subscriber_alloc_exten(struct gsm_subscriber *subscriber)
+{
+	dbi_result result = NULL;
+	u_int32_t try;
+
+	for (;;) {
+		try = (rand()%(GSM_MAX_EXTEN-GSM_MIN_EXTEN+1)+GSM_MIN_EXTEN);
+		result = dbi_conn_queryf(conn,
+			"SELECT * FROM Subscriber "
+			"WHERE extension = %i",
+			try
+		);
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR, "Failed to query Subscriber "
+				"while allocating new extension.\n");
+			return 1;
+		}
+		if (dbi_result_get_numrows(result)){
+			dbi_result_free(result);
+			continue;
+		}
+		if (!dbi_result_next_row(result)) {
+			dbi_result_free(result);
+			break;
+		}
+		dbi_result_free(result);
+	}
+	sprintf(subscriber->extension, "%i", try);
+	DEBUGP(DDB, "Allocated extension %i for IMSI %s.\n", try, subscriber->imsi);
+	return db_sync_subscriber(subscriber);
+}
+/*
+ * try to allocate a new unique token for this subscriber and return it
+ * via a parameter. if the subscriber already has a token, return
+ * an error.
+ */
+
+int db_subscriber_alloc_token(struct gsm_subscriber *subscriber, u_int32_t *token)
+{
+	dbi_result result;
+	u_int32_t try;
+
+	for (;;) {
+		try = rand();
+		if (!try) /* 0 is an invalid token */
+			continue;
+		result = dbi_conn_queryf(conn,
+			"SELECT * FROM AuthToken "
+			"WHERE subscriber_id = %llu OR token = \"%08X\" ",
+			subscriber->id, try);
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR, "Failed to query AuthToken "
+				"while allocating new token.\n");
+			return 1;
+		}
+		if (dbi_result_get_numrows(result)) {
+			dbi_result_free(result);
+			continue;
+		}
+		if (!dbi_result_next_row(result)) {
+			dbi_result_free(result);
+			break;
+		}
+		dbi_result_free(result);
+	}
+	result = dbi_conn_queryf(conn,
+		"INSERT INTO AuthToken "
+		"(subscriber_id, created, token) "
+		"VALUES "
+		"(%llu, datetime('now'), \"%08X\") ",
+		subscriber->id, try);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to create token %08X for "
+			"IMSI %s.\n", try, subscriber->imsi);
+		return 1;
+	}
+	dbi_result_free(result);
+	*token = try;
+	DEBUGP(DDB, "Allocated token %08X for IMSI %s.\n", try, subscriber->imsi);
+
+	return 0;
+}
+
+int db_subscriber_assoc_imei(struct gsm_subscriber *subscriber, char imei[GSM_IMEI_LENGTH])
+{
+	unsigned long long equipment_id, watch_id;
+	dbi_result result;
+
+	strncpy(subscriber->equipment.imei, imei,
+		sizeof(subscriber->equipment.imei)-1),
+
+	result = dbi_conn_queryf(conn,
+		"INSERT OR IGNORE INTO Equipment "
+		"(imei, created, updated) "
+		"VALUES "
+		"(%s, datetime('now'), datetime('now')) ",
+		imei);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to create Equipment by IMEI.\n");
+		return 1;
+	}
+
+	equipment_id = 0;
+	if (dbi_result_get_numrows_affected(result)) {
+		equipment_id = dbi_conn_sequence_last(conn, NULL);
+	}
+	dbi_result_free(result);
+
+	if (equipment_id)
+		DEBUGP(DDB, "New Equipment: ID %llu, IMEI %s\n", equipment_id, imei);
+	else {
+		result = dbi_conn_queryf(conn,
+			"SELECT id FROM Equipment "
+			"WHERE imei = %s ",
+			imei
+		);
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR, "Failed to query Equipment by IMEI.\n");
+			return 1;
+		}
+		if (!dbi_result_next_row(result)) {
+			LOGP(DDB, LOGL_ERROR, "Failed to find the Equipment.\n");
+			dbi_result_free(result);
+			return 1;
+		}
+		equipment_id = dbi_result_get_ulonglong(result, "id");
+		dbi_result_free(result);
+	}
+
+	result = dbi_conn_queryf(conn,
+		"INSERT OR IGNORE INTO EquipmentWatch "
+		"(subscriber_id, equipment_id, created, updated) "
+		"VALUES "
+		"(%llu, %llu, datetime('now'), datetime('now')) ",
+		subscriber->id, equipment_id);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to create EquipmentWatch.\n");
+		return 1;
+	}
+
+	watch_id = 0;
+	if (dbi_result_get_numrows_affected(result))
+		watch_id = dbi_conn_sequence_last(conn, NULL);
+
+	dbi_result_free(result);
+	if (watch_id)
+		DEBUGP(DDB, "New EquipmentWatch: ID %llu, IMSI %s, IMEI %s\n",
+			equipment_id, subscriber->imsi, imei);
+	else {
+		result = dbi_conn_queryf(conn,
+			"UPDATE EquipmentWatch "
+			"SET updated = datetime('now') "
+			"WHERE subscriber_id = %llu AND equipment_id = %llu ",
+			subscriber->id, equipment_id);
+		if (!result) {
+			LOGP(DDB, LOGL_ERROR, "Failed to update EquipmentWatch.\n");
+			return 1;
+		}
+		dbi_result_free(result);
+		DEBUGP(DDB, "Updated EquipmentWatch: ID %llu, IMSI %s, IMEI %s\n",
+			equipment_id, subscriber->imsi, imei);
+	}
+
+	return 0;
+}
+
+/* store an [unsent] SMS to the database */
+int db_sms_store(struct gsm_sms *sms)
+{
+	dbi_result result;
+	char *q_text, *q_daddr;
+	unsigned char *q_udata;
+	char *validity_timestamp = "2222-2-2";
+
+	/* FIXME: generate validity timestamp based on validity_minutes */
+
+	dbi_conn_quote_string_copy(conn, (char *)sms->text, &q_text);
+	dbi_conn_quote_string_copy(conn, (char *)sms->dest_addr, &q_daddr);
+	dbi_conn_quote_binary_copy(conn, sms->user_data, sms->user_data_len,
+				   &q_udata);
+	/* FIXME: correct validity period */
+	result = dbi_conn_queryf(conn,
+		"INSERT INTO SMS "
+		"(created, sender_id, receiver_id, valid_until, "
+		 "reply_path_req, status_rep_req, protocol_id, "
+		 "data_coding_scheme, ud_hdr_ind, dest_addr, "
+		 "user_data, text) VALUES "
+		"(datetime('now'), %llu, %llu, %u, "
+		 "%u, %u, %u, %u, %u, %s, %s, %s)",
+		sms->sender->id,
+		sms->receiver ? sms->receiver->id : 0, validity_timestamp,
+		sms->reply_path_req, sms->status_rep_req, sms->protocol_id,
+		sms->data_coding_scheme, sms->ud_hdr_ind,
+		q_daddr, q_udata, q_text);
+	free(q_text);
+	free(q_daddr);
+	free(q_udata);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+	return 0;
+}
+
+static struct gsm_sms *sms_from_result(struct gsm_network *net, dbi_result result)
+{
+	struct gsm_sms *sms = sms_alloc();
+	long long unsigned int sender_id, receiver_id;
+	const char *text, *daddr;
+	const unsigned char *user_data;
+
+	if (!sms)
+		return NULL;
+
+	sms->id = dbi_result_get_ulonglong(result, "id");
+
+	sender_id = dbi_result_get_ulonglong(result, "sender_id");
+	sms->sender = subscr_get_by_id(net, sender_id);
+
+	receiver_id = dbi_result_get_ulonglong(result, "receiver_id");
+	sms->receiver = subscr_get_by_id(net, receiver_id);
+
+	/* FIXME: validity */
+	/* FIXME: those should all be get_uchar, but sqlite3 is braindead */
+	sms->reply_path_req = dbi_result_get_uint(result, "reply_path_req");
+	sms->status_rep_req = dbi_result_get_uint(result, "status_rep_req");
+	sms->ud_hdr_ind = dbi_result_get_uint(result, "ud_hdr_ind");
+	sms->protocol_id = dbi_result_get_uint(result, "protocol_id");
+	sms->data_coding_scheme = dbi_result_get_uint(result,
+						  "data_coding_scheme");
+	/* sms->msg_ref is temporary and not stored in DB */
+
+	daddr = dbi_result_get_string(result, "dest_addr");
+	if (daddr) {
+		strncpy(sms->dest_addr, daddr, sizeof(sms->dest_addr));
+		sms->dest_addr[sizeof(sms->dest_addr)-1] = '\0';
+	}
+
+	sms->user_data_len = dbi_result_get_field_length(result, "user_data");
+	user_data = dbi_result_get_binary(result, "user_data");
+	if (sms->user_data_len > sizeof(sms->user_data))
+		sms->user_data_len = (u_int8_t) sizeof(sms->user_data);
+	memcpy(sms->user_data, user_data, sms->user_data_len);
+
+	text = dbi_result_get_string(result, "text");
+	if (text) {
+		strncpy(sms->text, text, sizeof(sms->text));
+		sms->text[sizeof(sms->text)-1] = '\0';
+	}
+	return sms;
+}
+
+struct gsm_sms *db_sms_get(struct gsm_network *net, unsigned long long id)
+{
+	dbi_result result;
+	struct gsm_sms *sms;
+
+	result = dbi_conn_queryf(conn,
+		"SELECT * FROM SMS WHERE SMS.id = %llu", id);
+	if (!result)
+		return NULL;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return NULL;
+	}
+
+	sms = sms_from_result(net, result);
+
+	dbi_result_free(result);
+
+	return sms;
+}
+
+/* retrieve the next unsent SMS with ID >= min_id */
+struct gsm_sms *db_sms_get_unsent(struct gsm_network *net, unsigned long long min_id)
+{
+	dbi_result result;
+	struct gsm_sms *sms;
+
+	result = dbi_conn_queryf(conn,
+		"SELECT SMS.* "
+			"FROM SMS JOIN Subscriber ON "
+				"SMS.receiver_id = Subscriber.id "
+			"WHERE SMS.id >= %llu AND SMS.sent IS NULL "
+				"AND Subscriber.lac > 0 "
+			"ORDER BY SMS.id LIMIT 1",
+		min_id);
+	if (!result)
+		return NULL;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return NULL;
+	}
+
+	sms = sms_from_result(net, result);
+
+	dbi_result_free(result);
+
+	return sms;
+}
+
+struct gsm_sms *db_sms_get_unsent_by_subscr(struct gsm_network *net,
+					    unsigned long long min_subscr_id,
+					    unsigned int failed)
+{
+	dbi_result result;
+	struct gsm_sms *sms;
+
+	result = dbi_conn_queryf(conn,
+		"SELECT SMS.* "
+			"FROM SMS JOIN Subscriber ON "
+				"SMS.receiver_id = Subscriber.id "
+			"WHERE SMS.receiver_id >= %llu AND SMS.sent IS NULL "
+				"AND Subscriber.lac > 0 AND SMS.deliver_attempts < %u "
+			"ORDER BY SMS.receiver_id, SMS.id LIMIT 1",
+		min_subscr_id, failed);
+	if (!result)
+		return NULL;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return NULL;
+	}
+
+	sms = sms_from_result(net, result);
+
+	dbi_result_free(result);
+
+	return sms;
+}
+
+/* retrieve the next unsent SMS for a given subscriber */
+struct gsm_sms *db_sms_get_unsent_for_subscr(struct gsm_subscriber *subscr)
+{
+	dbi_result result;
+	struct gsm_sms *sms;
+
+	result = dbi_conn_queryf(conn,
+		"SELECT SMS.* "
+			"FROM SMS JOIN Subscriber ON "
+				"SMS.receiver_id = Subscriber.id "
+			"WHERE SMS.receiver_id = %llu AND SMS.sent IS NULL "
+				"AND Subscriber.lac > 0 "
+			"ORDER BY SMS.id LIMIT 1",
+		subscr->id);
+	if (!result)
+		return NULL;
+
+	if (!dbi_result_next_row(result)) {
+		dbi_result_free(result);
+		return NULL;
+	}
+
+	sms = sms_from_result(subscr->net, result);
+
+	dbi_result_free(result);
+
+	return sms;
+}
+
+/* mark a given SMS as read */
+int db_sms_mark_sent(struct gsm_sms *sms)
+{
+	dbi_result result;
+
+	result = dbi_conn_queryf(conn,
+		"UPDATE SMS "
+		"SET sent = datetime('now') "
+		"WHERE id = %llu", sms->id);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to mark SMS %llu as sent.\n", sms->id);
+		return 1;
+	}
+
+	dbi_result_free(result);
+	return 0;
+}
+
+/* increase the number of attempted deliveries */
+int db_sms_inc_deliver_attempts(struct gsm_sms *sms)
+{
+	dbi_result result;
+
+	result = dbi_conn_queryf(conn,
+		"UPDATE SMS "
+		"SET deliver_attempts = deliver_attempts + 1 "
+		"WHERE id = %llu", sms->id);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR, "Failed to inc deliver attempts for "
+			"SMS %llu.\n", sms->id);
+		return 1;
+	}
+
+	dbi_result_free(result);
+	return 0;
+}
+
+int db_apdu_blob_store(struct gsm_subscriber *subscr,
+			u_int8_t apdu_id_flags, u_int8_t len,
+			u_int8_t *apdu)
+{
+	dbi_result result;
+	unsigned char *q_apdu;
+
+	dbi_conn_quote_binary_copy(conn, apdu, len, &q_apdu);
+
+	result = dbi_conn_queryf(conn,
+		"INSERT INTO ApduBlobs "
+		"(created,subscriber_id,apdu_id_flags,apdu) VALUES "
+		"(datetime('now'),%llu,%u,%s)",
+		subscr->id, apdu_id_flags, q_apdu);
+
+	free(q_apdu);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+	return 0;
+}
+
+int db_store_counter(struct counter *ctr)
+{
+	dbi_result result;
+	char *q_name;
+
+	dbi_conn_quote_string_copy(conn, ctr->name, &q_name);
+
+	result = dbi_conn_queryf(conn,
+		"INSERT INTO Counters "
+		"(timestamp,name,value) VALUES "
+		"(datetime('now'),%s,%lu)", q_name, ctr->value);
+
+	free(q_name);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+	return 0;
+}
+
+static int db_store_rate_ctr(struct rate_ctr_group *ctrg, unsigned int num,
+			     char *q_prefix)
+{
+	dbi_result result;
+	char *q_name;
+
+	dbi_conn_quote_string_copy(conn, ctrg->desc->ctr_desc[num].name,
+				   &q_name);
+
+	result = dbi_conn_queryf(conn,
+		"Insert INTO RateCounters "
+		"(timestamp,name,idx,value) VALUES "
+		"(datetime('now'),%s.%s,%u,%"PRIu64")",
+		q_prefix, q_name, ctrg->idx, ctrg->ctr[num].current);
+
+	free(q_name);
+
+	if (!result)
+		return -EIO;
+
+	dbi_result_free(result);
+	return 0;
+}
+
+int db_store_rate_ctr_group(struct rate_ctr_group *ctrg)
+{
+	unsigned int i;
+	char *q_prefix;
+
+	dbi_conn_quote_string_copy(conn, ctrg->desc->group_name_prefix, &q_prefix);
+
+	for (i = 0; i < ctrg->desc->num_ctr; i++)
+		db_store_rate_ctr(ctrg, i, q_prefix);
+
+	free(q_prefix);
+
+	return 0;
+}
diff --git a/openbsc/src/msc/gsm_04_08.c b/openbsc/src/msc/gsm_04_08.c
new file mode 100644
index 0000000..2b61aa9
--- /dev/null
+++ b/openbsc/src/msc/gsm_04_08.c
@@ -0,0 +1,3345 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008-2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <netinet/in.h>
+
+#include <openbsc/auth.h>
+#include <openbsc/db.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+#include <openbsc/trau_frame.h>
+#include <openbsc/trau_mux.h>
+#include <openbsc/rtp_proxy.h>
+#include <openbsc/transaction.h>
+#include <openbsc/ussd.h>
+#include <openbsc/silent_call.h>
+#include <openbsc/bsc_api.h>
+#include <openbsc/osmo_msc.h>
+#include <osmocore/bitvec.h>
+
+#include <osmocore/gsm48.h>
+#include <osmocore/gsm0480.h>
+#include <osmocore/gsm_utils.h>
+#include <osmocore/msgb.h>
+#include <osmocore/talloc.h>
+#include <osmocore/tlv.h>
+
+void *tall_locop_ctx;
+void *tall_authciphop_ctx;
+
+int gsm0408_loc_upd_acc(struct gsm_subscriber_connection *conn, u_int32_t tmsi);
+static int gsm48_tx_simple(struct gsm_subscriber_connection *conn,
+			   u_int8_t pdisc, u_int8_t msg_type);
+static void schedule_reject(struct gsm_subscriber_connection *conn);
+static void release_anchor(struct gsm_subscriber_connection *conn);
+
+struct gsm_lai {
+	u_int16_t mcc;
+	u_int16_t mnc;
+	u_int16_t lac;
+};
+
+static u_int32_t new_callref = 0x80000001;
+
+void cc_tx_to_mncc(struct gsm_network *net, struct msgb *msg)
+{
+	net->mncc_recv(net, msg);
+}
+
+static int gsm48_conn_sendmsg(struct msgb *msg, struct gsm_subscriber_connection *conn,
+			      struct gsm_trans *trans)
+{
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msg->data;
+
+	/* if we get passed a transaction reference, do some common
+	 * work that the caller no longer has to do */
+	if (trans) {
+		gh->proto_discr = trans->protocol | (trans->transaction_id << 4);
+		msg->lchan = trans->conn->lchan;
+	}
+
+
+	if (msg->lchan) {
+		msg->trx = msg->lchan->ts->trx;
+		if ((gh->proto_discr & GSM48_PDISC_MASK) == GSM48_PDISC_CC)
+			DEBUGP(DCC, "(bts %d trx %d ts %d ti %02x) "
+				"Sending '%s' to MS.\n", msg->trx->bts->nr,
+				msg->trx->nr, msg->lchan->ts->nr,
+				gh->proto_discr & 0xf0,
+				gsm48_cc_msg_name(gh->msg_type));
+		else
+			DEBUGP(DCC, "(bts %d trx %d ts %d pd %02x) "
+				"Sending 0x%02x to MS.\n", msg->trx->bts->nr,
+				msg->trx->nr, msg->lchan->ts->nr,
+				gh->proto_discr, gh->msg_type);
+	}
+
+	return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+int gsm48_cc_tx_notify_ss(struct gsm_trans *trans, const char *message)
+{
+	struct gsm48_hdr *gh;
+	struct msgb *ss_notify;
+
+	ss_notify = gsm0480_create_notifySS(message);
+	if (!ss_notify)
+		return -1;
+
+	gsm0480_wrap_invoke(ss_notify, GSM0480_OP_CODE_NOTIFY_SS, 0);
+	uint8_t *data = msgb_push(ss_notify, 1);
+	data[0] = ss_notify->len - 1;
+	gh = (struct gsm48_hdr *) msgb_push(ss_notify, sizeof(*gh));
+	gh->msg_type = GSM48_MT_CC_FACILITY;
+	return gsm48_conn_sendmsg(ss_notify, trans->conn, trans);
+}
+
+static void release_security_operation(struct gsm_subscriber_connection *conn)
+{
+	if (!conn->sec_operation)
+		return;
+
+	talloc_free(conn->sec_operation);
+	conn->sec_operation = NULL;
+	msc_release_connection(conn);
+}
+
+static void allocate_security_operation(struct gsm_subscriber_connection *conn)
+{
+	conn->sec_operation = talloc_zero(tall_authciphop_ctx,
+	                                  struct gsm_security_operation);
+}
+
+int gsm48_secure_channel(struct gsm_subscriber_connection *conn, int key_seq,
+                         gsm_cbfn *cb, void *cb_data)
+{
+	struct gsm_network *net = conn->bts->network;
+	struct gsm_subscriber *subscr = conn->subscr;
+	struct gsm_security_operation *op;
+	struct gsm_auth_tuple atuple;
+	int status = -1, rc;
+
+	/* Check if we _can_ enable encryption. Cases where we can't:
+	 *  - Encryption disabled in config
+	 *  - Channel already secured (nothing to do)
+	 *  - Subscriber equipment doesn't support configured encryption
+	 */
+	if (!net->a5_encryption) {
+		status = GSM_SECURITY_NOAVAIL;
+	} else if (conn->lchan->encr.alg_id > RSL_ENC_ALG_A5(0)) {
+		DEBUGP(DMM, "Requesting to secure an already secure channel");
+		status = GSM_SECURITY_SUCCEEDED;
+	} else if (!ms_cm2_a5n_support(subscr->equipment.classmark2,
+	                               net->a5_encryption)) {
+		DEBUGP(DMM, "Subscriber equipment doesn't support requested encryption");
+		status = GSM_SECURITY_NOAVAIL;
+	}
+
+	/* If not done yet, try to get info for this user */
+	if (status < 0) {
+		rc = auth_get_tuple_for_subscr(&atuple, subscr, key_seq);
+		if (rc <= 0)
+			status = GSM_SECURITY_NOAVAIL;
+	}
+
+	/* Are we done yet ? */
+	if (status >= 0)
+		return cb ?
+			cb(GSM_HOOK_RR_SECURITY, status, NULL, conn, cb_data) :
+			0;
+
+	/* Start an operation (can't have more than one pending !!!) */
+	if (conn->sec_operation)
+		return -EBUSY;
+
+	allocate_security_operation(conn);
+	op = conn->sec_operation;
+	op->cb = cb;
+	op->cb_data = cb_data;
+	memcpy(&op->atuple, &atuple, sizeof(struct gsm_auth_tuple));
+
+		/* FIXME: Should start a timer for completion ... */
+
+	/* Then do whatever is needed ... */
+	if (rc == AUTH_DO_AUTH_THAN_CIPH) {
+		/* Start authentication */
+		return gsm48_tx_mm_auth_req(conn, op->atuple.rand, op->atuple.key_seq);
+	} else if (rc == AUTH_DO_CIPH) {
+		/* Start ciphering directly */
+		return gsm0808_cipher_mode(conn, net->a5_encryption,
+		                           op->atuple.kc, 8, 0);
+	}
+
+	return -EINVAL; /* not reached */
+}
+
+static int authorize_subscriber(struct gsm_loc_updating_operation *loc,
+				struct gsm_subscriber *subscriber)
+{
+	if (!subscriber)
+		return 0;
+
+	/*
+	 * Do not send accept yet as more information should arrive. Some
+	 * phones will not send us the information and we will have to check
+	 * what we want to do with that.
+	 */
+	if (loc && (loc->waiting_for_imsi || loc->waiting_for_imei))
+		return 0;
+
+	switch (subscriber->net->auth_policy) {
+	case GSM_AUTH_POLICY_CLOSED:
+		return subscriber->authorized;
+	case GSM_AUTH_POLICY_TOKEN:
+		if (subscriber->authorized)
+			return subscriber->authorized;
+		return (subscriber->flags & GSM_SUBSCRIBER_FIRST_CONTACT);
+	case GSM_AUTH_POLICY_ACCEPT_ALL:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+static void release_loc_updating_req(struct gsm_subscriber_connection *conn)
+{
+	if (!conn->loc_operation)
+		return;
+
+	/* No need to keep the connection up */
+	release_anchor(conn);
+
+	bsc_del_timer(&conn->loc_operation->updating_timer);
+	talloc_free(conn->loc_operation);
+	conn->loc_operation = NULL;
+	msc_release_connection(conn);
+}
+
+static void allocate_loc_updating_req(struct gsm_subscriber_connection *conn)
+{
+	if (conn->loc_operation)
+		LOGP(DMM, LOGL_ERROR, "Connection already had operation.\n");
+	release_loc_updating_req(conn);
+
+	conn->loc_operation = talloc_zero(tall_locop_ctx,
+					   struct gsm_loc_updating_operation);
+}
+
+static int _gsm0408_authorize_sec_cb(unsigned int hooknum, unsigned int event,
+                                     struct msgb *msg, void *data, void *param)
+{
+	struct gsm_subscriber_connection *conn = data;
+	int rc = 0;
+
+	switch (event) {
+		case GSM_SECURITY_AUTH_FAILED:
+			release_loc_updating_req(conn);
+			break;
+
+		case GSM_SECURITY_NOAVAIL:
+		case GSM_SECURITY_SUCCEEDED:
+			/* We're all good */
+			db_subscriber_alloc_tmsi(conn->subscr);
+			rc = gsm0408_loc_upd_acc(conn, conn->subscr->tmsi);
+			if (conn->bts->network->send_mm_info) {
+				/* send MM INFO with network name */
+				rc = gsm48_tx_mm_info(conn);
+			}
+
+			/* call subscr_update after putting the loc_upd_acc
+			 * in the transmit queue, since S_SUBSCR_ATTACHED might
+			 * trigger further action like SMS delivery */
+			subscr_update(conn->subscr, conn->bts,
+				      GSM_SUBSCRIBER_UPDATE_ATTACHED);
+
+			/*
+			 * The gsm0408_loc_upd_acc sends a MI with the TMSI. The
+			 * MS needs to respond with a TMSI REALLOCATION COMPLETE
+			 * (even if the TMSI is the same).
+			 */
+			break;
+
+		default:
+			rc = -EINVAL;
+	};
+
+	return rc;
+}
+
+static int gsm0408_authorize(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	if (authorize_subscriber(conn->loc_operation, conn->subscr))
+		return gsm48_secure_channel(conn,
+			conn->loc_operation->key_seq,
+			_gsm0408_authorize_sec_cb, NULL);
+	return 0;
+}
+
+void gsm0408_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause)
+{
+	struct gsm_trans *trans, *temp;
+
+	/* avoid someone issuing a clear */
+	conn->in_release = 1;
+
+	/*
+	 * Cancel any outstanding location updating request
+	 * operation taking place on the subscriber connection.
+	 */
+	release_loc_updating_req(conn);
+
+	/* We might need to cancel the paging response or such. */
+	if (conn->sec_operation && conn->sec_operation->cb) {
+		conn->sec_operation->cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_AUTH_FAILED,
+					NULL, conn, conn->sec_operation->cb_data);
+	}
+
+	release_security_operation(conn);
+	release_anchor(conn);
+
+	/* Free all transactions that are associated with the released lchan */
+	/* FIXME: this is not neccessarily the right thing to do, we should
+	 * only set trans->lchan to NULL and wait for another lchan to be
+	 * established to the same MM entity (phone/subscriber) */
+	llist_for_each_entry_safe(trans, temp, &conn->bts->network->trans_list, entry) {
+		if (trans->conn == conn)
+			trans_free(trans);
+	}
+}
+
+void gsm0408_clear_all_trans(struct gsm_network *net, int protocol)
+{
+	struct gsm_trans *trans, *temp;
+
+	LOGP(DCC, LOGL_NOTICE, "Clearing all currently active transactions!!!\n");
+
+	llist_for_each_entry_safe(trans, temp, &net->trans_list, entry) {
+		if (trans->protocol == protocol) {
+			trans->callref = 0;
+			trans_free(trans);
+		}
+	}
+}
+
+/* Chapter 9.2.14 : Send LOCATION UPDATING REJECT */
+int gsm0408_loc_upd_rej(struct gsm_subscriber_connection *conn, u_int8_t cause)
+{
+	struct gsm_bts *bts = conn->bts;
+	struct msgb *msg;
+
+	counter_inc(bts->network->stats.loc_upd_resp.reject);
+
+	msg = gsm48_create_loc_upd_rej(cause);
+	if (!msg) {
+		LOGP(DMM, LOGL_ERROR, "Failed to create msg for LOCATION UPDATING REJECT.\n");
+		return -1;
+	}
+	
+	msg->lchan = conn->lchan;
+
+	LOGP(DMM, LOGL_INFO, "Subscriber %s: LOCATION UPDATING REJECT "
+	     "LAC=%u BTS=%u\n", conn->subscr ?
+	     			subscr_name(conn->subscr) : "unknown",
+	     bts->location_area_code, bts->nr);
+
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+/* Chapter 9.2.13 : Send LOCATION UPDATE ACCEPT */
+int gsm0408_loc_upd_acc(struct gsm_subscriber_connection *conn, u_int32_t tmsi)
+{
+	struct gsm_bts *bts = conn->bts;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	struct gsm48_loc_area_id *lai;
+	u_int8_t *mid;
+	
+	msg->lchan = conn->lchan;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_LOC_UPD_ACCEPT;
+
+	lai = (struct gsm48_loc_area_id *) msgb_put(msg, sizeof(*lai));
+	gsm48_generate_lai(lai, bts->network->country_code,
+		     bts->network->network_code, bts->location_area_code);
+
+	mid = msgb_put(msg, GSM48_MID_TMSI_LEN);
+	gsm48_generate_mid_from_tmsi(mid, tmsi);
+
+	DEBUGP(DMM, "-> LOCATION UPDATE ACCEPT\n");
+
+	counter_inc(bts->network->stats.loc_upd_resp.accept);
+
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+/* Transmit Chapter 9.2.10 Identity Request */
+static int mm_tx_identity_req(struct gsm_subscriber_connection *conn, u_int8_t id_type)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+
+	msg->lchan = conn->lchan;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 1);
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_ID_REQ;
+	gh->data[0] = id_type;
+
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+
+/* Parse Chapter 9.2.11 Identity Response */
+static int mm_rx_id_resp(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm_lchan *lchan = msg->lchan;
+	struct gsm_bts *bts = lchan->ts->trx->bts;
+	struct gsm_network *net = bts->network;
+	u_int8_t mi_type = gh->data[1] & GSM_MI_TYPE_MASK;
+	char mi_string[GSM48_MI_SIZE];
+
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), &gh->data[1], gh->data[0]);
+	DEBUGP(DMM, "IDENTITY RESPONSE: mi_type=0x%02x MI(%s)\n",
+		mi_type, mi_string);
+
+	dispatch_signal(SS_SUBSCR, S_SUBSCR_IDENTITY, gh->data);
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_IMSI:
+		/* look up subscriber based on IMSI, create if not found */
+		if (!conn->subscr) {
+			conn->subscr = subscr_get_by_imsi(net, mi_string);
+			if (!conn->subscr)
+				conn->subscr = db_create_subscriber(net, mi_string);
+		}
+		if (conn->loc_operation)
+			conn->loc_operation->waiting_for_imsi = 0;
+		break;
+	case GSM_MI_TYPE_IMEI:
+	case GSM_MI_TYPE_IMEISV:
+		/* update subscribe <-> IMEI mapping */
+		if (conn->subscr) {
+			db_subscriber_assoc_imei(conn->subscr, mi_string);
+			db_sync_equipment(&conn->subscr->equipment);
+		}
+		if (conn->loc_operation)
+			conn->loc_operation->waiting_for_imei = 0;
+		break;
+	}
+
+	/* Check if we can let the mobile station enter */
+	return gsm0408_authorize(conn, msg);
+}
+
+
+static void loc_upd_rej_cb(void *data)
+{
+	struct gsm_subscriber_connection *conn = data;
+	struct gsm_lchan *lchan = conn->lchan;
+	struct gsm_bts *bts = lchan->ts->trx->bts;
+
+	gsm0408_loc_upd_rej(conn, bts->network->reject_cause);
+	release_loc_updating_req(conn);
+}
+
+static void schedule_reject(struct gsm_subscriber_connection *conn)
+{
+	conn->loc_operation->updating_timer.cb = loc_upd_rej_cb;
+	conn->loc_operation->updating_timer.data = conn;
+	bsc_schedule_timer(&conn->loc_operation->updating_timer, 5, 0);
+}
+
+static const char *lupd_name(u_int8_t type)
+{
+	switch (type) {
+	case GSM48_LUPD_NORMAL:
+		return "NORMAL";
+	case GSM48_LUPD_PERIODIC:
+		return "PEROIDOC";
+	case GSM48_LUPD_IMSI_ATT:
+		return "IMSI ATTACH";
+	default:
+		return "UNKNOWN";
+	}
+}
+
+/* Chapter 9.2.15: Receive Location Updating Request */
+static int mm_rx_loc_upd_req(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm48_loc_upd_req *lu;
+	struct gsm_subscriber *subscr = NULL;
+	struct gsm_bts *bts = conn->bts;
+	u_int8_t mi_type;
+	char mi_string[GSM48_MI_SIZE];
+	int rc;
+
+ 	lu = (struct gsm48_loc_upd_req *) gh->data;
+
+	mi_type = lu->mi[0] & GSM_MI_TYPE_MASK;
+
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), lu->mi, lu->mi_len);
+
+	DEBUGPC(DMM, "mi_type=0x%02x MI(%s) type=%s ", mi_type, mi_string,
+		lupd_name(lu->type));
+
+	dispatch_signal(SS_SUBSCR, S_SUBSCR_IDENTITY, &lu->mi_len);
+
+	switch (lu->type) {
+	case GSM48_LUPD_NORMAL:
+		counter_inc(bts->network->stats.loc_upd_type.normal);
+		break;
+	case GSM48_LUPD_IMSI_ATT:
+		counter_inc(bts->network->stats.loc_upd_type.attach);
+		break;
+	case GSM48_LUPD_PERIODIC:
+		counter_inc(bts->network->stats.loc_upd_type.periodic);
+		break;
+	}
+
+	/*
+	 * Pseudo Spoof detection: Just drop a second/concurrent
+	 * location updating request.
+	 */
+	if (conn->loc_operation) {
+		DEBUGPC(DMM, "ignoring request due an existing one: %p.\n",
+			conn->loc_operation);
+		gsm0408_loc_upd_rej(conn, GSM48_REJECT_PROTOCOL_ERROR);
+		return 0;
+	}
+
+	allocate_loc_updating_req(conn);
+
+	conn->loc_operation->key_seq = lu->key_seq;
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_IMSI:
+		DEBUGPC(DMM, "\n");
+		/* we always want the IMEI, too */
+		rc = mm_tx_identity_req(conn, GSM_MI_TYPE_IMEI);
+		conn->loc_operation->waiting_for_imei = 1;
+
+		/* look up subscriber based on IMSI, create if not found */
+		subscr = subscr_get_by_imsi(bts->network, mi_string);
+		if (!subscr) {
+			subscr = db_create_subscriber(bts->network, mi_string);
+		}
+		break;
+	case GSM_MI_TYPE_TMSI:
+		DEBUGPC(DMM, "\n");
+		/* look up the subscriber based on TMSI, request IMSI if it fails */
+		subscr = subscr_get_by_tmsi(bts->network,
+					    tmsi_from_string(mi_string));
+		if (!subscr) {
+			/* send IDENTITY REQUEST message to get IMSI */
+			rc = mm_tx_identity_req(conn, GSM_MI_TYPE_IMSI);
+			conn->loc_operation->waiting_for_imsi = 1;
+		}
+		/* we always want the IMEI, too */
+		rc = mm_tx_identity_req(conn, GSM_MI_TYPE_IMEI);
+		conn->loc_operation->waiting_for_imei = 1;
+		break;
+	case GSM_MI_TYPE_IMEI:
+	case GSM_MI_TYPE_IMEISV:
+		/* no sim card... FIXME: what to do ? */
+		DEBUGPC(DMM, "unimplemented mobile identity type\n");
+		break;
+	default:	
+		DEBUGPC(DMM, "unknown mobile identity type\n");
+		break;
+	}
+
+	/* schedule the reject timer */
+	schedule_reject(conn);
+
+	if (!subscr) {
+		DEBUGPC(DRR, "<- Can't find any subscriber for this ID\n");
+		/* FIXME: request id? close channel? */
+		return -EINVAL;
+	}
+
+	conn->subscr = subscr;
+	conn->subscr->equipment.classmark1 = lu->classmark1;
+
+	/* check if we can let the subscriber into our network immediately
+	 * or if we need to wait for identity responses. */
+	return gsm0408_authorize(conn, msg);
+}
+
+#if 0
+static u_int8_t to_bcd8(u_int8_t val)
+{
+       return ((val / 10) << 4) | (val % 10);
+}
+#endif
+
+/* Section 9.2.15a */
+int gsm48_tx_mm_info(struct gsm_subscriber_connection *conn)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	struct gsm_network *net = conn->bts->network;
+	u_int8_t *ptr8;
+	int name_len, name_pad;
+#if 0
+	time_t cur_t;
+	struct tm* cur_time;
+	int tz15min;
+#endif
+
+	msg->lchan = conn->lchan;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_INFO;
+
+	if (net->name_long) {
+#if 0
+		name_len = strlen(net->name_long);
+		/* 10.5.3.5a */
+		ptr8 = msgb_put(msg, 3);
+		ptr8[0] = GSM48_IE_NAME_LONG;
+		ptr8[1] = name_len*2 +1;
+		ptr8[2] = 0x90; /* UCS2, no spare bits, no CI */
+
+		ptr16 = (u_int16_t *) msgb_put(msg, name_len*2);
+		for (i = 0; i < name_len; i++)
+			ptr16[i] = htons(net->name_long[i]);
+
+		/* FIXME: Use Cell Broadcast, not UCS-2, since
+		 * UCS-2 is only supported by later revisions of the spec */
+#endif
+		name_len = (strlen(net->name_long)*7)/8;
+		name_pad = (8 - strlen(net->name_long)*7)%8;
+		if (name_pad > 0)
+			name_len++;
+		/* 10.5.3.5a */
+		ptr8 = msgb_put(msg, 3);
+		ptr8[0] = GSM48_IE_NAME_LONG;
+		ptr8[1] = name_len +1;
+		ptr8[2] = 0x80 | name_pad; /* Cell Broadcast DCS, no CI */
+
+		ptr8 = msgb_put(msg, name_len);
+		gsm_7bit_encode(ptr8, net->name_long);
+
+	}
+
+	if (net->name_short) {
+#if 0
+		name_len = strlen(net->name_short);
+		/* 10.5.3.5a */
+		ptr8 = (u_int8_t *) msgb_put(msg, 3);
+		ptr8[0] = GSM48_IE_NAME_SHORT;
+		ptr8[1] = name_len*2 + 1;
+		ptr8[2] = 0x90; /* UCS2, no spare bits, no CI */
+
+		ptr16 = (u_int16_t *) msgb_put(msg, name_len*2);
+		for (i = 0; i < name_len; i++)
+			ptr16[i] = htons(net->name_short[i]);
+#endif
+		name_len = (strlen(net->name_short)*7)/8;
+		name_pad = (8 - strlen(net->name_short)*7)%8;
+		if (name_pad > 0)
+			name_len++;
+		/* 10.5.3.5a */
+		ptr8 = (u_int8_t *) msgb_put(msg, 3);
+		ptr8[0] = GSM48_IE_NAME_SHORT;
+		ptr8[1] = name_len +1;
+		ptr8[2] = 0x80 | name_pad; /* Cell Broadcast DCS, no CI */
+
+		ptr8 = msgb_put(msg, name_len);
+		gsm_7bit_encode(ptr8, net->name_short);
+
+	}
+
+#if 0
+	/* Section 10.5.3.9 */
+	cur_t = time(NULL);
+	cur_time = gmtime(&cur_t);
+	ptr8 = msgb_put(msg, 8);
+	ptr8[0] = GSM48_IE_NET_TIME_TZ;
+	ptr8[1] = to_bcd8(cur_time->tm_year % 100);
+	ptr8[2] = to_bcd8(cur_time->tm_mon);
+	ptr8[3] = to_bcd8(cur_time->tm_mday);
+	ptr8[4] = to_bcd8(cur_time->tm_hour);
+	ptr8[5] = to_bcd8(cur_time->tm_min);
+	ptr8[6] = to_bcd8(cur_time->tm_sec);
+	/* 02.42: coded as BCD encoded signed value in units of 15 minutes */
+	tz15min = (cur_time->tm_gmtoff)/(60*15);
+	ptr8[7] = to_bcd8(tz15min);
+	if (tz15min < 0)
+		ptr8[7] |= 0x80;
+#endif
+
+	DEBUGP(DMM, "-> MM INFO\n");
+
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+/* Section 9.2.2 */
+int gsm48_tx_mm_auth_req(struct gsm_subscriber_connection *conn, u_int8_t *rand, int key_seq)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	struct gsm48_auth_req *ar = (struct gsm48_auth_req *) msgb_put(msg, sizeof(*ar));
+
+	DEBUGP(DMM, "-> AUTH REQ (rand = %s)\n", hexdump(rand, 16));
+
+	msg->lchan = conn->lchan;
+	gh->proto_discr = GSM48_PDISC_MM;
+	gh->msg_type = GSM48_MT_MM_AUTH_REQ;
+
+	ar->key_seq = key_seq;
+
+	/* 16 bytes RAND parameters */
+	if (rand)
+		memcpy(ar->rand, rand, 16);
+
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+/* Section 9.2.1 */
+int gsm48_tx_mm_auth_rej(struct gsm_subscriber_connection *conn)
+{
+	DEBUGP(DMM, "-> AUTH REJECT\n");
+	return gsm48_tx_simple(conn, GSM48_PDISC_MM, GSM48_MT_MM_AUTH_REJ);
+}
+
+static int gsm48_tx_mm_serv_ack(struct gsm_subscriber_connection *conn)
+{
+	DEBUGP(DMM, "-> CM SERVICE ACK\n");
+	return gsm48_tx_simple(conn, GSM48_PDISC_MM, GSM48_MT_MM_CM_SERV_ACC);
+}
+
+/* 9.2.6 CM service reject */
+static int gsm48_tx_mm_serv_rej(struct gsm_subscriber_connection *conn,
+				enum gsm48_reject_value value)
+{
+	struct msgb *msg;
+
+	msg = gsm48_create_mm_serv_rej(value);
+	if (!msg) {
+		LOGP(DMM, LOGL_ERROR, "Failed to allocate CM Service Reject.\n");
+		return -1;
+	}
+
+	DEBUGP(DMM, "-> CM SERVICE Reject cause: %d\n", value);
+	msg->lchan = conn->lchan;
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+static int _gsm48_rx_mm_serv_req_sec_cb(
+	unsigned int hooknum, unsigned int event,
+	struct msgb *msg, void *data, void *param)
+{
+	struct gsm_subscriber_connection *conn = data;
+	int rc = 0;
+
+	switch (event) {
+		case GSM_SECURITY_AUTH_FAILED:
+			/* Nothing to do */
+			break;
+
+		case GSM_SECURITY_NOAVAIL:
+			rc = gsm48_tx_mm_serv_ack(conn);
+			break;
+
+		case GSM_SECURITY_SUCCEEDED:
+			/* nothing to do. CIPHER MODE COMMAND is
+			 * implicit CM SERV ACK */
+			break;
+
+		default:
+			rc = -EINVAL;
+	};
+
+	return rc;
+}
+
+/*
+ * Handle CM Service Requests
+ * a) Verify that the packet is long enough to contain the information
+ *    we require otherwsie reject with INCORRECT_MESSAGE
+ * b) Try to parse the TMSI. If we do not have one reject
+ * c) Check that we know the subscriber with the TMSI otherwise reject
+ *    with a HLR cause
+ * d) Set the subscriber on the gsm_lchan and accept
+ */
+static int gsm48_rx_mm_serv_req(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	u_int8_t mi_type;
+	char mi_string[GSM48_MI_SIZE];
+
+	struct gsm_bts *bts = conn->bts;
+	struct gsm_subscriber *subscr;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm48_service_request *req =
+			(struct gsm48_service_request *)gh->data;
+	/* unfortunately in Phase1 the classmark2 length is variable */
+	u_int8_t classmark2_len = gh->data[1];
+	u_int8_t *classmark2 = gh->data+2;
+	u_int8_t mi_len = *(classmark2 + classmark2_len);
+	u_int8_t *mi = (classmark2 + classmark2_len + 1);
+
+	DEBUGP(DMM, "<- CM SERVICE REQUEST ");
+	if (msg->data_len < sizeof(struct gsm48_service_request*)) {
+		DEBUGPC(DMM, "wrong sized message\n");
+		return gsm48_tx_mm_serv_rej(conn,
+					    GSM48_REJECT_INCORRECT_MESSAGE);
+	}
+
+	if (msg->data_len < req->mi_len + 6) {
+		DEBUGPC(DMM, "does not fit in packet\n");
+		return gsm48_tx_mm_serv_rej(conn,
+					    GSM48_REJECT_INCORRECT_MESSAGE);
+	}
+
+	mi_type = mi[0] & GSM_MI_TYPE_MASK;
+	if (mi_type != GSM_MI_TYPE_TMSI) {
+		DEBUGPC(DMM, "mi_type is not TMSI: %d\n", mi_type);
+		return gsm48_tx_mm_serv_rej(conn,
+					    GSM48_REJECT_INCORRECT_MESSAGE);
+	}
+
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), mi, mi_len);
+	DEBUGPC(DMM, "serv_type=0x%02x mi_type=0x%02x M(%s)\n",
+		req->cm_service_type, mi_type, mi_string);
+
+	dispatch_signal(SS_SUBSCR, S_SUBSCR_IDENTITY, (classmark2 + classmark2_len));
+
+	if (is_siemens_bts(bts))
+		send_siemens_mrpci(msg->lchan, classmark2-1);
+
+	subscr = subscr_get_by_tmsi(bts->network,
+				    tmsi_from_string(mi_string));
+
+	/* FIXME: if we don't know the TMSI, inquire abit IMSI and allocate new TMSI */
+	if (!subscr)
+		return gsm48_tx_mm_serv_rej(conn,
+					    GSM48_REJECT_IMSI_UNKNOWN_IN_HLR);
+
+	if (!conn->subscr)
+		conn->subscr = subscr;
+	else if (conn->subscr == subscr)
+		subscr_put(subscr); /* lchan already has a ref, don't need another one */
+	else {
+		DEBUGP(DMM, "<- CM Channel already owned by someone else?\n");
+		subscr_put(subscr);
+	}
+
+	subscr->equipment.classmark2_len = classmark2_len;
+	memcpy(subscr->equipment.classmark2, classmark2, classmark2_len);
+	db_sync_equipment(&subscr->equipment);
+
+	return gsm48_secure_channel(conn, req->cipher_key_seq,
+			_gsm48_rx_mm_serv_req_sec_cb, NULL);
+}
+
+static int gsm48_rx_mm_imsi_detach_ind(struct msgb *msg)
+{
+	struct gsm_bts *bts = msg->lchan->ts->trx->bts;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm48_imsi_detach_ind *idi =
+				(struct gsm48_imsi_detach_ind *) gh->data;
+	u_int8_t mi_type = idi->mi[0] & GSM_MI_TYPE_MASK;
+	char mi_string[GSM48_MI_SIZE];
+	struct gsm_subscriber *subscr = NULL;
+
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), idi->mi, idi->mi_len);
+	DEBUGP(DMM, "IMSI DETACH INDICATION: mi_type=0x%02x MI(%s): ",
+		mi_type, mi_string);
+
+	counter_inc(bts->network->stats.loc_upd_type.detach);
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_TMSI:
+		subscr = subscr_get_by_tmsi(bts->network,
+					    tmsi_from_string(mi_string));
+		break;
+	case GSM_MI_TYPE_IMSI:
+		subscr = subscr_get_by_imsi(bts->network, mi_string);
+		break;
+	case GSM_MI_TYPE_IMEI:
+	case GSM_MI_TYPE_IMEISV:
+		/* no sim card... FIXME: what to do ? */
+		DEBUGPC(DMM, "unimplemented mobile identity type\n");
+		break;
+	default:	
+		DEBUGPC(DMM, "unknown mobile identity type\n");
+		break;
+	}
+
+	if (subscr) {
+		subscr_update(subscr, msg->trx->bts,
+				GSM_SUBSCRIBER_UPDATE_DETACHED);
+		DEBUGP(DMM, "Subscriber: %s\n", subscr_name(subscr));
+
+		subscr->equipment.classmark1 = idi->classmark1;
+		db_sync_equipment(&subscr->equipment);
+
+		subscr_put(subscr);
+	} else
+		DEBUGP(DMM, "Unknown Subscriber ?!?\n");
+
+	/* FIXME: iterate over all transactions and release them,
+	 * imagine an IMSI DETACH happening during an active call! */
+
+	/* subscriber is detached: should we release lchan? */
+	return 0;
+}
+
+static int gsm48_rx_mm_status(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+
+	DEBUGP(DMM, "MM STATUS (reject cause 0x%02x)\n", gh->data[0]);
+
+	return 0;
+}
+
+/* Chapter 9.2.3: Authentication Response */
+static int gsm48_rx_mm_auth_resp(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm48_auth_resp *ar = (struct gsm48_auth_resp*) gh->data;
+	struct gsm_network *net = conn->bts->network;
+
+	DEBUGP(DMM, "MM AUTHENTICATION RESPONSE (sres = %s): ",
+		hexdump(ar->sres, 4));
+
+	/* Safety check */
+	if (!conn->sec_operation) {
+		DEBUGP(DMM, "No authentication/cipher operation in progress !!!\n");
+		return -EIO;
+	}
+
+	/* Validate SRES */
+	if (memcmp(conn->sec_operation->atuple.sres, ar->sres,4)) {
+		int rc;
+		gsm_cbfn *cb = conn->sec_operation->cb;
+
+		DEBUGPC(DMM, "Invalid (expected %s)\n",
+			hexdump(conn->sec_operation->atuple.sres, 4));
+
+		if (cb)
+			cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_AUTH_FAILED,
+			   NULL, conn, conn->sec_operation->cb_data);
+
+		rc = gsm48_tx_mm_auth_rej(conn);
+		release_security_operation(conn);
+		return rc;
+	}
+
+	DEBUGPC(DMM, "OK\n");
+
+	/* Start ciphering */
+	return gsm0808_cipher_mode(conn, net->a5_encryption,
+	                           conn->sec_operation->atuple.kc, 8, 0);
+}
+
+/* Receive a GSM 04.08 Mobility Management (MM) message */
+static int gsm0408_rcv_mm(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	int rc = 0;
+
+	switch (gh->msg_type & 0xbf) {
+	case GSM48_MT_MM_LOC_UPD_REQUEST:
+		DEBUGP(DMM, "LOCATION UPDATING REQUEST: ");
+		rc = mm_rx_loc_upd_req(conn, msg);
+		break;
+	case GSM48_MT_MM_ID_RESP:
+		rc = mm_rx_id_resp(conn, msg);
+		break;
+	case GSM48_MT_MM_CM_SERV_REQ:
+		rc = gsm48_rx_mm_serv_req(conn, msg);
+		break;
+	case GSM48_MT_MM_STATUS:
+		rc = gsm48_rx_mm_status(msg);
+		break;
+	case GSM48_MT_MM_TMSI_REALL_COMPL:
+		DEBUGP(DMM, "TMSI Reallocation Completed. Subscriber: %s\n",
+		       conn->subscr ?
+				subscr_name(conn->subscr) :
+				"unknown subscriber");
+		release_loc_updating_req(conn);
+		break;
+	case GSM48_MT_MM_IMSI_DETACH_IND:
+		rc = gsm48_rx_mm_imsi_detach_ind(msg);
+		break;
+	case GSM48_MT_MM_CM_REEST_REQ:
+		DEBUGP(DMM, "CM REESTABLISH REQUEST: Not implemented\n");
+		break;
+	case GSM48_MT_MM_AUTH_RESP:
+		rc = gsm48_rx_mm_auth_resp(conn, msg);
+		break;
+	default:
+		LOGP(DMM, LOGL_NOTICE, "Unknown GSM 04.08 MM msg type 0x%02x\n",
+			gh->msg_type);
+		break;
+	}
+
+	return rc;
+}
+
+/* Receive a PAGING RESPONSE message from the MS */
+static int gsm48_rx_rr_pag_resp(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm_bts *bts = conn->bts;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm48_pag_resp *resp;
+	u_int8_t *classmark2_lv = gh->data + 1;
+	u_int8_t mi_type;
+	char mi_string[GSM48_MI_SIZE];
+	struct gsm_subscriber *subscr = NULL;
+	int rc = 0;
+
+	resp = (struct gsm48_pag_resp *) &gh->data[0];
+	gsm48_paging_extract_mi(resp, msgb_l3len(msg) - sizeof(*gh),
+				mi_string, &mi_type);
+	DEBUGP(DRR, "PAGING RESPONSE: mi_type=0x%02x MI(%s)\n",
+		mi_type, mi_string);
+
+	switch (mi_type) {
+	case GSM_MI_TYPE_TMSI:
+		subscr = subscr_get_by_tmsi(bts->network,
+					    tmsi_from_string(mi_string));
+		break;
+	case GSM_MI_TYPE_IMSI:
+		subscr = subscr_get_by_imsi(bts->network, mi_string);
+		break;
+	}
+
+	if (!subscr) {
+		DEBUGP(DRR, "<- Can't find any subscriber for this ID\n");
+		/* FIXME: request id? close channel? */
+		return -EINVAL;
+	}
+	DEBUGP(DRR, "<- Channel was requested by %s\n",
+		subscr->name && strlen(subscr->name) ? subscr->name : subscr->imsi);
+
+	subscr->equipment.classmark2_len = *classmark2_lv;
+	memcpy(subscr->equipment.classmark2, classmark2_lv+1, *classmark2_lv);
+	db_sync_equipment(&subscr->equipment);
+
+	rc = gsm48_handle_paging_resp(conn, msg, subscr);
+	return rc;
+}
+
+static int gsm48_rx_rr_classmark(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	struct gsm_subscriber *subscr = conn->subscr;
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	u_int8_t cm2_len, cm3_len = 0;
+	u_int8_t *cm2, *cm3 = NULL;
+
+	DEBUGP(DRR, "CLASSMARK CHANGE ");
+
+	/* classmark 2 */
+	cm2_len = gh->data[0];
+	cm2 = &gh->data[1];
+	DEBUGPC(DRR, "CM2(len=%u) ", cm2_len);
+
+	if (payload_len > cm2_len + 1) {
+		/* we must have a classmark3 */
+		if (gh->data[cm2_len+1] != 0x20) {
+			DEBUGPC(DRR, "ERR CM3 TAG\n");
+			return -EINVAL;
+		}
+		if (cm2_len > 3) {
+			DEBUGPC(DRR, "CM2 too long!\n");
+			return -EINVAL;
+		}
+		
+		cm3_len = gh->data[cm2_len+2];
+		cm3 = &gh->data[cm2_len+3];
+		if (cm3_len > 14) {
+			DEBUGPC(DRR, "CM3 len %u too long!\n", cm3_len);
+			return -EINVAL;
+		}
+		DEBUGPC(DRR, "CM3(len=%u)\n", cm3_len);
+	}
+	if (subscr) {
+		subscr->equipment.classmark2_len = cm2_len;
+		memcpy(subscr->equipment.classmark2, cm2, cm2_len);
+		if (cm3) {
+			subscr->equipment.classmark3_len = cm3_len;
+			memcpy(subscr->equipment.classmark3, cm3, cm3_len);
+		}
+		db_sync_equipment(&subscr->equipment);
+	}
+
+	return 0;
+}
+
+static int gsm48_rx_rr_status(struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+
+	DEBUGP(DRR, "STATUS rr_cause = %s\n",
+		rr_cause_name(gh->data[0]));
+
+	return 0;
+}
+
+static int gsm48_rx_rr_meas_rep(struct msgb *msg)
+{
+	struct gsm_meas_rep *meas_rep = lchan_next_meas_rep(msg->lchan);
+
+	/* This shouldn't actually end up here, as RSL treats
+	 * L3 Info of 08.58 MEASUREMENT REPORT different by calling
+	 * directly into gsm48_parse_meas_rep */
+	DEBUGP(DMEAS, "DIRECT GSM48 MEASUREMENT REPORT ?!? ");
+	gsm48_parse_meas_rep(meas_rep, msg);
+
+	return 0;
+}
+
+static int gsm48_rx_rr_app_info(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t apdu_id_flags;
+	u_int8_t apdu_len;
+	u_int8_t *apdu_data;
+
+	apdu_id_flags = gh->data[0];
+	apdu_len = gh->data[1];
+	apdu_data = gh->data+2;
+	
+	DEBUGP(DNM, "RX APPLICATION INFO id/flags=0x%02x apdu_len=%u apdu=%s",
+		apdu_id_flags, apdu_len, hexdump(apdu_data, apdu_len));
+
+	return db_apdu_blob_store(conn->subscr, apdu_id_flags, apdu_len, apdu_data);
+}
+
+/* Chapter 9.1.10 Ciphering Mode Complete */
+static int gsm48_rx_rr_ciph_m_compl(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	gsm_cbfn *cb;
+	int rc = 0;
+
+	DEBUGP(DRR, "CIPHERING MODE COMPLETE\n");
+
+	/* Safety check */
+	if (!conn->sec_operation) {
+		DEBUGP(DRR, "No authentication/cipher operation in progress !!!\n");
+		return -EIO;
+	}
+
+	/* FIXME: check for MI (if any) */
+
+	/* Call back whatever was in progress (if anything) ... */
+	cb = conn->sec_operation->cb;
+	if (cb) {
+		rc = cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_SUCCEEDED,
+			NULL, conn, conn->sec_operation->cb_data);
+	}
+
+	/* Complete the operation */
+	release_security_operation(conn);
+
+	return rc;
+}
+
+/* Chapter 9.1.16 Handover complete */
+static int gsm48_rx_rr_ho_compl(struct msgb *msg)
+{
+	struct lchan_signal_data sig;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+
+	DEBUGP(DRR, "HANDOVER COMPLETE cause = %s\n",
+		rr_cause_name(gh->data[0]));
+
+	sig.lchan = msg->lchan;
+	sig.mr = NULL;
+	dispatch_signal(SS_LCHAN, S_LCHAN_HANDOVER_COMPL, &sig);
+	/* FIXME: release old channel */
+
+	return 0;
+}
+
+/* Chapter 9.1.17 Handover Failure */
+static int gsm48_rx_rr_ho_fail(struct msgb *msg)
+{
+	struct lchan_signal_data sig;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+
+	DEBUGP(DRR, "HANDOVER FAILED cause = %s\n",
+		rr_cause_name(gh->data[0]));
+
+	sig.lchan = msg->lchan;
+	sig.mr = NULL;
+	dispatch_signal(SS_LCHAN, S_LCHAN_HANDOVER_FAIL, &sig);
+	/* FIXME: release allocated new channel */
+
+	return 0;
+}
+
+/* Receive a GSM 04.08 Radio Resource (RR) message */
+static int gsm0408_rcv_rr(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	int rc = 0;
+
+	switch (gh->msg_type) {
+	case GSM48_MT_RR_CLSM_CHG:
+		rc = gsm48_rx_rr_classmark(conn, msg);
+		break;
+	case GSM48_MT_RR_GPRS_SUSP_REQ:
+		DEBUGP(DRR, "GRPS SUSPEND REQUEST\n");
+		break;
+	case GSM48_MT_RR_PAG_RESP:
+		rc = gsm48_rx_rr_pag_resp(conn, msg);
+		break;
+	case GSM48_MT_RR_STATUS:
+		rc = gsm48_rx_rr_status(msg);
+		break;
+	case GSM48_MT_RR_MEAS_REP:
+		rc = gsm48_rx_rr_meas_rep(msg);
+		break;
+	case GSM48_MT_RR_APP_INFO:
+		rc = gsm48_rx_rr_app_info(conn, msg);
+		break;
+	case GSM48_MT_RR_CIPH_M_COMPL:
+		rc = gsm48_rx_rr_ciph_m_compl(conn, msg);
+		break;
+	case GSM48_MT_RR_HANDO_COMPL:
+		rc = gsm48_rx_rr_ho_compl(msg);
+		break;
+	case GSM48_MT_RR_HANDO_FAIL:
+		rc = gsm48_rx_rr_ho_fail(msg);
+		break;
+	default:
+		LOGP(DRR, LOGL_NOTICE, "Unimplemented "
+			"GSM 04.08 RR msg type 0x%02x\n", gh->msg_type);
+		break;
+	}
+
+	return rc;
+}
+
+int gsm48_send_rr_app_info(struct gsm_subscriber_connection *conn, u_int8_t apdu_id,
+			   u_int8_t apdu_len, const u_int8_t *apdu)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+
+	msg->lchan = conn->lchan;
+	
+	DEBUGP(DRR, "TX APPLICATION INFO id=0x%02x, len=%u\n",
+		apdu_id, apdu_len);
+	
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh) + 2 + apdu_len);
+	gh->proto_discr = GSM48_PDISC_RR;
+	gh->msg_type = GSM48_MT_RR_APP_INFO;
+	gh->data[0] = apdu_id;
+	gh->data[1] = apdu_len;
+	memcpy(gh->data+2, apdu, apdu_len);
+
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+/* Call Control */
+
+/* The entire call control code is written in accordance with Figure 7.10c
+ * for 'very early assignment', i.e. we allocate a TCH/F during IMMEDIATE
+ * ASSIGN, then first use that TCH/F for signalling and later MODE MODIFY
+ * it for voice */
+
+static void new_cc_state(struct gsm_trans *trans, int state)
+{
+	if (state > 31 || state < 0)
+		return;
+
+	DEBUGP(DCC, "new state %s -> %s\n",
+		gsm48_cc_state_name(trans->cc.state),
+		gsm48_cc_state_name(state));
+
+	trans->cc.state = state;
+}
+
+static int gsm48_cc_tx_status(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	u_int8_t *cause, *call_state;
+
+	gh->msg_type = GSM48_MT_CC_STATUS;
+
+	cause = msgb_put(msg, 3);
+	cause[0] = 2;
+	cause[1] = GSM48_CAUSE_CS_GSM | GSM48_CAUSE_LOC_USER;
+	cause[2] = 0x80 | 30;	/* response to status inquiry */
+
+	call_state = msgb_put(msg, 1);
+	call_state[0] = 0xc0 | 0x00;
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_tx_simple(struct gsm_subscriber_connection *conn,
+			   u_int8_t pdisc, u_int8_t msg_type)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	msg->lchan = conn->lchan;
+
+	gh->proto_discr = pdisc;
+	gh->msg_type = msg_type;
+
+	return gsm48_conn_sendmsg(msg, conn, NULL);
+}
+
+static void gsm48_stop_cc_timer(struct gsm_trans *trans)
+{
+	if (bsc_timer_pending(&trans->cc.timer)) {
+		DEBUGP(DCC, "stopping pending timer T%x\n", trans->cc.Tcurrent);
+		bsc_del_timer(&trans->cc.timer);
+		trans->cc.Tcurrent = 0;
+	}
+}
+
+static int mncc_recvmsg(struct gsm_network *net, struct gsm_trans *trans,
+			int msg_type, struct gsm_mncc *mncc)
+{
+	struct msgb *msg;
+	unsigned char *data;
+
+	if (trans)
+		if (trans->conn && trans->conn->lchan)
+			DEBUGP(DCC, "(bts %d trx %d ts %d ti %x sub %s) "
+				"Sending '%s' to MNCC.\n",
+				trans->conn->lchan->ts->trx->bts->nr,
+				trans->conn->lchan->ts->trx->nr,
+				trans->conn->lchan->ts->nr, trans->transaction_id,
+				(trans->subscr)?(trans->subscr->extension):"-",
+				get_mncc_name(msg_type));
+		else
+			DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+				"Sending '%s' to MNCC.\n",
+				(trans->subscr)?(trans->subscr->extension):"-",
+				get_mncc_name(msg_type));
+	else
+		DEBUGP(DCC, "(bts - trx - ts - ti -- sub -) "
+			"Sending '%s' to MNCC.\n", get_mncc_name(msg_type));
+
+	mncc->msg_type = msg_type;
+	
+	msg = msgb_alloc(sizeof(struct gsm_mncc), "MNCC");
+	if (!msg)
+		return -ENOMEM;
+
+	data = msgb_put(msg, sizeof(struct gsm_mncc));
+	memcpy(data, mncc, sizeof(struct gsm_mncc));
+
+	cc_tx_to_mncc(net, msg);
+
+	return 0;
+}
+
+int mncc_release_ind(struct gsm_network *net, struct gsm_trans *trans,
+		     u_int32_t callref, int location, int value)
+{
+	struct gsm_mncc rel;
+
+	memset(&rel, 0, sizeof(rel));
+	rel.callref = callref;
+	mncc_set_cause(&rel, location, value);
+	return mncc_recvmsg(net, trans, MNCC_REL_IND, &rel);
+}
+
+/* Call Control Specific transaction release.
+ * gets called by trans_free, DO NOT CALL YOURSELF! */
+void _gsm48_cc_trans_free(struct gsm_trans *trans)
+{
+	gsm48_stop_cc_timer(trans);
+
+	/* send release to L4, if callref still exists */
+	if (trans->callref) {
+		/* Ressource unavailable */
+		mncc_release_ind(trans->subscr->net, trans, trans->callref,
+				 GSM48_CAUSE_LOC_PRN_S_LU,
+				 GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+	}
+	if (trans->cc.state != GSM_CSTATE_NULL)
+		new_cc_state(trans, GSM_CSTATE_NULL);
+	if (trans->conn)
+		trau_mux_unmap(&trans->conn->lchan->ts->e1_link, trans->callref);
+}
+
+static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg);
+
+/* call-back from paging the B-end of the connection */
+static int setup_trig_pag_evt(unsigned int hooknum, unsigned int event,
+			      struct msgb *msg, void *_conn, void *param)
+{
+	int found = 0;
+	struct gsm_subscriber_connection *conn = _conn;
+	struct gsm_network **paging_request = param, *net;
+	struct gsm_trans *transt, *tmp;
+
+	if (hooknum != GSM_HOOK_RR_PAGING)
+		return -EINVAL;
+
+	net = *paging_request;
+	if (!net) {
+		DEBUGP(DCC, "Error Network not set!\n");
+		return -EINVAL;
+	}
+
+	/* check all tranactions (without lchan) for subscriber */
+	llist_for_each_entry_safe(transt, tmp, &net->trans_list, entry) {
+		if (transt->paging_request != paging_request || transt->conn)
+			continue;
+		switch (event) {
+		case GSM_PAGING_SUCCEEDED:
+			if (!conn) // paranoid
+				break;
+			DEBUGP(DCC, "Paging subscr %s succeeded!\n",
+				transt->subscr->extension);
+			found = 1;
+			/* Assign lchan */
+			if (!transt->conn) {
+				transt->paging_request = NULL;
+				transt->conn = conn;
+				conn->put_channel = 1;
+			}
+			/* send SETUP request to called party */
+			gsm48_cc_tx_setup(transt, &transt->cc.msg);
+			break;
+		case GSM_PAGING_EXPIRED:
+		case GSM_PAGING_BUSY:
+			DEBUGP(DCC, "Paging subscr %s expired!\n",
+				transt->subscr->extension);
+			/* Temporarily out of order */
+			found = 1;
+			mncc_release_ind(transt->subscr->net, transt,
+					 transt->callref,
+					 GSM48_CAUSE_LOC_PRN_S_LU,
+					 GSM48_CC_CAUSE_DEST_OOO);
+			transt->callref = 0;
+			transt->paging_request = NULL;
+			trans_free(transt);
+			break;
+		}
+	}
+
+	talloc_free(paging_request);
+
+	/*
+	 * FIXME: The queue needs to be kicked. This is likely to go through a RF
+	 * failure and then the subscr will be poke again. This needs a lot of fixing
+	 * in the subscriber queue code.
+	 */
+	if (!found && conn)
+		conn->put_channel = 1;
+	return 0;
+}
+
+static int tch_recv_mncc(struct gsm_network *net, u_int32_t callref, int enable);
+
+/* handle audio path for handover */
+static int handle_ho_signal(unsigned int subsys, unsigned int signal,
+			    void *handler_data, void *signal_data)
+{
+	struct rtp_socket *old_rs, *new_rs, *other_rs;
+	struct ho_signal_data *sig = signal_data;
+
+	if (subsys != SS_HO || signal != S_HANDOVER_ACK)
+		return 0;
+
+	if (ipacc_rtp_direct) {
+		LOGP(DHO, LOGL_ERROR, "unable to handover in direct RTP mode\n");
+		return 0;
+	}
+
+	/* RTP Proxy mode */
+	new_rs = sig->new_lchan->abis_ip.rtp_socket;
+	old_rs = sig->old_lchan->abis_ip.rtp_socket;
+
+	if (!new_rs) {
+		LOGP(DHO, LOGL_ERROR, "no RTP socket for new_lchan\n");
+		return -EIO;
+	}
+
+	rsl_ipacc_mdcx_to_rtpsock(sig->new_lchan);
+
+	if (!old_rs) {
+		LOGP(DHO, LOGL_ERROR, "no RTP socket for old_lchan\n");
+		return -EIO;
+	}
+
+	/* copy rx_action and reference to other sock */
+	new_rs->rx_action = old_rs->rx_action;
+	new_rs->tx_action = old_rs->tx_action;
+	new_rs->transmit = old_rs->transmit;
+
+	switch (sig->old_lchan->abis_ip.rtp_socket->rx_action) {
+	case RTP_PROXY:
+		other_rs = old_rs->proxy.other_sock;
+		rtp_socket_proxy(new_rs, other_rs);
+		/* delete reference to other end socket to prevent
+		 * rtp_socket_free() from removing the inverse reference */
+		old_rs->proxy.other_sock = NULL;
+		break;
+	case RTP_RECV_UPSTREAM:
+		new_rs->receive = old_rs->receive;
+		break;
+	case RTP_NONE:
+		break;
+	}
+
+	return 0;
+}
+
+/* some other part of the code sends us a signal */
+static int handle_abisip_signal(unsigned int subsys, unsigned int signal,
+				 void *handler_data, void *signal_data)
+{
+	struct gsm_lchan *lchan = signal_data;
+	int rc;
+	struct gsm_network *net;
+	struct gsm_trans *trans;
+
+	if (subsys != SS_ABISIP)
+		return 0;
+
+	/* in case we use direct BTS-to-BTS RTP */
+	if (ipacc_rtp_direct)
+		return 0;
+
+	switch (signal) {
+	case S_ABISIP_CRCX_ACK:
+		/* in case we don't use direct BTS-to-BTS RTP */
+		/* the BTS has successfully bound a TCH to a local ip/port,
+		 * which means we can connect our UDP socket to it */
+		if (lchan->abis_ip.rtp_socket) {
+			rtp_socket_free(lchan->abis_ip.rtp_socket);
+			lchan->abis_ip.rtp_socket = NULL;
+		}
+
+		lchan->abis_ip.rtp_socket = rtp_socket_create();
+		if (!lchan->abis_ip.rtp_socket)
+			return -EIO;
+
+		rc = rtp_socket_connect(lchan->abis_ip.rtp_socket,
+				   lchan->abis_ip.bound_ip,
+				   lchan->abis_ip.bound_port);
+		if (rc < 0)
+			return -EIO;
+
+		/* check if any transactions on this lchan still have
+		 * a tch_recv_mncc request pending */
+		net = lchan->ts->trx->bts->network;
+		llist_for_each_entry(trans, &net->trans_list, entry) {
+			if (trans->conn && trans->conn->lchan == lchan && trans->tch_recv) {
+				DEBUGP(DCC, "pending tch_recv_mncc request\n");
+				tch_recv_mncc(net, trans->callref, 1);
+			}
+		}
+		break;
+	case S_ABISIP_DLCX_IND:
+		/* the BTS tells us a RTP stream has been disconnected */
+		if (lchan->abis_ip.rtp_socket) {
+			rtp_socket_free(lchan->abis_ip.rtp_socket);
+			lchan->abis_ip.rtp_socket = NULL;
+		}
+
+		break;
+	}
+
+	return 0;
+}
+
+/* map two ipaccess RTP streams onto each other */
+static int tch_map(struct gsm_lchan *lchan, struct gsm_lchan *remote_lchan)
+{
+	struct gsm_bts *bts = lchan->ts->trx->bts;
+	struct gsm_bts *remote_bts = remote_lchan->ts->trx->bts;
+	int rc;
+
+	DEBUGP(DCC, "Setting up TCH map between (bts=%u,trx=%u,ts=%u) and (bts=%u,trx=%u,ts=%u)\n",
+		bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
+		remote_bts->nr, remote_lchan->ts->trx->nr, remote_lchan->ts->nr);
+
+	if (bts->type != remote_bts->type) {
+		DEBUGP(DCC, "Cannot switch calls between different BTS types yet\n");
+		return -EINVAL;
+	}
+
+	// todo: map between different bts types
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NANOBTS:
+		if (!ipacc_rtp_direct) {
+			/* connect the TCH's to our RTP proxy */
+			rc = rsl_ipacc_mdcx_to_rtpsock(lchan);
+			if (rc < 0)
+				return rc;
+			rc = rsl_ipacc_mdcx_to_rtpsock(remote_lchan);
+			if (rc < 0)
+				return rc;
+			/* connect them with each other */
+			rtp_socket_proxy(lchan->abis_ip.rtp_socket,
+					 remote_lchan->abis_ip.rtp_socket);
+		} else {
+			/* directly connect TCH RTP streams to each other */
+			rc = rsl_ipacc_mdcx(lchan, remote_lchan->abis_ip.bound_ip,
+						remote_lchan->abis_ip.bound_port,
+						remote_lchan->abis_ip.rtp_payload2);
+			if (rc < 0)
+				return rc;
+			rc = rsl_ipacc_mdcx(remote_lchan, lchan->abis_ip.bound_ip,
+						lchan->abis_ip.bound_port,
+						lchan->abis_ip.rtp_payload2);
+		}
+		break;
+	case GSM_BTS_TYPE_BS11:
+		trau_mux_map_lchan(lchan, remote_lchan);
+		break;
+	default:
+		DEBUGP(DCC, "Unknown BTS type %u\n", bts->type);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* bridge channels of two transactions */
+static int tch_bridge(struct gsm_network *net, u_int32_t *refs)
+{
+	struct gsm_trans *trans1 = trans_find_by_callref(net, refs[0]);
+	struct gsm_trans *trans2 = trans_find_by_callref(net, refs[1]);
+
+	if (!trans1 || !trans2)
+		return -EIO;
+
+	if (!trans1->conn || !trans2->conn)
+		return -EIO;
+
+	/* through-connect channel */
+	return tch_map(trans1->conn->lchan, trans2->conn->lchan);
+}
+
+/* enable receive of channels to MNCC upqueue */
+static int tch_recv_mncc(struct gsm_network *net, u_int32_t callref, int enable)
+{
+	struct gsm_trans *trans;
+	struct gsm_lchan *lchan;
+	struct gsm_bts *bts;
+	int rc;
+
+	/* Find callref */
+	trans = trans_find_by_callref(net, callref);
+	if (!trans)
+		return -EIO;
+	if (!trans->conn)
+		return 0;
+	lchan = trans->conn->lchan;
+	bts = lchan->ts->trx->bts;
+
+	switch (bts->type) {
+	case GSM_BTS_TYPE_NANOBTS:
+		if (ipacc_rtp_direct) {
+			DEBUGP(DCC, "Error: RTP proxy is disabled\n");
+			return -EINVAL;
+		}
+		/* in case, we don't have a RTP socket yet, we note this
+		 * in the transaction and try later */
+		if (!lchan->abis_ip.rtp_socket) {
+			trans->tch_recv = enable;
+			DEBUGP(DCC, "queue tch_recv_mncc request (%d)\n", enable);
+			return 0;
+		}
+		if (enable) {
+			/* connect the TCH's to our RTP proxy */
+			rc = rsl_ipacc_mdcx_to_rtpsock(lchan);
+			if (rc < 0)
+				return rc;
+			/* assign socket to application interface */
+			rtp_socket_upstream(lchan->abis_ip.rtp_socket,
+				net, callref);
+		} else
+			rtp_socket_upstream(lchan->abis_ip.rtp_socket,
+				net, 0);
+		break;
+	case GSM_BTS_TYPE_BS11:
+		if (enable)
+			return trau_recv_lchan(lchan, callref);
+		return trau_mux_unmap(NULL, callref);
+		break;
+	default:
+		DEBUGP(DCC, "Unknown BTS type %u\n", bts->type);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int gsm48_cc_rx_status_enq(struct gsm_trans *trans, struct msgb *msg)
+{
+	DEBUGP(DCC, "-> STATUS ENQ\n");
+	return gsm48_cc_tx_status(trans, msg);
+}
+
+static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg);
+static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg);
+
+static void gsm48_cc_timeout(void *arg)
+{
+	struct gsm_trans *trans = arg;
+	int disconnect = 0, release = 0;
+	int mo_cause = GSM48_CC_CAUSE_RECOVERY_TIMER;
+	int mo_location = GSM48_CAUSE_LOC_USER;
+	int l4_cause = GSM48_CC_CAUSE_NORMAL_UNSPEC;
+	int l4_location = GSM48_CAUSE_LOC_PRN_S_LU;
+	struct gsm_mncc mo_rel, l4_rel;
+
+	memset(&mo_rel, 0, sizeof(struct gsm_mncc));
+	mo_rel.callref = trans->callref;
+	memset(&l4_rel, 0, sizeof(struct gsm_mncc));
+	l4_rel.callref = trans->callref;
+
+	switch(trans->cc.Tcurrent) {
+	case 0x303:
+		release = 1;
+		l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND;
+		break;
+	case 0x310:
+		disconnect = 1;
+		l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND;
+		break;
+	case 0x313:
+		disconnect = 1;
+		/* unknown, did not find it in the specs */
+		break;
+	case 0x301:
+		disconnect = 1;
+		l4_cause = GSM48_CC_CAUSE_USER_NOTRESPOND;
+		break;
+	case 0x308:
+		if (!trans->cc.T308_second) {
+			/* restart T308 a second time */
+			gsm48_cc_tx_release(trans, &trans->cc.msg);
+			trans->cc.T308_second = 1;
+			break; /* stay in release state */
+		}
+		trans_free(trans);
+		return;
+//		release = 1;
+//		l4_cause = 14;
+//		break;
+	case 0x306:
+		release = 1;
+		mo_cause = trans->cc.msg.cause.value;
+		mo_location = trans->cc.msg.cause.location;
+		break;
+	case 0x323:
+		disconnect = 1;
+		break;
+	default:
+		release = 1;
+	}
+
+	if (release && trans->callref) {
+		/* process release towards layer 4 */
+		mncc_release_ind(trans->subscr->net, trans, trans->callref,
+				 l4_location, l4_cause);
+		trans->callref = 0;
+	}
+
+	if (disconnect && trans->callref) {
+		/* process disconnect towards layer 4 */
+		mncc_set_cause(&l4_rel, l4_location, l4_cause);
+		mncc_recvmsg(trans->subscr->net, trans, MNCC_DISC_IND, &l4_rel);
+	}
+
+	/* process disconnect towards mobile station */
+	if (disconnect || release) {
+		mncc_set_cause(&mo_rel, mo_location, mo_cause);
+		mo_rel.cause.diag[0] = ((trans->cc.Tcurrent & 0xf00) >> 8) + '0';
+		mo_rel.cause.diag[1] = ((trans->cc.Tcurrent & 0x0f0) >> 4) + '0';
+		mo_rel.cause.diag[2] = (trans->cc.Tcurrent & 0x00f) + '0';
+		mo_rel.cause.diag_len = 3;
+
+		if (disconnect)
+			gsm48_cc_tx_disconnect(trans, &mo_rel);
+		if (release)
+			gsm48_cc_tx_release(trans, &mo_rel);
+	}
+
+}
+
+static void gsm48_start_cc_timer(struct gsm_trans *trans, int current,
+				 int sec, int micro)
+{
+	DEBUGP(DCC, "starting timer T%x with %d seconds\n", current, sec);
+	trans->cc.timer.cb = gsm48_cc_timeout;
+	trans->cc.timer.data = trans;
+	bsc_schedule_timer(&trans->cc.timer, sec, micro);
+	trans->cc.Tcurrent = current;
+}
+
+static int gsm48_cc_rx_setup(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t msg_type = gh->msg_type & 0xbf;
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc setup;
+
+	memset(&setup, 0, sizeof(struct gsm_mncc));
+	setup.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* emergency setup is identified by msg_type */
+	if (msg_type == GSM48_MT_CC_EMERG_SETUP)
+		setup.emergency = 1;
+
+	/* use subscriber as calling party number */
+	if (trans->subscr) {
+		setup.fields |= MNCC_F_CALLING;
+		strncpy(setup.calling.number, trans->subscr->extension,
+			sizeof(setup.calling.number)-1);
+		strncpy(setup.imsi, trans->subscr->imsi,
+			sizeof(setup.imsi)-1);
+	}
+	/* bearer capability */
+	if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+		setup.fields |= MNCC_F_BEARER_CAP;
+		gsm48_decode_bearer_cap(&setup.bearer_cap,
+				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+	}
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		setup.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&setup.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* called party bcd number */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CALLED_BCD)) {
+		setup.fields |= MNCC_F_CALLED;
+		gsm48_decode_called(&setup.called,
+			      TLVP_VAL(&tp, GSM48_IE_CALLED_BCD)-1);
+	}
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		setup.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&setup.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		setup.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&setup.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+	/* CLIR suppression */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CLIR_SUPP))
+		setup.clir.sup = 1;
+	/* CLIR invocation */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CLIR_INVOC))
+		setup.clir.inv = 1;
+	/* cc cap */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CC_CAP)) {
+		setup.fields |= MNCC_F_CCCAP;
+		gsm48_decode_cccap(&setup.cccap,
+			     TLVP_VAL(&tp, GSM48_IE_CC_CAP)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_INITIATED);
+
+	LOGP(DCC, LOGL_INFO, "Subscriber %s (%s) sends SETUP to %s\n",
+	     subscr_name(trans->subscr), trans->subscr->extension,
+	     setup.called.number);
+
+	counter_inc(trans->subscr->net->stats.call.mo_setup);
+
+	/* indicate setup to MNCC */
+	mncc_recvmsg(trans->subscr->net, trans, MNCC_SETUP_IND, &setup);
+
+	/* MNCC code will modify the channel asynchronously, we should
+	 * ipaccess-bind only after the modification has been made to the
+	 * lchan->tch_mode */
+	return 0;
+}
+
+static int gsm48_cc_tx_setup(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	struct gsm_mncc *setup = arg;
+	int rc, trans_id;
+
+	gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	/* transaction id must not be assigned */
+	if (trans->transaction_id != 0xff) { /* unasssigned */
+		DEBUGP(DCC, "TX Setup with assigned transaction. "
+			"This is not allowed!\n");
+		/* Temporarily out of order */
+		rc = mncc_release_ind(trans->subscr->net, trans, trans->callref,
+				      GSM48_CAUSE_LOC_PRN_S_LU,
+				      GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+		trans->callref = 0;
+		trans_free(trans);
+		return rc;
+	}
+	
+	/* Get free transaction_id */
+	trans_id = trans_assign_trans_id(trans->subscr, GSM48_PDISC_CC, 0);
+	if (trans_id < 0) {
+		/* no free transaction ID */
+		rc = mncc_release_ind(trans->subscr->net, trans, trans->callref,
+				      GSM48_CAUSE_LOC_PRN_S_LU,
+				      GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+		trans->callref = 0;
+		trans_free(trans);
+		return rc;
+	}
+	trans->transaction_id = trans_id;
+
+	gh->msg_type = GSM48_MT_CC_SETUP;
+
+	gsm48_start_cc_timer(trans, 0x303, GSM48_T303);
+
+	/* bearer capability */
+	if (setup->fields & MNCC_F_BEARER_CAP)
+		gsm48_encode_bearer_cap(msg, 0, &setup->bearer_cap);
+	/* facility */
+	if (setup->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &setup->facility);
+	/* progress */
+	if (setup->fields & MNCC_F_PROGRESS)
+		gsm48_encode_progress(msg, 0, &setup->progress);
+	/* calling party BCD number */
+	if (setup->fields & MNCC_F_CALLING)
+		gsm48_encode_calling(msg, &setup->calling);
+	/* called party BCD number */
+	if (setup->fields & MNCC_F_CALLED)
+		gsm48_encode_called(msg, &setup->called);
+	/* user-user */
+	if (setup->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &setup->useruser);
+	/* redirecting party BCD number */
+	if (setup->fields & MNCC_F_REDIRECTING)
+		gsm48_encode_redirecting(msg, &setup->redirecting);
+	/* signal */
+	if (setup->fields & MNCC_F_SIGNAL)
+		gsm48_encode_signal(msg, setup->signal);
+	
+	new_cc_state(trans, GSM_CSTATE_CALL_PRESENT);
+
+	counter_inc(trans->subscr->net->stats.call.mt_setup);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_call_conf(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc call_conf;
+
+	gsm48_stop_cc_timer(trans);
+	gsm48_start_cc_timer(trans, 0x310, GSM48_T310);
+
+	memset(&call_conf, 0, sizeof(struct gsm_mncc));
+	call_conf.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+#if 0
+	/* repeat */
+	if (TLVP_PRESENT(&tp, GSM48_IE_REPEAT_CIR))
+		call_conf.repeat = 1;
+	if (TLVP_PRESENT(&tp, GSM48_IE_REPEAT_SEQ))
+		call_conf.repeat = 2;
+#endif
+	/* bearer capability */
+	if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+		call_conf.fields |= MNCC_F_BEARER_CAP;
+		gsm48_decode_bearer_cap(&call_conf.bearer_cap,
+				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+	}
+	/* cause */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+		call_conf.fields |= MNCC_F_CAUSE;
+		gsm48_decode_cause(&call_conf.cause,
+			     TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+	}
+	/* cc cap */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CC_CAP)) {
+		call_conf.fields |= MNCC_F_CCCAP;
+		gsm48_decode_cccap(&call_conf.cccap,
+			     TLVP_VAL(&tp, GSM48_IE_CC_CAP)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_MO_TERM_CALL_CONF);
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_CALL_CONF_IND,
+			    &call_conf);
+}
+
+static int gsm48_cc_tx_call_proc(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *proceeding = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_CALL_PROC;
+
+	new_cc_state(trans, GSM_CSTATE_MO_CALL_PROC);
+
+	/* bearer capability */
+	if (proceeding->fields & MNCC_F_BEARER_CAP)
+		gsm48_encode_bearer_cap(msg, 0, &proceeding->bearer_cap);
+	/* facility */
+	if (proceeding->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &proceeding->facility);
+	/* progress */
+	if (proceeding->fields & MNCC_F_PROGRESS)
+		gsm48_encode_progress(msg, 0, &proceeding->progress);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_alerting(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc alerting;
+	
+	gsm48_stop_cc_timer(trans);
+	gsm48_start_cc_timer(trans, 0x301, GSM48_T301);
+
+	memset(&alerting, 0, sizeof(struct gsm_mncc));
+	alerting.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		alerting.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&alerting.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+
+	/* progress */
+	if (TLVP_PRESENT(&tp, GSM48_IE_PROGR_IND)) {
+		alerting.fields |= MNCC_F_PROGRESS;
+		gsm48_decode_progress(&alerting.progress,
+				TLVP_VAL(&tp, GSM48_IE_PROGR_IND)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		alerting.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&alerting.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_CALL_RECEIVED);
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_ALERT_IND,
+			    &alerting);
+}
+
+static int gsm48_cc_tx_alerting(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *alerting = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_ALERTING;
+
+	/* facility */
+	if (alerting->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &alerting->facility);
+	/* progress */
+	if (alerting->fields & MNCC_F_PROGRESS)
+		gsm48_encode_progress(msg, 0, &alerting->progress);
+	/* user-user */
+	if (alerting->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &alerting->useruser);
+
+	new_cc_state(trans, GSM_CSTATE_CALL_DELIVERED);
+	
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_progress(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *progress = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_PROGRESS;
+
+	/* progress */
+	gsm48_encode_progress(msg, 1, &progress->progress);
+	/* user-user */
+	if (progress->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &progress->useruser);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_connect(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *connect = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_CONNECT;
+
+	gsm48_stop_cc_timer(trans);
+	gsm48_start_cc_timer(trans, 0x313, GSM48_T313);
+
+	/* facility */
+	if (connect->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &connect->facility);
+	/* progress */
+	if (connect->fields & MNCC_F_PROGRESS)
+		gsm48_encode_progress(msg, 0, &connect->progress);
+	/* connected number */
+	if (connect->fields & MNCC_F_CONNECTED)
+		gsm48_encode_connected(msg, &connect->connected);
+	/* user-user */
+	if (connect->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &connect->useruser);
+
+	new_cc_state(trans, GSM_CSTATE_CONNECT_IND);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_connect(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc connect;
+
+	gsm48_stop_cc_timer(trans);
+
+	memset(&connect, 0, sizeof(struct gsm_mncc));
+	connect.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* use subscriber as connected party number */
+	if (trans->subscr) {
+		connect.fields |= MNCC_F_CONNECTED;
+		strncpy(connect.connected.number, trans->subscr->extension,
+			sizeof(connect.connected.number)-1);
+		strncpy(connect.imsi, trans->subscr->imsi,
+			sizeof(connect.imsi)-1);
+	}
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		connect.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&connect.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		connect.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&connect.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		connect.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&connect.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_CONNECT_REQUEST);
+	counter_inc(trans->subscr->net->stats.call.mt_connect);
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_SETUP_CNF, &connect);
+}
+
+
+static int gsm48_cc_rx_connect_ack(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm_mncc connect_ack;
+
+	gsm48_stop_cc_timer(trans);
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+	counter_inc(trans->subscr->net->stats.call.mo_connect_ack);
+	
+	memset(&connect_ack, 0, sizeof(struct gsm_mncc));
+	connect_ack.callref = trans->callref;
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_SETUP_COMPL_IND,
+			    &connect_ack);
+}
+
+static int gsm48_cc_tx_connect_ack(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_CONNECT_ACK;
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_disconnect(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc disc;
+
+	gsm48_stop_cc_timer(trans);
+
+	new_cc_state(trans, GSM_CSTATE_DISCONNECT_REQ);
+
+	memset(&disc, 0, sizeof(struct gsm_mncc));
+	disc.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_CAUSE, 0);
+	/* cause */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+		disc.fields |= MNCC_F_CAUSE;
+		gsm48_decode_cause(&disc.cause,
+			     TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+	}
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		disc.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&disc.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		disc.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&disc.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		disc.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&disc.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_DISC_IND, &disc);
+
+}
+
+static struct gsm_mncc_cause default_cause = {
+	.location	= GSM48_CAUSE_LOC_PRN_S_LU,
+	.coding		= 0,
+	.rec		= 0,
+	.rec_val	= 0,
+	.value		= GSM48_CC_CAUSE_NORMAL_UNSPEC,
+	.diag_len	= 0,
+	.diag		= { 0 },
+};
+
+static int gsm48_cc_tx_disconnect(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *disc = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_DISCONNECT;
+
+	gsm48_stop_cc_timer(trans);
+	gsm48_start_cc_timer(trans, 0x306, GSM48_T306);
+
+	/* cause */
+	if (disc->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 1, &disc->cause);
+	else
+		gsm48_encode_cause(msg, 1, &default_cause);
+
+	/* facility */
+	if (disc->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &disc->facility);
+	/* progress */
+	if (disc->fields & MNCC_F_PROGRESS)
+		gsm48_encode_progress(msg, 0, &disc->progress);
+	/* user-user */
+	if (disc->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &disc->useruser);
+
+	/* store disconnect cause for T306 expiry */
+	memcpy(&trans->cc.msg, disc, sizeof(struct gsm_mncc));
+
+	new_cc_state(trans, GSM_CSTATE_DISCONNECT_IND);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_release(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc rel;
+	int rc;
+
+	gsm48_stop_cc_timer(trans);
+
+	memset(&rel, 0, sizeof(struct gsm_mncc));
+	rel.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* cause */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+		rel.fields |= MNCC_F_CAUSE;
+		gsm48_decode_cause(&rel.cause,
+			     TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+	}
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		rel.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&rel.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		rel.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&rel.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		rel.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&rel.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	if (trans->cc.state == GSM_CSTATE_RELEASE_REQ) {
+		/* release collision 5.4.5 */
+		rc = mncc_recvmsg(trans->subscr->net, trans, MNCC_REL_CNF, &rel);
+	} else {
+		rc = gsm48_tx_simple(trans->conn,
+				     GSM48_PDISC_CC | (trans->transaction_id << 4),
+				     GSM48_MT_CC_RELEASE_COMPL);
+		rc = mncc_recvmsg(trans->subscr->net, trans, MNCC_REL_IND, &rel);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_NULL);
+
+	trans->callref = 0;
+	trans_free(trans);
+
+	return rc;
+}
+
+static int gsm48_cc_tx_release(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *rel = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_RELEASE;
+
+	trans->callref = 0;
+	
+	gsm48_stop_cc_timer(trans);
+	gsm48_start_cc_timer(trans, 0x308, GSM48_T308);
+
+	/* cause */
+	if (rel->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 0, &rel->cause);
+	/* facility */
+	if (rel->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &rel->facility);
+	/* user-user */
+	if (rel->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &rel->useruser);
+
+	trans->cc.T308_second = 0;
+	memcpy(&trans->cc.msg, rel, sizeof(struct gsm_mncc));
+
+	if (trans->cc.state != GSM_CSTATE_RELEASE_REQ)
+		new_cc_state(trans, GSM_CSTATE_RELEASE_REQ);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_release_compl(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc rel;
+	int rc = 0;
+
+	gsm48_stop_cc_timer(trans);
+
+	memset(&rel, 0, sizeof(struct gsm_mncc));
+	rel.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* cause */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+		rel.fields |= MNCC_F_CAUSE;
+		gsm48_decode_cause(&rel.cause,
+			     TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+	}
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		rel.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&rel.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		rel.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&rel.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		rel.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&rel.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	if (trans->callref) {
+		switch (trans->cc.state) {
+		case GSM_CSTATE_CALL_PRESENT:
+			rc = mncc_recvmsg(trans->subscr->net, trans,
+					  MNCC_REJ_IND, &rel);
+			break;
+		case GSM_CSTATE_RELEASE_REQ:
+			rc = mncc_recvmsg(trans->subscr->net, trans,
+					  MNCC_REL_CNF, &rel);
+			break;
+		default:
+			rc = mncc_recvmsg(trans->subscr->net, trans,
+					  MNCC_REL_IND, &rel);
+		}
+	}
+
+	trans->callref = 0;
+	trans_free(trans);
+
+	return rc;
+}
+
+static int gsm48_cc_tx_release_compl(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *rel = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+	int ret;
+
+	gh->msg_type = GSM48_MT_CC_RELEASE_COMPL;
+
+	trans->callref = 0;
+	
+	gsm48_stop_cc_timer(trans);
+
+	/* cause */
+	if (rel->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 0, &rel->cause);
+	/* facility */
+	if (rel->fields & MNCC_F_FACILITY)
+		gsm48_encode_facility(msg, 0, &rel->facility);
+	/* user-user */
+	if (rel->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 0, &rel->useruser);
+
+	ret =  gsm48_conn_sendmsg(msg, trans->conn, trans);
+
+	trans_free(trans);
+
+	return ret;
+}
+
+static int gsm48_cc_rx_facility(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc fac;
+
+	memset(&fac, 0, sizeof(struct gsm_mncc));
+	fac.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_FACILITY, 0);
+	/* facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
+		fac.fields |= MNCC_F_FACILITY;
+		gsm48_decode_facility(&fac.facility,
+				TLVP_VAL(&tp, GSM48_IE_FACILITY)-1);
+	}
+	/* ss-version */
+	if (TLVP_PRESENT(&tp, GSM48_IE_SS_VERS)) {
+		fac.fields |= MNCC_F_SSVERSION;
+		gsm48_decode_ssversion(&fac.ssversion,
+				 TLVP_VAL(&tp, GSM48_IE_SS_VERS)-1);
+	}
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_FACILITY_IND, &fac);
+}
+
+static int gsm48_cc_tx_facility(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *fac = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_FACILITY;
+
+	/* facility */
+	gsm48_encode_facility(msg, 1, &fac->facility);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_hold(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm_mncc hold;
+
+	memset(&hold, 0, sizeof(struct gsm_mncc));
+	hold.callref = trans->callref;
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_HOLD_IND, &hold);
+}
+
+static int gsm48_cc_tx_hold_ack(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_HOLD_ACK;
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_hold_rej(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *hold_rej = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_HOLD_REJ;
+
+	/* cause */
+	if (hold_rej->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 1, &hold_rej->cause);
+	else
+		gsm48_encode_cause(msg, 1, &default_cause);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_retrieve(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm_mncc retrieve;
+
+	memset(&retrieve, 0, sizeof(struct gsm_mncc));
+	retrieve.callref = trans->callref;
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_RETRIEVE_IND,
+			    &retrieve);
+}
+
+static int gsm48_cc_tx_retrieve_ack(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_RETR_ACK;
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_retrieve_rej(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *retrieve_rej = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_RETR_REJ;
+
+	/* cause */
+	if (retrieve_rej->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 1, &retrieve_rej->cause);
+	else
+		gsm48_encode_cause(msg, 1, &default_cause);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_start_dtmf(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc dtmf;
+
+	memset(&dtmf, 0, sizeof(struct gsm_mncc));
+	dtmf.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+	/* keypad facility */
+	if (TLVP_PRESENT(&tp, GSM48_IE_KPD_FACILITY)) {
+		dtmf.fields |= MNCC_F_KEYPAD;
+		gsm48_decode_keypad(&dtmf.keypad,
+			      TLVP_VAL(&tp, GSM48_IE_KPD_FACILITY)-1);
+	}
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_START_DTMF_IND, &dtmf);
+}
+
+static int gsm48_cc_tx_start_dtmf_ack(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *dtmf = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_START_DTMF_ACK;
+
+	/* keypad */
+	if (dtmf->fields & MNCC_F_KEYPAD)
+		gsm48_encode_keypad(msg, dtmf->keypad);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_start_dtmf_rej(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *dtmf = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_START_DTMF_REJ;
+
+	/* cause */
+	if (dtmf->fields & MNCC_F_CAUSE)
+		gsm48_encode_cause(msg, 1, &dtmf->cause);
+	else
+		gsm48_encode_cause(msg, 1, &default_cause);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_stop_dtmf_ack(struct gsm_trans *trans, void *arg)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_STOP_DTMF_ACK;
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_stop_dtmf(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm_mncc dtmf;
+
+	memset(&dtmf, 0, sizeof(struct gsm_mncc));
+	dtmf.callref = trans->callref;
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_STOP_DTMF_IND, &dtmf);
+}
+
+static int gsm48_cc_rx_modify(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc modify;
+
+	memset(&modify, 0, sizeof(struct gsm_mncc));
+	modify.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_BEARER_CAP, 0);
+	/* bearer capability */
+	if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+		modify.fields |= MNCC_F_BEARER_CAP;
+		gsm48_decode_bearer_cap(&modify.bearer_cap,
+				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_MO_ORIG_MODIFY);
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_MODIFY_IND, &modify);
+}
+
+static int gsm48_cc_tx_modify(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *modify = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_MODIFY;
+
+	gsm48_start_cc_timer(trans, 0x323, GSM48_T323);
+
+	/* bearer capability */
+	gsm48_encode_bearer_cap(msg, 1, &modify->bearer_cap);
+
+	new_cc_state(trans, GSM_CSTATE_MO_TERM_MODIFY);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_modify_complete(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc modify;
+
+	gsm48_stop_cc_timer(trans);
+
+	memset(&modify, 0, sizeof(struct gsm_mncc));
+	modify.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_BEARER_CAP, 0);
+	/* bearer capability */
+	if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+		modify.fields |= MNCC_F_BEARER_CAP;
+		gsm48_decode_bearer_cap(&modify.bearer_cap,
+				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_MODIFY_CNF, &modify);
+}
+
+static int gsm48_cc_tx_modify_complete(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *modify = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_MODIFY_COMPL;
+
+	/* bearer capability */
+	gsm48_encode_bearer_cap(msg, 1, &modify->bearer_cap);
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_modify_reject(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc modify;
+
+	gsm48_stop_cc_timer(trans);
+
+	memset(&modify, 0, sizeof(struct gsm_mncc));
+	modify.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_BEARER_CAP, GSM48_IE_CAUSE);
+	/* bearer capability */
+	if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
+		modify.fields |= GSM48_IE_BEARER_CAP;
+		gsm48_decode_bearer_cap(&modify.bearer_cap,
+				  TLVP_VAL(&tp, GSM48_IE_BEARER_CAP)-1);
+	}
+	/* cause */
+	if (TLVP_PRESENT(&tp, GSM48_IE_CAUSE)) {
+		modify.fields |= MNCC_F_CAUSE;
+		gsm48_decode_cause(&modify.cause,
+			     TLVP_VAL(&tp, GSM48_IE_CAUSE)-1);
+	}
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_MODIFY_REJ, &modify);
+}
+
+static int gsm48_cc_tx_modify_reject(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *modify = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_MODIFY_REJECT;
+
+	/* bearer capability */
+	gsm48_encode_bearer_cap(msg, 1, &modify->bearer_cap);
+	/* cause */
+	gsm48_encode_cause(msg, 1, &modify->cause);
+
+	new_cc_state(trans, GSM_CSTATE_ACTIVE);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_tx_notify(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *notify = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_NOTIFY;
+
+	/* notify */
+	gsm48_encode_notify(msg, notify->notify);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_notify(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+//	struct tlv_parsed tp;
+	struct gsm_mncc notify;
+
+	memset(&notify, 0, sizeof(struct gsm_mncc));
+	notify.callref = trans->callref;
+//	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len);
+	if (payload_len >= 1)
+		gsm48_decode_notify(&notify.notify, gh->data);
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_NOTIFY_IND, &notify);
+}
+
+static int gsm48_cc_tx_userinfo(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *user = arg;
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+
+	gh->msg_type = GSM48_MT_CC_USER_INFO;
+
+	/* user-user */
+	if (user->fields & MNCC_F_USERUSER)
+		gsm48_encode_useruser(msg, 1, &user->useruser);
+	/* more data */
+	if (user->more)
+		gsm48_encode_more(msg);
+
+	return gsm48_conn_sendmsg(msg, trans->conn, trans);
+}
+
+static int gsm48_cc_rx_userinfo(struct gsm_trans *trans, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	struct gsm_mncc user;
+
+	memset(&user, 0, sizeof(struct gsm_mncc));
+	user.callref = trans->callref;
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, GSM48_IE_USER_USER, 0);
+	/* user-user */
+	if (TLVP_PRESENT(&tp, GSM48_IE_USER_USER)) {
+		user.fields |= MNCC_F_USERUSER;
+		gsm48_decode_useruser(&user.useruser,
+				TLVP_VAL(&tp, GSM48_IE_USER_USER)-1);
+	}
+	/* more data */
+	if (TLVP_PRESENT(&tp, GSM48_IE_MORE_DATA))
+		user.more = 1;
+
+	return mncc_recvmsg(trans->subscr->net, trans, MNCC_USERINFO_IND, &user);
+}
+
+static int _gsm48_lchan_modify(struct gsm_trans *trans, void *arg)
+{
+	struct gsm_mncc *mode = arg;
+
+	return gsm0808_assign_req(trans->conn, mode->lchan_mode, 1);
+}
+
+static struct downstate {
+	u_int32_t	states;
+	int		type;
+	int		(*rout) (struct gsm_trans *trans, void *arg);
+} downstatelist[] = {
+	/* mobile originating call establishment */
+	{SBIT(GSM_CSTATE_INITIATED), /* 5.2.1.2 */
+	 MNCC_CALL_PROC_REQ, gsm48_cc_tx_call_proc},
+	{SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC), /* 5.2.1.2 | 5.2.1.5 */
+	 MNCC_ALERT_REQ, gsm48_cc_tx_alerting},
+	{SBIT(GSM_CSTATE_INITIATED) | SBIT(GSM_CSTATE_MO_CALL_PROC) | SBIT(GSM_CSTATE_CALL_DELIVERED), /* 5.2.1.2 | 5.2.1.6 | 5.2.1.6 */
+	 MNCC_SETUP_RSP, gsm48_cc_tx_connect},
+	{SBIT(GSM_CSTATE_MO_CALL_PROC), /* 5.2.1.4.2 */
+	 MNCC_PROGRESS_REQ, gsm48_cc_tx_progress},
+	/* mobile terminating call establishment */
+	{SBIT(GSM_CSTATE_NULL), /* 5.2.2.1 */
+	 MNCC_SETUP_REQ, gsm48_cc_tx_setup},
+	{SBIT(GSM_CSTATE_CONNECT_REQUEST),
+	 MNCC_SETUP_COMPL_REQ, gsm48_cc_tx_connect_ack},
+	 /* signalling during call */
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_NOTIFY_REQ, gsm48_cc_tx_notify},
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ),
+	 MNCC_FACILITY_REQ, gsm48_cc_tx_facility},
+	{ALL_STATES,
+	 MNCC_START_DTMF_RSP, gsm48_cc_tx_start_dtmf_ack},
+	{ALL_STATES,
+	 MNCC_START_DTMF_REJ, gsm48_cc_tx_start_dtmf_rej},
+	{ALL_STATES,
+	 MNCC_STOP_DTMF_RSP, gsm48_cc_tx_stop_dtmf_ack},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_HOLD_CNF, gsm48_cc_tx_hold_ack},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_HOLD_REJ, gsm48_cc_tx_hold_rej},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_RETRIEVE_CNF, gsm48_cc_tx_retrieve_ack},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_RETRIEVE_REJ, gsm48_cc_tx_retrieve_rej},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_MODIFY_REQ, gsm48_cc_tx_modify},
+	{SBIT(GSM_CSTATE_MO_ORIG_MODIFY),
+	 MNCC_MODIFY_RSP, gsm48_cc_tx_modify_complete},
+	{SBIT(GSM_CSTATE_MO_ORIG_MODIFY),
+	 MNCC_MODIFY_REJ, gsm48_cc_tx_modify_reject},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 MNCC_USERINFO_REQ, gsm48_cc_tx_userinfo},
+	/* clearing */
+	{SBIT(GSM_CSTATE_INITIATED),
+	 MNCC_REJ_REQ, gsm48_cc_tx_release_compl},
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_DISCONNECT_IND) - SBIT(GSM_CSTATE_RELEASE_REQ) - SBIT(GSM_CSTATE_DISCONNECT_REQ), /* 5.4.4 */
+	 MNCC_DISC_REQ, gsm48_cc_tx_disconnect},
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ), /* 5.4.3.2 */
+	 MNCC_REL_REQ, gsm48_cc_tx_release},
+	/* special */
+	{ALL_STATES,
+	 MNCC_LCHAN_MODIFY, _gsm48_lchan_modify},
+};
+
+#define DOWNSLLEN \
+	(sizeof(downstatelist) / sizeof(struct downstate))
+
+
+int mncc_tx_to_cc(struct gsm_network *net, int msg_type, void *arg)
+{
+	int i, rc = 0;
+	struct gsm_trans *trans = NULL, *transt;
+	struct gsm_subscriber_connection *conn = NULL;
+	struct gsm_bts *bts = NULL;
+	struct gsm_mncc *data = arg, rel;
+
+	DEBUGP(DMNCC, "receive message %s\n", get_mncc_name(msg_type));
+
+	/* handle special messages */
+	switch(msg_type) {
+	case MNCC_BRIDGE:
+		return tch_bridge(net, arg);
+	case MNCC_FRAME_DROP:
+		return tch_recv_mncc(net, data->callref, 0);
+	case MNCC_FRAME_RECV:
+		return tch_recv_mncc(net, data->callref, 1);
+	case GSM_TCHF_FRAME:
+		/* Find callref */
+		trans = trans_find_by_callref(net, data->callref);
+		if (!trans) {
+			LOGP(DMNCC, LOGL_ERROR, "TCH frame for non-existing trans\n");
+			return -EIO;
+		}
+		if (!trans->conn) {
+			LOGP(DMNCC, LOGL_NOTICE, "TCH frame for trans without conn\n");
+			return 0;
+		}
+		if (trans->conn->lchan->type != GSM_LCHAN_TCH_F) {
+			/* This should be LOGL_ERROR or NOTICE, but
+			 * unfortuantely it happens for a couple of frames at
+			 * the beginning of every RTP connection */
+			LOGP(DMNCC, LOGL_DEBUG, "TCH frame for lchan != TCH_F\n");
+			return 0;
+		}
+		bts = trans->conn->lchan->ts->trx->bts;
+		switch (bts->type) {
+		case GSM_BTS_TYPE_NANOBTS:
+			if (!trans->conn->lchan->abis_ip.rtp_socket) {
+				DEBUGP(DMNCC, "TCH frame to lchan without RTP connection\n");
+				return 0;
+			}
+			return rtp_send_frame(trans->conn->lchan->abis_ip.rtp_socket, arg);
+		case GSM_BTS_TYPE_BS11:
+			return trau_send_frame(trans->conn->lchan, arg);
+		default:
+			DEBUGP(DCC, "Unknown BTS type %u\n", bts->type);
+		}
+		return -EINVAL;
+	}
+
+	memset(&rel, 0, sizeof(struct gsm_mncc));
+	rel.callref = data->callref;
+
+	/* Find callref */
+	trans = trans_find_by_callref(net, data->callref);
+
+	/* Callref unknown */
+	if (!trans) {
+		struct gsm_subscriber *subscr;
+
+		if (msg_type != MNCC_SETUP_REQ) {
+			DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+				"Received '%s' from MNCC with "
+				"unknown callref %d\n", data->called.number,
+				get_mncc_name(msg_type), data->callref);
+			/* Invalid call reference */
+			return mncc_release_ind(net, NULL, data->callref,
+						GSM48_CAUSE_LOC_PRN_S_LU,
+						GSM48_CC_CAUSE_INVAL_TRANS_ID);
+		}
+		if (!data->called.number[0] && !data->imsi[0]) {
+			DEBUGP(DCC, "(bts - trx - ts - ti) "
+				"Received '%s' from MNCC with "
+				"no number or IMSI\n", get_mncc_name(msg_type));
+			/* Invalid number */
+			return mncc_release_ind(net, NULL, data->callref,
+						GSM48_CAUSE_LOC_PRN_S_LU,
+						GSM48_CC_CAUSE_INV_NR_FORMAT);
+		}
+		/* New transaction due to setup, find subscriber */
+		if (data->called.number[0])
+			subscr = subscr_get_by_extension(net,
+							data->called.number);
+		else
+			subscr = subscr_get_by_imsi(net, data->imsi);
+		/* If subscriber is not found */
+		if (!subscr) {
+			DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+				"Received '%s' from MNCC with "
+				"unknown subscriber %s\n", data->called.number,
+				get_mncc_name(msg_type), data->called.number);
+			/* Unknown subscriber */
+			return mncc_release_ind(net, NULL, data->callref,
+						GSM48_CAUSE_LOC_PRN_S_LU,
+						GSM48_CC_CAUSE_UNASSIGNED_NR);
+		}
+		/* If subscriber is not "attached" */
+		if (!subscr->lac) {
+			DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+				"Received '%s' from MNCC with "
+				"detached subscriber %s\n", data->called.number,
+				get_mncc_name(msg_type), data->called.number);
+			subscr_put(subscr);
+			/* Temporarily out of order */
+			return mncc_release_ind(net, NULL, data->callref,
+						GSM48_CAUSE_LOC_PRN_S_LU,
+						GSM48_CC_CAUSE_DEST_OOO);
+		}
+		/* Create transaction */
+		trans = trans_alloc(subscr, GSM48_PDISC_CC, 0xff, data->callref);
+		if (!trans) {
+			DEBUGP(DCC, "No memory for trans.\n");
+			subscr_put(subscr);
+			/* Ressource unavailable */
+			mncc_release_ind(net, NULL, data->callref,
+					 GSM48_CAUSE_LOC_PRN_S_LU,
+					 GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+			return -ENOMEM;
+		}
+		/* Find lchan */
+		conn = connection_for_subscr(subscr);
+
+		/* If subscriber has no lchan */
+		if (!conn) {
+			/* find transaction with this subscriber already paging */
+			llist_for_each_entry(transt, &net->trans_list, entry) {
+				/* Transaction of our lchan? */
+				if (transt == trans ||
+				    transt->subscr != subscr)
+					continue;
+				DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+					"Received '%s' from MNCC with "
+					"unallocated channel, paging already "
+					"started for lac %d.\n",
+					data->called.number,
+					get_mncc_name(msg_type), subscr->lac);
+				subscr_put(subscr);
+				trans_free(trans);
+				return 0;
+			}
+			/* store setup informations until paging was successfull */
+			memcpy(&trans->cc.msg, data, sizeof(struct gsm_mncc));
+
+			/* Get a channel */
+			trans->paging_request = talloc_zero(subscr->net, struct gsm_network*);
+			if (!trans->paging_request) {
+				LOGP(DCC, LOGL_ERROR, "Failed to allocate paging token.\n");
+				subscr_put(subscr);
+				trans_free(trans);
+				return 0;
+			}
+
+			*trans->paging_request = subscr->net;
+			subscr_get_channel(subscr, RSL_CHANNEED_TCH_F, setup_trig_pag_evt, trans->paging_request);
+
+			subscr_put(subscr);
+			return 0;
+		}
+		/* Assign lchan */
+		trans->conn = conn;
+		subscr_put(subscr);
+	}
+
+	if (trans->conn)
+		conn = trans->conn;
+
+	/* if paging did not respond yet */
+	if (!conn) {
+		DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
+			"Received '%s' from MNCC in paging state\n",
+			(trans->subscr)?(trans->subscr->extension):"-",
+			get_mncc_name(msg_type));
+		mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_NORM_CALL_CLEAR);
+		if (msg_type == MNCC_REL_REQ)
+			rc = mncc_recvmsg(net, trans, MNCC_REL_CNF, &rel);
+		else
+			rc = mncc_recvmsg(net, trans, MNCC_REL_IND, &rel);
+		trans->callref = 0;
+		trans_free(trans);
+		return rc;
+	}
+
+	DEBUGP(DCC, "(bts %d trx %d ts %d ti %02x sub %s) "
+		"Received '%s' from MNCC in state %d (%s)\n",
+		conn->bts->nr, conn->lchan->ts->trx->nr, conn->lchan->ts->nr,
+		trans->transaction_id,
+		(trans->conn->subscr)?(trans->conn->subscr->extension):"-",
+		get_mncc_name(msg_type), trans->cc.state,
+		gsm48_cc_state_name(trans->cc.state));
+
+	/* Find function for current state and message */
+	for (i = 0; i < DOWNSLLEN; i++)
+		if ((msg_type == downstatelist[i].type)
+		 && ((1 << trans->cc.state) & downstatelist[i].states))
+			break;
+	if (i == DOWNSLLEN) {
+		DEBUGP(DCC, "Message unhandled at this state.\n");
+		return 0;
+	}
+
+	rc = downstatelist[i].rout(trans, arg);
+
+	return rc;
+}
+
+
+static struct datastate {
+	u_int32_t	states;
+	int		type;
+	int		(*rout) (struct gsm_trans *trans, struct msgb *msg);
+} datastatelist[] = {
+	/* mobile originating call establishment */
+	{SBIT(GSM_CSTATE_NULL), /* 5.2.1.2 */
+	 GSM48_MT_CC_SETUP, gsm48_cc_rx_setup},
+	{SBIT(GSM_CSTATE_NULL), /* 5.2.1.2 */
+	 GSM48_MT_CC_EMERG_SETUP, gsm48_cc_rx_setup},
+	{SBIT(GSM_CSTATE_CONNECT_IND), /* 5.2.1.2 */
+	 GSM48_MT_CC_CONNECT_ACK, gsm48_cc_rx_connect_ack},
+	/* mobile terminating call establishment */
+	{SBIT(GSM_CSTATE_CALL_PRESENT), /* 5.2.2.3.2 */
+	 GSM48_MT_CC_CALL_CONF, gsm48_cc_rx_call_conf},
+	{SBIT(GSM_CSTATE_CALL_PRESENT) | SBIT(GSM_CSTATE_MO_TERM_CALL_CONF), /* ???? | 5.2.2.3.2 */
+	 GSM48_MT_CC_ALERTING, gsm48_cc_rx_alerting},
+	{SBIT(GSM_CSTATE_CALL_PRESENT) | SBIT(GSM_CSTATE_MO_TERM_CALL_CONF) | SBIT(GSM_CSTATE_CALL_RECEIVED), /* (5.2.2.6) | 5.2.2.6 | 5.2.2.6 */
+	 GSM48_MT_CC_CONNECT, gsm48_cc_rx_connect},
+	 /* signalling during call */
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL),
+	 GSM48_MT_CC_FACILITY, gsm48_cc_rx_facility},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 GSM48_MT_CC_NOTIFY, gsm48_cc_rx_notify},
+	{ALL_STATES,
+	 GSM48_MT_CC_START_DTMF, gsm48_cc_rx_start_dtmf},
+	{ALL_STATES,
+	 GSM48_MT_CC_STOP_DTMF, gsm48_cc_rx_stop_dtmf},
+	{ALL_STATES,
+	 GSM48_MT_CC_STATUS_ENQ, gsm48_cc_rx_status_enq},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 GSM48_MT_CC_HOLD, gsm48_cc_rx_hold},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 GSM48_MT_CC_RETR, gsm48_cc_rx_retrieve},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 GSM48_MT_CC_MODIFY, gsm48_cc_rx_modify},
+	{SBIT(GSM_CSTATE_MO_TERM_MODIFY),
+	 GSM48_MT_CC_MODIFY_COMPL, gsm48_cc_rx_modify_complete},
+	{SBIT(GSM_CSTATE_MO_TERM_MODIFY),
+	 GSM48_MT_CC_MODIFY_REJECT, gsm48_cc_rx_modify_reject},
+	{SBIT(GSM_CSTATE_ACTIVE),
+	 GSM48_MT_CC_USER_INFO, gsm48_cc_rx_userinfo},
+	/* clearing */
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL) - SBIT(GSM_CSTATE_RELEASE_REQ), /* 5.4.3.2 */
+	 GSM48_MT_CC_DISCONNECT, gsm48_cc_rx_disconnect},
+	{ALL_STATES - SBIT(GSM_CSTATE_NULL), /* 5.4.4.1.2.2 */
+	 GSM48_MT_CC_RELEASE, gsm48_cc_rx_release},
+	{ALL_STATES, /* 5.4.3.4 */
+	 GSM48_MT_CC_RELEASE_COMPL, gsm48_cc_rx_release_compl},
+};
+
+#define DATASLLEN \
+	(sizeof(datastatelist) / sizeof(struct datastate))
+
+static int gsm0408_rcv_cc(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t msg_type = gh->msg_type & 0xbf;
+	u_int8_t transaction_id = ((gh->proto_discr & 0xf0) ^ 0x80) >> 4; /* flip */
+	struct gsm_trans *trans = NULL;
+	int i, rc = 0;
+
+	if (msg_type & 0x80) {
+		DEBUGP(DCC, "MSG 0x%2x not defined for PD error\n", msg_type);
+		return -EINVAL;
+	}
+
+	/* Find transaction */
+	trans = trans_find_by_id(conn->subscr, GSM48_PDISC_CC, transaction_id);
+
+	DEBUGP(DCC, "(bts %d trx %d ts %d ti %x sub %s) "
+		"Received '%s' from MS in state %d (%s)\n",
+		conn->bts->nr, conn->lchan->ts->trx->nr, conn->lchan->ts->nr,
+		transaction_id, (conn->subscr)?(conn->subscr->extension):"-",
+		gsm48_cc_msg_name(msg_type), trans?(trans->cc.state):0,
+		gsm48_cc_state_name(trans?(trans->cc.state):0));
+
+	/* Create transaction */
+	if (!trans) {
+		DEBUGP(DCC, "Unknown transaction ID %x, "
+			"creating new trans.\n", transaction_id);
+		/* Create transaction */
+		trans = trans_alloc(conn->subscr, GSM48_PDISC_CC,
+				    transaction_id, new_callref++);
+		if (!trans) {
+			DEBUGP(DCC, "No memory for trans.\n");
+			rc = gsm48_tx_simple(conn,
+					     GSM48_PDISC_CC | (transaction_id << 4),
+					     GSM48_MT_CC_RELEASE_COMPL);
+			return -ENOMEM;
+		}
+		/* Assign transaction */
+		trans->conn = conn;
+	}
+
+	/* find function for current state and message */
+	for (i = 0; i < DATASLLEN; i++)
+		if ((msg_type == datastatelist[i].type)
+		 && ((1 << trans->cc.state) & datastatelist[i].states))
+			break;
+	if (i == DATASLLEN) {
+		DEBUGP(DCC, "Message unhandled at this state.\n");
+		return 0;
+	}
+
+	rc = datastatelist[i].rout(trans, msg);
+
+	return rc;
+}
+
+/* Create a dummy to wait five seconds */
+static void release_anchor(struct gsm_subscriber_connection *conn)
+{
+	if (!conn->anch_operation)
+		return;
+
+	bsc_del_timer(&conn->anch_operation->timeout);
+	talloc_free(conn->anch_operation);
+	conn->anch_operation = NULL;
+}
+
+static void anchor_timeout(void *_data)
+{
+	struct gsm_subscriber_connection *con = _data;
+
+	release_anchor(con);
+	msc_release_connection(con);
+}
+
+int gsm0408_new_conn(struct gsm_subscriber_connection *conn)
+{
+	conn->anch_operation = talloc_zero(conn, struct gsm_anchor_operation);
+	if (!conn->anch_operation)
+		return -1;
+
+	conn->anch_operation->timeout.data = conn;
+	conn->anch_operation->timeout.cb = anchor_timeout;
+	bsc_schedule_timer(&conn->anch_operation->timeout, 5, 0);
+	return 0;
+}
+
+/* here we get data from the BSC level... */
+int gsm0408_dispatch(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t pdisc = gh->proto_discr & 0x0f;
+	int rc = 0;
+
+	if (silent_call_reroute(conn, msg))
+		return silent_call_rx(conn, msg);
+	
+	switch (pdisc) {
+	case GSM48_PDISC_CC:
+		release_anchor(conn);
+		rc = gsm0408_rcv_cc(conn, msg);
+		break;
+	case GSM48_PDISC_MM:
+		rc = gsm0408_rcv_mm(conn, msg);
+		break;
+	case GSM48_PDISC_RR:
+		rc = gsm0408_rcv_rr(conn, msg);
+		break;
+	case GSM48_PDISC_SMS:
+		release_anchor(conn);
+		rc = gsm0411_rcv_sms(conn, msg);
+		break;
+	case GSM48_PDISC_MM_GPRS:
+	case GSM48_PDISC_SM_GPRS:
+		LOGP(DRLL, LOGL_NOTICE, "Unimplemented "
+			"GSM 04.08 discriminator 0x%02x\n", pdisc);
+		break;
+	case GSM48_PDISC_NC_SS:
+		release_anchor(conn);
+		rc = handle_rcv_ussd(conn, msg);
+		break;
+	default:
+		LOGP(DRLL, LOGL_NOTICE, "Unknown "
+			"GSM 04.08 discriminator 0x%02x\n", pdisc);
+		break;
+	}
+
+	return rc;
+}
+
+/*
+ * This will be ran by the linker when loading the DSO. We use it to
+ * do system initialization, e.g. registration of signal handlers.
+ */
+static __attribute__((constructor)) void on_dso_load_0408(void)
+{
+	register_signal_handler(SS_HO, handle_ho_signal, NULL);
+	register_signal_handler(SS_ABISIP, handle_abisip_signal, NULL);
+}
diff --git a/openbsc/src/msc/gsm_04_11.c b/openbsc/src/msc/gsm_04_11.c
new file mode 100644
index 0000000..812e758
--- /dev/null
+++ b/openbsc/src/msc/gsm_04_11.c
@@ -0,0 +1,1240 @@
+/* Point-to-Point (PP) Short Message Service (SMS)
+ * Support on Mobile Radio Interface
+ * 3GPP TS 04.11 version 7.1.0 Release 1998 / ETSI TS 100 942 V7.1.0 */
+
+/* (C) 2008 by Daniel Willmann <daniel@totalueberwachung.de>
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (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 Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <netinet/in.h>
+
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/db.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/gsm_04_08.h>
+#include <osmocore/gsm_utils.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/signal.h>
+#include <openbsc/db.h>
+#include <osmocore/talloc.h>
+#include <openbsc/transaction.h>
+#include <openbsc/paging.h>
+#include <openbsc/bsc_rll.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/bsc_api.h>
+
+#define GSM411_ALLOC_SIZE	1024
+#define GSM411_ALLOC_HEADROOM	128
+
+void *tall_gsms_ctx;
+static u_int32_t new_callref = 0x40000001;
+
+static const struct value_string cp_cause_strs[] = {
+	{ GSM411_CP_CAUSE_NET_FAIL,	"Network Failure" },
+	{ GSM411_CP_CAUSE_CONGESTION,	"Congestion" },
+	{ GSM411_CP_CAUSE_INV_TRANS_ID,	"Invalid Transaction ID" },
+	{ GSM411_CP_CAUSE_SEMANT_INC_MSG, "Semantically Incorrect Message" },
+	{ GSM411_CP_CAUSE_INV_MAND_INF,	"Invalid Mandatory Information" },
+	{ GSM411_CP_CAUSE_MSGTYPE_NOTEXIST, "Message Type doesn't exist" },
+	{ GSM411_CP_CAUSE_MSG_INCOMP_STATE,
+				"Message incompatible with protocol state" },
+	{ GSM411_CP_CAUSE_IE_NOTEXIST,	"IE does not exist" },
+	{ GSM411_CP_CAUSE_PROTOCOL_ERR,	"Protocol Error" },
+	{ 0, 0 }
+};
+
+static const struct value_string rp_cause_strs[] = {
+	{ GSM411_RP_CAUSE_MO_NUM_UNASSIGNED, "(MO) Number not assigned" },
+	{ GSM411_RP_CAUSE_MO_OP_DET_BARR, "(MO) Operator determined barring" },
+	{ GSM411_RP_CAUSE_MO_CALL_BARRED, "(MO) Call barred" },
+	{ GSM411_RP_CAUSE_MO_SMS_REJECTED, "(MO) SMS rejected" },
+	{ GSM411_RP_CAUSE_MO_DEST_OUT_OF_ORDER, "(MO) Destination out of order" },
+	{ GSM411_RP_CAUSE_MO_UNIDENTIFIED_SUBSCR, "(MO) Unidentified subscriber" },
+	{ GSM411_RP_CAUSE_MO_FACILITY_REJ, "(MO) Facility reject" },
+	{ GSM411_RP_CAUSE_MO_UNKNOWN_SUBSCR, "(MO) Unknown subscriber" },
+	{ GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER, "(MO) Network out of order" },
+	{ GSM411_RP_CAUSE_MO_TEMP_FAIL, "(MO) Temporary failure" },
+	{ GSM411_RP_CAUSE_MO_CONGESTION, "(MO) Congestion" },
+	{ GSM411_RP_CAUSE_MO_RES_UNAVAIL, "(MO) Resource unavailable" },
+	{ GSM411_RP_CAUSE_MO_REQ_FAC_NOTSUBSCR, "(MO) Requested facility not subscribed" },
+	{ GSM411_RP_CAUSE_MO_REQ_FAC_NOTIMPL, "(MO) Requested facility not implemented" },
+	{ GSM411_RP_CAUSE_MO_INTERWORKING, "(MO) Interworking" },
+	/* valid only for MT */
+	{ GSM411_RP_CAUSE_MT_MEM_EXCEEDED, "(MT) Memory Exceeded" },
+	/* valid for both directions */
+	{ GSM411_RP_CAUSE_INV_TRANS_REF, "Invalid Transaction Reference" },
+	{ GSM411_RP_CAUSE_SEMANT_INC_MSG, "Semantically Incorrect Message" },
+	{ GSM411_RP_CAUSE_INV_MAND_INF, "Invalid Mandatory Information" },
+	{ GSM411_RP_CAUSE_MSGTYPE_NOTEXIST, "Message Type non-existant" },
+	{ GSM411_RP_CAUSE_MSG_INCOMP_STATE, "Message incompatible with protocol state" },
+	{ GSM411_RP_CAUSE_IE_NOTEXIST, "Information Element not existing" },
+	{ GSM411_RP_CAUSE_PROTOCOL_ERR, "Protocol Error" },
+	{ 0, NULL }
+};
+
+
+struct gsm_sms *sms_alloc(void)
+{
+	return talloc_zero(tall_gsms_ctx, struct gsm_sms);
+}
+
+void sms_free(struct gsm_sms *sms)
+{
+	/* drop references to subscriber structure */
+	if (sms->sender)
+		subscr_put(sms->sender);
+	if (sms->receiver)
+		subscr_put(sms->receiver);
+
+	talloc_free(sms);
+}
+
+struct gsm_sms *sms_from_text(struct gsm_subscriber *receiver, int dcs, const char *text)
+{
+	struct gsm_sms *sms = sms_alloc();
+
+	if (!sms)
+		return NULL;
+
+	sms->receiver = subscr_get(receiver);
+	strncpy(sms->text, text, sizeof(sms->text)-1);
+
+	/* FIXME: don't use ID 1 static */
+	sms->sender = subscr_get_by_id(receiver->net, 1);
+	sms->reply_path_req = 0;
+	sms->status_rep_req = 0;
+	sms->ud_hdr_ind = 0;
+	sms->protocol_id = 0; /* implicit */
+	sms->data_coding_scheme = dcs;
+	strncpy(sms->dest_addr, receiver->extension, sizeof(sms->dest_addr)-1);
+	/* Generate user_data */
+	sms->user_data_len = gsm_7bit_encode(sms->user_data, sms->text);
+
+	return sms;
+}
+
+
+static void send_signal(int sig_no,
+			struct gsm_trans *trans,
+			struct gsm_sms *sms,
+			int paging_result)
+{
+	struct sms_signal_data sig;
+	sig.trans = trans;
+	sig.sms = sms;
+	sig.paging_result = paging_result;
+	dispatch_signal(SS_SMS, sig_no, &sig);
+}
+
+/*
+ * This should be called whenever all SMS to a given subscriber
+ * on a given connection has been sent. This will inform the higher
+ * layers that a channel can be given up.
+ */
+static void gsm411_release_conn(struct gsm_subscriber_connection *conn)
+{
+	if (!conn)
+		return;
+
+	subscr_put_channel(conn->subscr);
+}
+
+struct msgb *gsm411_msgb_alloc(void)
+{
+	return msgb_alloc_headroom(GSM411_ALLOC_SIZE, GSM411_ALLOC_HEADROOM,
+				   "GSM 04.11");
+}
+
+static int gsm411_sendmsg(struct gsm_subscriber_connection *conn, struct msgb *msg, u_int8_t link_id)
+{
+	DEBUGP(DSMS, "GSM4.11 TX %s\n", hexdump(msg->data, msg->len));
+	msg->l3h = msg->data;
+	return gsm0808_submit_dtap(conn, msg, link_id, 1);
+}
+
+/* SMC TC1* is expired */
+static void cp_timer_expired(void *data)
+{
+	struct gsm_trans *trans = data;
+
+	DEBUGP(DSMS, "SMC Timer TC1* is expired, calling trans_free()\n");
+	/* FIXME: we need to re-transmit the last CP-DATA 1..3 times */
+	trans_free(trans);
+}
+
+/* Prefix msg with a 04.08/04.11 CP header */
+static int gsm411_cp_sendmsg(struct msgb *msg, struct gsm_trans *trans,
+			     u_int8_t msg_type)
+{
+	struct gsm48_hdr *gh;
+
+	gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
+	/* Outgoing needs the highest bit set */
+	gh->proto_discr = trans->protocol | (trans->transaction_id<<4);
+	gh->msg_type = msg_type;
+
+	/* mobile originating */
+	switch (gh->msg_type) {
+	case GSM411_MT_CP_DATA:
+		/* 5.2.3.1.2: enter MO-wait for CP-ack */
+		/* 5.2.3.2.3: enter MT-wait for CP-ACK */
+		trans->sms.cp_state = GSM411_CPS_WAIT_CP_ACK;
+		trans->sms.cp_timer.data = trans;
+		trans->sms.cp_timer.cb = cp_timer_expired;
+		/* 5.3.2.1: Set Timer TC1A */
+		bsc_schedule_timer(&trans->sms.cp_timer, GSM411_TMR_TC1A);
+		DEBUGP(DSMS, "TX: CP-DATA ");
+		break;
+	case GSM411_MT_CP_ACK:
+		DEBUGP(DSMS, "TX: CP-ACK ");
+		break;
+	case GSM411_MT_CP_ERROR:
+		DEBUGP(DSMS, "TX: CP-ERROR ");
+		break;
+	}
+
+	DEBUGPC(DSMS, "trans=%x\n", trans->transaction_id);
+
+	return gsm411_sendmsg(trans->conn, msg, trans->sms.link_id);
+}
+
+/* Prefix msg with a RP-DATA header and send as CP-DATA */
+static int gsm411_rp_sendmsg(struct msgb *msg, struct gsm_trans *trans,
+			     u_int8_t rp_msg_type, u_int8_t rp_msg_ref)
+{
+	struct gsm411_rp_hdr *rp;
+	u_int8_t len = msg->len;
+
+	/* GSM 04.11 RP-DATA header */
+	rp = (struct gsm411_rp_hdr *)msgb_push(msg, sizeof(*rp));
+	rp->len = len + 2;
+	rp->msg_type = rp_msg_type;
+	rp->msg_ref = rp_msg_ref; /* FIXME: Choose randomly */
+
+	return gsm411_cp_sendmsg(msg, trans, GSM411_MT_CP_DATA);
+}
+
+/* Turn int into semi-octet representation: 98 => 0x89 */
+static u_int8_t bcdify(u_int8_t value)
+{
+	u_int8_t ret;
+
+	ret = value / 10;
+	ret |= (value % 10) << 4;
+
+	return ret;
+}
+
+/* Turn semi-octet representation into int: 0x89 => 98 */
+static u_int8_t unbcdify(u_int8_t value)
+{
+	u_int8_t ret;
+
+	if ((value & 0x0F) > 9 || (value >> 4) > 9)
+		LOGP(DSMS, LOGL_ERROR,
+		     "unbcdify got too big nibble: 0x%02X\n", value);
+
+	ret = (value&0x0F)*10;
+	ret += value>>4;
+
+	return ret;
+}
+
+/* Generate 03.40 TP-SCTS */
+static void gsm340_gen_scts(u_int8_t *scts, time_t time)
+{
+	struct tm *tm = localtime(&time);
+
+	*scts++ = bcdify(tm->tm_year % 100);
+	*scts++ = bcdify(tm->tm_mon + 1);
+	*scts++ = bcdify(tm->tm_mday);
+	*scts++ = bcdify(tm->tm_hour);
+	*scts++ = bcdify(tm->tm_min);
+	*scts++ = bcdify(tm->tm_sec);
+	*scts++ = bcdify(tm->tm_gmtoff/(60*15));
+}
+
+/* Decode 03.40 TP-SCTS (into utc/gmt timestamp) */
+static time_t gsm340_scts(u_int8_t *scts)
+{
+	struct tm tm;
+
+	u_int8_t yr = unbcdify(*scts++);
+
+	if (yr <= 80)
+		tm.tm_year = 100 + yr;
+	else
+		tm.tm_year = yr;
+	tm.tm_mon  = unbcdify(*scts++) - 1;
+	tm.tm_mday = unbcdify(*scts++);
+	tm.tm_hour = unbcdify(*scts++);
+	tm.tm_min  = unbcdify(*scts++);
+	tm.tm_sec  = unbcdify(*scts++);
+	/* according to gsm 03.40 time zone is
+	   "expressed in quarters of an hour" */
+	tm.tm_gmtoff = unbcdify(*scts++) * 15*60;
+
+	return mktime(&tm);
+}
+
+/* Return the default validity period in minutes */
+static unsigned long gsm340_vp_default(void)
+{
+	unsigned long minutes;
+	/* Default validity: two days */
+	minutes = 24 * 60 * 2;
+	return minutes;
+}
+
+/* Decode validity period format 'relative' */
+static unsigned long gsm340_vp_relative(u_int8_t *sms_vp)
+{
+	/* Chapter 9.2.3.12.1 */
+	u_int8_t vp;
+	unsigned long minutes;
+
+	vp = *(sms_vp);
+	if (vp <= 143)
+		minutes = vp + 1 * 5;
+	else if (vp <= 167)
+		minutes = 12*60 + (vp-143) * 30;
+	else if (vp <= 196)
+		minutes = vp-166 * 60 * 24;
+	else
+		minutes = vp-192 * 60 * 24 * 7;
+	return minutes;
+}
+
+/* Decode validity period format 'absolute' */
+static unsigned long gsm340_vp_absolute(u_int8_t *sms_vp)
+{
+	/* Chapter 9.2.3.12.2 */
+	time_t expires, now;
+	unsigned long minutes;
+
+	expires = gsm340_scts(sms_vp);
+	now = time(NULL);
+	if (expires <= now)
+		minutes = 0;
+	else
+		minutes = (expires-now)/60;
+	return minutes;
+}
+
+/* Decode validity period format 'relative in integer representation' */
+static unsigned long gsm340_vp_relative_integer(u_int8_t *sms_vp)
+{
+	u_int8_t vp;
+	unsigned long minutes;
+	vp = *(sms_vp);
+	if (vp == 0) {
+		LOGP(DSMS, LOGL_ERROR,
+		     "reserved relative_integer validity period\n");
+		return gsm340_vp_default();
+	}
+	minutes = vp/60;
+	return minutes;
+}
+
+/* Decode validity period format 'relative in semi-octet representation' */
+static unsigned long gsm340_vp_relative_semioctet(u_int8_t *sms_vp)
+{
+	unsigned long minutes;
+	minutes = unbcdify(*sms_vp++)*60;  /* hours */
+	minutes += unbcdify(*sms_vp++);    /* minutes */
+	minutes += unbcdify(*sms_vp++)/60; /* seconds */
+	return minutes;
+}
+
+/* decode validity period. return minutes */
+static unsigned long gsm340_validity_period(u_int8_t sms_vpf, u_int8_t *sms_vp)
+{
+	u_int8_t fi; /* functionality indicator */
+
+	switch (sms_vpf) {
+	case GSM340_TP_VPF_RELATIVE:
+		return gsm340_vp_relative(sms_vp);
+	case GSM340_TP_VPF_ABSOLUTE:
+		return gsm340_vp_absolute(sms_vp);
+	case GSM340_TP_VPF_ENHANCED:
+		/* Chapter 9.2.3.12.3 */
+		fi = *sms_vp++;
+		/* ignore additional fi */
+		if (fi & (1<<7)) sms_vp++;
+		/* read validity period format */
+		switch (fi & 0x7) {
+		case 0x0:
+			return gsm340_vp_default(); /* no vpf specified */
+		case 0x1:
+			return gsm340_vp_relative(sms_vp);
+		case 0x2:
+			return gsm340_vp_relative_integer(sms_vp);
+		case 0x3:
+			return gsm340_vp_relative_semioctet(sms_vp);
+		default:
+			/* The GSM spec says that the SC should reject any
+			   unsupported and/or undefined values. FIXME */
+			LOGP(DSMS, LOGL_ERROR,
+			     "Reserved enhanced validity period format\n");
+			return gsm340_vp_default();
+		}
+	case GSM340_TP_VPF_NONE:
+	default:
+		return gsm340_vp_default();
+	}
+}
+
+/* determine coding alphabet dependent on GSM 03.38 Section 4 DCS */
+enum sms_alphabet gsm338_get_sms_alphabet(u_int8_t dcs)
+{
+	u_int8_t cgbits = dcs >> 4;
+	enum sms_alphabet alpha = DCS_NONE;
+
+	if ((cgbits & 0xc) == 0) {
+		if (cgbits & 2) {
+			LOGP(DSMS, LOGL_NOTICE,
+			     "Compressed SMS not supported yet\n");
+			return 0xffffffff;
+		}
+
+		switch ((dcs >> 2)&0x03) {
+		case 0:
+			alpha = DCS_7BIT_DEFAULT;
+			break;
+		case 1:
+			alpha = DCS_8BIT_DATA;
+			break;
+		case 2:
+			alpha = DCS_UCS2;
+			break;
+		}
+	} else if (cgbits == 0xc || cgbits == 0xd)
+		alpha = DCS_7BIT_DEFAULT;
+	else if (cgbits == 0xe)
+		alpha = DCS_UCS2;
+	else if (cgbits == 0xf) {
+		if (dcs & 4)
+			alpha = DCS_8BIT_DATA;
+		else
+			alpha = DCS_7BIT_DEFAULT;
+	}
+
+	return alpha;
+}
+
+static int gsm340_rx_sms_submit(struct msgb *msg, struct gsm_sms *gsms)
+{
+	if (db_sms_store(gsms) != 0) {
+		LOGP(DSMS, LOGL_ERROR, "Failed to store SMS in Database\n");
+		return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+	}
+	/* dispatch a signal to tell higher level about it */
+	send_signal(S_SMS_SUBMITTED, NULL, gsms, 0);
+
+	return 0;
+}
+
+/* generate a TPDU address field compliant with 03.40 sec. 9.1.2.5 */
+static int gsm340_gen_oa(u_int8_t *oa, unsigned int oa_len,
+			 struct gsm_subscriber *subscr)
+{
+	int len_in_bytes;
+
+	oa[1] = 0xb9; /* networks-specific number, private numbering plan */
+
+	len_in_bytes = gsm48_encode_bcd_number(oa, oa_len, 1, subscr->extension);
+
+	/* GSM 03.40 tells us the length is in 'useful semi-octets' */
+	oa[0] = strlen(subscr->extension) & 0xff;
+
+	return len_in_bytes;
+}
+
+/* generate a msgb containing a TPDU derived from struct gsm_sms,
+ * returns total size of TPDU */
+static int gsm340_gen_tpdu(struct msgb *msg, struct gsm_sms *sms)
+{
+	u_int8_t *smsp;
+	u_int8_t oa[12];	/* max len per 03.40 */
+	u_int8_t oa_len = 0;
+	u_int8_t octet_len;
+	unsigned int old_msg_len = msg->len;
+
+	/* generate first octet with masked bits */
+	smsp = msgb_put(msg, 1);
+	/* TP-MTI (message type indicator) */
+	*smsp = GSM340_SMS_DELIVER_SC2MS;
+	/* TP-MMS (more messages to send) */
+	if (0 /* FIXME */)
+		*smsp |= 0x04;
+	/* TP-SRI(deliver)/SRR(submit) */
+	if (sms->status_rep_req)
+		*smsp |= 0x20;
+	/* TP-UDHI (indicating TP-UD contains a header) */
+	if (sms->ud_hdr_ind)
+		*smsp |= 0x40;
+	
+	/* generate originator address */
+	oa_len = gsm340_gen_oa(oa, sizeof(oa), sms->sender);
+	smsp = msgb_put(msg, oa_len);
+	memcpy(smsp, oa, oa_len);
+
+	/* generate TP-PID */
+	smsp = msgb_put(msg, 1);
+	*smsp = sms->protocol_id;
+
+	/* generate TP-DCS */
+	smsp = msgb_put(msg, 1);
+	*smsp = sms->data_coding_scheme;
+
+	/* generate TP-SCTS */
+	smsp = msgb_put(msg, 7);
+	gsm340_gen_scts(smsp, time(NULL));
+
+	/* generate TP-UDL */
+	smsp = msgb_put(msg, 1);
+	*smsp = sms->user_data_len;
+
+	/* generate TP-UD */
+	switch (gsm338_get_sms_alphabet(sms->data_coding_scheme)) {
+	case DCS_7BIT_DEFAULT:
+		octet_len = sms->user_data_len*7/8;
+		if (sms->user_data_len*7%8 != 0)
+			octet_len++;
+		/* Warning, user_data_len indicates the amount of septets
+		 * (characters), we need amount of octets occupied */
+		smsp = msgb_put(msg, octet_len);
+		memcpy(smsp, sms->user_data, octet_len);
+		break;
+	case DCS_UCS2:
+	case DCS_8BIT_DATA:
+		smsp = msgb_put(msg, sms->user_data_len);
+		memcpy(smsp, sms->user_data, sms->user_data_len);
+		break;
+	default:
+		LOGP(DSMS, LOGL_NOTICE, "Unhandled Data Coding Scheme: 0x%02X\n",
+		     sms->data_coding_scheme);
+		break;
+	}
+
+	return msg->len - old_msg_len;
+}
+
+/* process an incoming TPDU (called from RP-DATA)
+ * return value > 0: RP CAUSE for ERROR; < 0: silent error; 0 = success */
+static int gsm340_rx_tpdu(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	u_int8_t *smsp = msgb_sms(msg);
+	struct gsm_sms *gsms;
+	u_int8_t sms_mti, sms_mms, sms_vpf, sms_alphabet, sms_rp;
+	u_int8_t *sms_vp;
+	u_int8_t da_len_bytes;
+	u_int8_t address_lv[12]; /* according to 03.40 / 9.1.2.5 */
+	int rc = 0;
+
+	counter_inc(conn->bts->network->stats.sms.submitted);
+
+	gsms = sms_alloc();
+	if (!gsms)
+		return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+
+	/* invert those fields where 0 means active/present */
+	sms_mti = *smsp & 0x03;
+	sms_mms = !!(*smsp & 0x04);
+	sms_vpf = (*smsp & 0x18) >> 3;
+	gsms->status_rep_req = (*smsp & 0x20);
+	gsms->ud_hdr_ind = (*smsp & 0x40);
+	sms_rp  = (*smsp & 0x80);
+
+	smsp++;
+	gsms->msg_ref = *smsp++;
+
+	/* length in bytes of the destination address */
+	da_len_bytes = 2 + *smsp/2 + *smsp%2;
+	if (da_len_bytes > 12) {
+		LOGP(DSMS, LOGL_ERROR, "Destination Address > 12 bytes ?!?\n");
+		rc = GSM411_RP_CAUSE_SEMANT_INC_MSG;
+		goto out;
+	}
+	memset(address_lv, 0, sizeof(address_lv));
+	memcpy(address_lv, smsp, da_len_bytes);
+	/* mangle first byte to reflect length in bytes, not digits */
+	address_lv[0] = da_len_bytes - 1;
+	/* convert to real number */
+	gsm48_decode_bcd_number(gsms->dest_addr, sizeof(gsms->dest_addr), address_lv, 1);
+	smsp += da_len_bytes;
+
+	gsms->protocol_id = *smsp++;
+	gsms->data_coding_scheme = *smsp++;
+
+	sms_alphabet = gsm338_get_sms_alphabet(gsms->data_coding_scheme);
+	if (sms_alphabet == 0xffffffff) {
+		sms_free(gsms);
+		return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+	}
+
+	switch (sms_vpf) {
+	case GSM340_TP_VPF_RELATIVE:
+		sms_vp = smsp++;
+		break;
+	case GSM340_TP_VPF_ABSOLUTE:
+	case GSM340_TP_VPF_ENHANCED:
+		sms_vp = smsp;
+		/* the additional functionality indicator... */
+		if (sms_vpf == GSM340_TP_VPF_ENHANCED && *smsp & (1<<7))
+			smsp++;
+		smsp += 7;
+		break;
+	case GSM340_TP_VPF_NONE:
+		sms_vp = 0;
+		break;
+	default:
+		LOGP(DSMS, LOGL_NOTICE,
+		     "SMS Validity period not implemented: 0x%02x\n", sms_vpf);
+		return GSM411_RP_CAUSE_MO_NET_OUT_OF_ORDER;
+	}
+	gsms->user_data_len = *smsp++;
+	if (gsms->user_data_len) {
+		memcpy(gsms->user_data, smsp, gsms->user_data_len);
+
+		switch (sms_alphabet) {
+		case DCS_7BIT_DEFAULT:
+			gsm_7bit_decode(gsms->text, smsp, gsms->user_data_len);
+			break;
+		case DCS_8BIT_DATA:
+		case DCS_UCS2:
+		case DCS_NONE:
+			break;
+		}
+	}
+
+	gsms->sender = subscr_get(conn->subscr);
+
+	LOGP(DSMS, LOGL_INFO, "RX SMS: Sender: %s, MTI: 0x%02x, VPF: 0x%02x, "
+	     "MR: 0x%02x PID: 0x%02x, DCS: 0x%02x, DA: %s, "
+	     "UserDataLength: 0x%02x, UserData: \"%s\"\n",
+	     subscr_name(gsms->sender), sms_mti, sms_vpf, gsms->msg_ref,
+	     gsms->protocol_id, gsms->data_coding_scheme, gsms->dest_addr,
+	     gsms->user_data_len,
+			sms_alphabet == DCS_7BIT_DEFAULT ? gsms->text :
+				hexdump(gsms->user_data, gsms->user_data_len));
+
+	gsms->validity_minutes = gsm340_validity_period(sms_vpf, sms_vp);
+
+	/* FIXME: This looks very wrong */
+	send_signal(0, NULL, gsms, 0);
+
+	/* determine gsms->receiver based on dialled number */
+	gsms->receiver = subscr_get_by_extension(conn->bts->network, gsms->dest_addr);
+	if (!gsms->receiver) {
+		rc = 1; /* cause 1: unknown subscriber */
+		counter_inc(conn->bts->network->stats.sms.no_receiver);
+		goto out;
+	}
+
+	switch (sms_mti) {
+	case GSM340_SMS_SUBMIT_MS2SC:
+		/* MS is submitting a SMS */
+		rc = gsm340_rx_sms_submit(msg, gsms);
+		break;
+	case GSM340_SMS_COMMAND_MS2SC:
+	case GSM340_SMS_DELIVER_REP_MS2SC:
+		LOGP(DSMS, LOGL_NOTICE, "Unimplemented MTI 0x%02x\n", sms_mti);
+		rc = GSM411_RP_CAUSE_IE_NOTEXIST;
+		break;
+	default:
+		LOGP(DSMS, LOGL_NOTICE, "Undefined MTI 0x%02x\n", sms_mti);
+		rc = GSM411_RP_CAUSE_IE_NOTEXIST;
+		break;
+	}
+
+	if (!rc && !gsms->receiver)
+		rc = GSM411_RP_CAUSE_MO_NUM_UNASSIGNED;
+
+out:
+	sms_free(gsms);
+
+	return rc;
+}
+
+static int gsm411_send_rp_ack(struct gsm_trans *trans, u_int8_t msg_ref)
+{
+	struct msgb *msg = gsm411_msgb_alloc();
+
+	DEBUGP(DSMS, "TX: SMS RP ACK\n");
+
+	return gsm411_rp_sendmsg(msg, trans, GSM411_MT_RP_ACK_MT, msg_ref);
+}
+
+static int gsm411_send_rp_error(struct gsm_trans *trans,
+				u_int8_t msg_ref, u_int8_t cause)
+{
+	struct msgb *msg = gsm411_msgb_alloc();
+
+	msgb_tv_put(msg, 1, cause);
+
+	LOGP(DSMS, LOGL_NOTICE, "TX: SMS RP ERROR, cause %d (%s)\n", cause,
+		get_value_string(rp_cause_strs, cause));
+
+	return gsm411_rp_sendmsg(msg, trans, GSM411_MT_RP_ERROR_MT, msg_ref);
+}
+
+/* Receive a 04.11 TPDU inside RP-DATA / user data */
+static int gsm411_rx_rp_ud(struct msgb *msg, struct gsm_trans *trans,
+			  struct gsm411_rp_hdr *rph,
+			  u_int8_t src_len, u_int8_t *src,
+			  u_int8_t dst_len, u_int8_t *dst,
+			  u_int8_t tpdu_len, u_int8_t *tpdu)
+{
+	int rc = 0;
+
+	if (src_len && src)
+		LOGP(DSMS, LOGL_ERROR, "RP-DATA (MO) with SRC ?!?\n");
+
+	if (!dst_len || !dst || !tpdu_len || !tpdu) {
+		LOGP(DSMS, LOGL_ERROR,
+			"RP-DATA (MO) without DST or TPDU ?!?\n");
+		gsm411_send_rp_error(trans, rph->msg_ref,
+				     GSM411_RP_CAUSE_INV_MAND_INF);
+		return -EIO;
+	}
+	msg->l4h = tpdu;
+
+	DEBUGP(DSMS, "DST(%u,%s)\n", dst_len, hexdump(dst, dst_len));
+
+	rc = gsm340_rx_tpdu(trans->conn, msg);
+	if (rc == 0)
+		return gsm411_send_rp_ack(trans, rph->msg_ref);
+	else if (rc > 0)
+		return gsm411_send_rp_error(trans, rph->msg_ref, rc);
+	else
+		return rc;
+}
+
+/* Receive a 04.11 RP-DATA message in accordance with Section 7.3.1.2 */
+static int gsm411_rx_rp_data(struct msgb *msg, struct gsm_trans *trans,
+			     struct gsm411_rp_hdr *rph)
+{
+	u_int8_t src_len, dst_len, rpud_len;
+	u_int8_t *src = NULL, *dst = NULL , *rp_ud = NULL;
+
+	/* in the MO case, this should always be zero length */
+	src_len = rph->data[0];
+	if (src_len)
+		src = &rph->data[1];
+
+	dst_len = rph->data[1+src_len];
+	if (dst_len)
+		dst = &rph->data[1+src_len+1];
+
+	rpud_len = rph->data[1+src_len+1+dst_len];
+	if (rpud_len)
+		rp_ud = &rph->data[1+src_len+1+dst_len+1];
+
+	DEBUGP(DSMS, "RX_RP-DATA: src_len=%u, dst_len=%u ud_len=%u\n",
+		src_len, dst_len, rpud_len);
+	return gsm411_rx_rp_ud(msg, trans, rph, src_len, src, dst_len, dst,
+				rpud_len, rp_ud);
+}
+
+/* Receive a 04.11 RP-ACK message (response to RP-DATA from us) */
+static int gsm411_rx_rp_ack(struct msgb *msg, struct gsm_trans *trans,
+			    struct gsm411_rp_hdr *rph)
+{
+	struct gsm_sms *sms = trans->sms.sms;
+
+	/* Acnkowledgement to MT RP_DATA, i.e. the MS confirms it
+	 * successfully received a SMS.  We can now safely mark it as
+	 * transmitted */
+
+	if (!trans->sms.is_mt) {
+		LOGP(DSMS, LOGL_ERROR, "RX RP-ACK on a MO transfer ?\n");
+		return gsm411_send_rp_error(trans, rph->msg_ref,
+					    GSM411_RP_CAUSE_MSG_INCOMP_STATE);
+	}
+
+	if (!sms) {
+		LOGP(DSMS, LOGL_ERROR, "RX RP-ACK but no sms in transaction?!?\n");
+		return gsm411_send_rp_error(trans, rph->msg_ref,
+					    GSM411_RP_CAUSE_PROTOCOL_ERR);
+	}
+
+	/* mark this SMS as sent in database */
+	db_sms_mark_sent(sms);
+
+	send_signal(S_SMS_DELIVERED, trans, sms, 0);
+
+	sms_free(sms);
+	trans->sms.sms = NULL;
+
+	/* check for more messages for this subscriber */
+	sms = db_sms_get_unsent_for_subscr(trans->subscr);
+	if (sms)
+		gsm411_send_sms(trans->conn, sms);
+	else
+		gsm411_release_conn(trans->conn);
+
+	/* free the transaction here */
+	trans_free(trans);
+	return 0;
+}
+
+static int gsm411_rx_rp_error(struct msgb *msg, struct gsm_trans *trans,
+			      struct gsm411_rp_hdr *rph)
+{
+	struct gsm_network *net = trans->conn->bts->network;
+	struct gsm_sms *sms = trans->sms.sms;
+	u_int8_t cause_len = rph->data[0];
+	u_int8_t cause = rph->data[1];
+
+	/* Error in response to MT RP_DATA, i.e. the MS did not
+	 * successfully receive the SMS.  We need to investigate
+	 * the cause and take action depending on it */
+
+	LOGP(DSMS, LOGL_NOTICE, "%s: RX SMS RP-ERROR, cause %d:%d (%s)\n",
+	     subscr_name(trans->conn->subscr), cause_len, cause,
+	     get_value_string(rp_cause_strs, cause));
+
+	if (!trans->sms.is_mt) {
+		LOGP(DSMS, LOGL_ERROR, "RX RP-ERR on a MO transfer ?\n");
+#if 0
+		return gsm411_send_rp_error(trans, rph->msg_ref,
+					    GSM411_RP_CAUSE_MSG_INCOMP_STATE);
+#endif
+	}
+
+	if (!sms) {
+		LOGP(DSMS, LOGL_ERROR,
+			"RX RP-ERR, but no sms in transaction?!?\n");
+		return -EINVAL;
+#if 0
+		return gsm411_send_rp_error(trans, rph->msg_ref,
+					    GSM411_RP_CAUSE_PROTOCOL_ERR);
+#endif
+	}
+
+	if (cause == GSM411_RP_CAUSE_MT_MEM_EXCEEDED) {
+		/* MS has not enough memory to store the message.  We need
+		 * to store this in our database and wait for a SMMA message */
+		/* FIXME */
+		send_signal(S_SMS_MEM_EXCEEDED, trans, sms, 0);
+		counter_inc(net->stats.sms.rp_err_mem);
+	} else {
+		send_signal(S_SMS_UNKNOWN_ERROR, trans, sms, 0);
+		counter_inc(net->stats.sms.rp_err_other);
+	}
+
+	sms_free(sms);
+	trans->sms.sms = NULL;
+
+	return 0;
+}
+
+static int gsm411_rx_rp_smma(struct msgb *msg, struct gsm_trans *trans,
+			     struct gsm411_rp_hdr *rph)
+{
+	struct gsm_sms *sms;
+	int rc;
+
+	rc = gsm411_send_rp_ack(trans, rph->msg_ref);
+	trans->sms.rp_state = GSM411_RPS_IDLE;
+
+	/* MS tells us that it has memory for more SMS, we need
+	 * to check if we have any pending messages for it and then
+	 * transfer those */
+	send_signal(S_SMS_SMMA, trans, NULL, 0);
+
+	/* check for more messages for this subscriber */
+	sms = db_sms_get_unsent_for_subscr(trans->subscr);
+	if (sms)
+		gsm411_send_sms(trans->conn, sms);
+	else
+		gsm411_release_conn(trans->conn);
+
+	return rc;
+}
+
+static int gsm411_rx_cp_data(struct msgb *msg, struct gsm48_hdr *gh,
+			     struct gsm_trans *trans)
+{
+	struct gsm411_rp_hdr *rp_data = (struct gsm411_rp_hdr*)&gh->data;
+	u_int8_t msg_type =  rp_data->msg_type & 0x07;
+	int rc = 0;
+
+	switch (msg_type) {
+	case GSM411_MT_RP_DATA_MO:
+		DEBUGP(DSMS, "RX SMS RP-DATA (MO)\n");
+		/* start TR2N and enter 'wait to send RP-ACK state' */
+		trans->sms.rp_state = GSM411_RPS_WAIT_TO_TX_RP_ACK;
+		rc = gsm411_rx_rp_data(msg, trans, rp_data);
+		break;
+	case GSM411_MT_RP_ACK_MO:
+		DEBUGP(DSMS,"RX SMS RP-ACK (MO)\n");
+		rc = gsm411_rx_rp_ack(msg, trans, rp_data);
+		break;
+	case GSM411_MT_RP_SMMA_MO:
+		DEBUGP(DSMS, "RX SMS RP-SMMA\n");
+		/* start TR2N and enter 'wait to send RP-ACK state' */
+		trans->sms.rp_state = GSM411_RPS_WAIT_TO_TX_RP_ACK;
+		rc = gsm411_rx_rp_smma(msg, trans, rp_data);
+		break;
+	case GSM411_MT_RP_ERROR_MO:
+		rc = gsm411_rx_rp_error(msg, trans, rp_data);
+		break;
+	default:
+		LOGP(DSMS, LOGL_NOTICE, "Invalid RP type 0x%02x\n", msg_type);
+		rc = gsm411_send_rp_error(trans, rp_data->msg_ref,
+					  GSM411_RP_CAUSE_MSGTYPE_NOTEXIST);
+		break;
+	}
+
+	return rc;
+}
+
+/* send CP-ACK to given transaction */
+static int gsm411_tx_cp_ack(struct gsm_trans *trans)
+{
+	struct msgb *msg = gsm411_msgb_alloc();
+	int rc;
+
+	rc = gsm411_cp_sendmsg(msg, trans, GSM411_MT_CP_ACK);
+
+	if (trans->sms.is_mt) {
+		/* If this is a MT SMS DELIVER, we can clear transaction here */
+		trans->sms.cp_state = GSM411_CPS_IDLE;
+		//trans_free(trans);
+	}
+
+	return rc;
+}
+
+static int gsm411_tx_cp_error(struct gsm_trans *trans, u_int8_t cause)
+{
+	struct msgb *msg = gsm411_msgb_alloc();
+	u_int8_t *causep;
+
+	LOGP(DSMS, LOGL_NOTICE, "TX CP-ERROR, cause %d (%s)\n", cause,
+		get_value_string(cp_cause_strs, cause));
+
+	causep = msgb_put(msg, 1);
+	*causep = cause;
+
+	return gsm411_cp_sendmsg(msg, trans, GSM411_MT_CP_ERROR);
+}
+
+/* Entry point for incoming GSM48_PDISC_SMS from abis_rsl.c */
+int gsm0411_rcv_sms(struct gsm_subscriber_connection *conn,
+		    struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t msg_type = gh->msg_type;
+	u_int8_t transaction_id = ((gh->proto_discr >> 4) ^ 0x8); /* flip */
+	struct gsm_trans *trans;
+	int rc = 0;
+
+	if (!conn->subscr)
+		return -EIO;
+		/* FIXME: send some error message */
+
+	DEBUGP(DSMS, "trans_id=%x ", transaction_id);
+	trans = trans_find_by_id(conn->subscr, GSM48_PDISC_SMS,
+				 transaction_id);
+	if (!trans) {
+		DEBUGPC(DSMS, "(new) ");
+		trans = trans_alloc(conn->subscr, GSM48_PDISC_SMS,
+				    transaction_id, new_callref++);
+		if (!trans) {
+			DEBUGPC(DSMS, "No memory for trans\n");
+			/* FIXME: send some error message */
+			return -ENOMEM;
+		}
+		trans->sms.cp_state = GSM411_CPS_IDLE;
+		trans->sms.rp_state = GSM411_RPS_IDLE;
+		trans->sms.is_mt = 0;
+		trans->sms.link_id = UM_SAPI_SMS;
+
+		trans->conn = conn;
+	}
+
+	switch(msg_type) {
+	case GSM411_MT_CP_DATA:
+		DEBUGPC(DSMS, "RX SMS CP-DATA\n");
+
+		/* 5.4: For MO, if a CP-DATA is received for a new
+		 * transaction, equals reception of an implicit
+		 * last CP-ACK for previous transaction */
+		if (trans->sms.cp_state == GSM411_CPS_IDLE) {
+			int i;
+			struct gsm_trans *ptrans;
+
+			/* Scan through all remote initiated transactions */
+			for (i=8; i<15; i++) {
+				if (i == transaction_id)
+					continue;
+
+				ptrans = trans_find_by_id(conn->subscr,
+				                          GSM48_PDISC_SMS, i);
+				if (!ptrans)
+					continue;
+
+				DEBUGP(DSMS, "Implicit CP-ACK for trans_id=%x\n", i);
+
+				/* Finish it for good */
+				bsc_del_timer(&ptrans->sms.cp_timer);
+				ptrans->sms.cp_state = GSM411_CPS_IDLE;
+				trans_free(ptrans);
+			}
+		}
+
+		/* 5.2.3.1.3: MO state exists when SMC has received
+		 * CP-DATA, including sending of the assoc. CP-ACK */
+		/* 5.2.3.2.4: MT state exists when SMC has received
+		 * CP-DATA, including sending of the assoc. CP-ACK */
+		trans->sms.cp_state = GSM411_CPS_MM_ESTABLISHED;
+
+		/* SMC instance acknowledges the CP-DATA frame */
+		gsm411_tx_cp_ack(trans);
+		
+		rc = gsm411_rx_cp_data(msg, gh, trans);
+#if 0
+		/* Send CP-ACK or CP-ERORR in response */
+		if (rc < 0) {
+			rc = gsm411_tx_cp_error(trans, GSM411_CP_CAUSE_NET_FAIL);
+		} else
+			rc = gsm411_tx_cp_ack(trans);
+#endif
+		break;
+	case GSM411_MT_CP_ACK:
+		/* previous CP-DATA in this transaction was confirmed */
+		DEBUGPC(DSMS, "RX SMS CP-ACK\n");
+		/* 5.2.3.1.3: MO state exists when SMC has received CP-ACK */
+		/* 5.2.3.2.4: MT state exists when SMC has received CP-ACK */
+		trans->sms.cp_state = GSM411_CPS_MM_ESTABLISHED;
+		/* Stop TC1* after CP-ACK has been received */
+		bsc_del_timer(&trans->sms.cp_timer);
+
+		if (!trans->sms.is_mt) {
+			/* FIXME: we have sent one CP-DATA, which was now
+			 * acknowledged.  Check if we want to transfer more,
+			 * i.e. multi-part message */
+			trans->sms.cp_state = GSM411_CPS_IDLE;
+			trans_free(trans);
+		}
+		break;
+	case GSM411_MT_CP_ERROR:
+		DEBUGPC(DSMS, "RX SMS CP-ERROR, cause %d (%s)\n", gh->data[0],
+			get_value_string(cp_cause_strs, gh->data[0]));
+		bsc_del_timer(&trans->sms.cp_timer);
+		trans->sms.cp_state = GSM411_CPS_IDLE;
+		trans_free(trans);
+		break;
+	default:
+		DEBUGPC(DSMS, "RX Unimplemented CP msg_type: 0x%02x\n", msg_type);
+		rc = gsm411_tx_cp_error(trans, GSM411_CP_CAUSE_MSGTYPE_NOTEXIST);
+		trans->sms.cp_state = GSM411_CPS_IDLE;
+		trans_free(trans);
+		break;
+	}
+
+	return rc;
+}
+
+/* Take a SMS in gsm_sms structure and send it through an already
+ * existing lchan. We also assume that the caller ensured this lchan already
+ * has a SAPI3 RLL connection! */
+int gsm411_send_sms(struct gsm_subscriber_connection *conn, struct gsm_sms *sms)
+{
+	struct msgb *msg = gsm411_msgb_alloc();
+	struct gsm_trans *trans;
+	u_int8_t *data, *rp_ud_len;
+	u_int8_t msg_ref = 42;
+	int transaction_id;
+	int rc;
+
+	transaction_id = trans_assign_trans_id(conn->subscr, GSM48_PDISC_SMS, 0);
+	if (transaction_id == -1) {
+		LOGP(DSMS, LOGL_ERROR, "No available transaction ids\n");
+		send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, 0);
+		sms_free(sms);
+		return -EBUSY;
+	}
+
+	DEBUGP(DSMS, "send_sms_lchan()\n");
+
+	/* FIXME: allocate transaction with message reference */
+	trans = trans_alloc(conn->subscr, GSM48_PDISC_SMS,
+			    transaction_id, new_callref++);
+	if (!trans) {
+		LOGP(DSMS, LOGL_ERROR, "No memory for trans\n");
+		send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, 0);
+		sms_free(sms);
+		/* FIXME: send some error message */
+		return -ENOMEM;
+	}
+	trans->sms.cp_state = GSM411_CPS_IDLE;
+	trans->sms.rp_state = GSM411_RPS_IDLE;
+	trans->sms.is_mt = 1;
+	trans->sms.sms = sms;
+	trans->sms.link_id = UM_SAPI_SMS;	/* FIXME: main or SACCH ? */
+
+	trans->conn = conn;
+
+	/* Hardcode SMSC Originating Address for now */
+	data = (u_int8_t *)msgb_put(msg, 8);
+	data[0] = 0x07;	/* originator length == 7 */
+	data[1] = 0x91; /* type of number: international, ISDN */
+	data[2] = 0x44; /* 447785016005 */
+	data[3] = 0x77;
+	data[4] = 0x58;
+	data[5] = 0x10;
+	data[6] = 0x06;
+	data[7] = 0x50;
+
+	/* Hardcoded Destination Address */
+	data = (u_int8_t *)msgb_put(msg, 1);
+	data[0] = 0;	/* destination length == 0 */
+
+	/* obtain a pointer for the rp_ud_len, so we can fill it later */
+	rp_ud_len = (u_int8_t *)msgb_put(msg, 1);
+
+	/* generate the 03.40 TPDU */
+	rc = gsm340_gen_tpdu(msg, sms);
+	if (rc < 0) {
+		send_signal(S_SMS_UNKNOWN_ERROR, trans, sms, 0);
+		trans_free(trans);
+		sms_free(sms);
+		msgb_free(msg);
+		return rc;
+	}
+
+	*rp_ud_len = rc;
+
+	DEBUGP(DSMS, "TX: SMS DELIVER\n");
+
+	counter_inc(conn->bts->network->stats.sms.delivered);
+	db_sms_inc_deliver_attempts(trans->sms.sms);
+
+	return gsm411_rp_sendmsg(msg, trans, GSM411_MT_RP_DATA_MT, msg_ref);
+	/* FIXME: enter 'wait for RP-ACK' state, start TR1N */
+}
+
+/* paging callback. Here we get called if paging a subscriber has
+ * succeeded or failed. */
+static int paging_cb_send_sms(unsigned int hooknum, unsigned int event,
+			      struct msgb *msg, void *_conn, void *_sms)
+{
+	struct gsm_subscriber_connection *conn = _conn;
+	struct gsm_sms *sms = _sms;
+	int rc = 0;
+
+	DEBUGP(DSMS, "paging_cb_send_sms(hooknum=%u, event=%u, msg=%p,"
+		"conn=%p, sms=%p/id: %llu)\n", hooknum, event, msg, conn, sms, sms->id);
+
+	if (hooknum != GSM_HOOK_RR_PAGING)
+		return -EINVAL;
+
+	switch (event) {
+	case GSM_PAGING_SUCCEEDED:
+		gsm411_send_sms(conn, sms);
+		break;
+	case GSM_PAGING_EXPIRED:
+	case GSM_PAGING_OOM:
+	case GSM_PAGING_BUSY:
+		send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, event);
+		sms_free(sms);
+		rc = -ETIMEDOUT;
+		break;
+	default:
+		LOGP(DSMS, LOGL_ERROR, "Unhandled paging event: %d\n", event);
+	}
+
+	return rc;
+}
+
+/* high-level function to send a SMS to a given subscriber. The function
+ * will take care of paging the subscriber, establishing the RLL SAPI3
+ * connection, etc. */
+int gsm411_send_sms_subscr(struct gsm_subscriber *subscr,
+			   struct gsm_sms *sms)
+{
+	struct gsm_subscriber_connection *conn;
+
+	/* check if we already have an open lchan to the subscriber.
+	 * if yes, send the SMS this way */
+	conn = connection_for_subscr(subscr);
+	if (conn) {
+		return gsm411_send_sms(conn, sms);
+	}
+
+	/* if not, we have to start paging */
+	subscr_get_channel(subscr, RSL_CHANNEED_SDCCH, paging_cb_send_sms, sms);
+	return 0;
+}
+
+void _gsm411_sms_trans_free(struct gsm_trans *trans)
+{
+	if (trans->sms.sms) {
+		LOGP(DSMS, LOGL_ERROR, "Transaction contains SMS.\n");
+		send_signal(S_SMS_UNKNOWN_ERROR, trans, trans->sms.sms, 0);
+		sms_free(trans->sms.sms);
+		trans->sms.sms = NULL;
+	}
+
+	bsc_del_timer(&trans->sms.cp_timer);
+}
+
+void gsm411_sapi_n_reject(struct gsm_subscriber_connection *conn)
+{
+	struct gsm_subscriber *subscr;
+	struct gsm_network *net;
+	struct gsm_trans *trans, *tmp;
+
+	subscr = subscr_get(conn->subscr);
+	net = conn->bts->network;
+
+	llist_for_each_entry_safe(trans, tmp, &net->trans_list, entry)
+		if (trans->conn == conn) {
+			struct gsm_sms *sms = trans->sms.sms;
+			if (!sms) {
+				LOGP(DSMS, LOGL_ERROR, "SAPI Reject but no SMS.\n");
+				continue;
+			}
+
+			send_signal(S_SMS_UNKNOWN_ERROR, trans, sms, 0);
+			sms_free(sms);
+			trans->sms.sms = NULL;
+			trans_free(trans);
+		}
+
+	subscr_put_channel(subscr);
+	subscr_put(subscr);
+}
+
diff --git a/openbsc/src/msc/gsm_04_80.c b/openbsc/src/msc/gsm_04_80.c
new file mode 100644
index 0000000..494c319
--- /dev/null
+++ b/openbsc/src/msc/gsm_04_80.c
@@ -0,0 +1,175 @@
+/* GSM Mobile Radio Interface Layer 3 messages on the A-bis interface
+ * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009, 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 by Mike Haben <michael.haben@btinternet.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/bsc_api.h>
+
+#include <osmocore/gsm0480.h>
+#include <osmocore/gsm_utils.h>
+#include <osmocore/msgb.h>
+#include <osmocore/tlv.h>
+
+static inline unsigned char *msgb_wrap_with_TL(struct msgb *msgb, u_int8_t tag)
+{
+	uint8_t *data = msgb_push(msgb, 2);
+
+	data[0] = tag;
+	data[1] = msgb->len - 2;
+	return data;
+}
+
+static inline unsigned char *msgb_push_TLV1(struct msgb *msgb, u_int8_t tag,
+					    u_int8_t value)
+{
+	uint8_t *data = msgb_push(msgb, 3);
+
+	data[0] = tag;
+	data[1] = 1;
+	data[2] = value;
+	return data;
+}
+
+
+/* Send response to a mobile-originated ProcessUnstructuredSS-Request */
+int gsm0480_send_ussd_response(struct gsm_subscriber_connection *conn,
+			       const struct msgb *in_msg, const char *response_text,
+			       const struct ussd_request *req)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+	u_int8_t *ptr8;
+	int response_len;
+
+	/* First put the payload text into the message */
+	ptr8 = msgb_put(msg, 0);
+	response_len = gsm_7bit_encode(ptr8, response_text);
+	msgb_put(msg, response_len);
+
+	/* Then wrap it as an Octet String */
+	msgb_wrap_with_TL(msg, ASN1_OCTET_STRING_TAG);
+
+	/* Pre-pend the DCS octet string */
+	msgb_push_TLV1(msg, ASN1_OCTET_STRING_TAG, 0x0F);
+
+	/* Then wrap these as a Sequence */
+	msgb_wrap_with_TL(msg, GSM_0480_SEQUENCE_TAG);
+
+	/* Pre-pend the operation code */
+	msgb_push_TLV1(msg, GSM0480_OPERATION_CODE,
+			GSM0480_OP_CODE_PROCESS_USS_REQ);
+
+	/* Wrap the operation code and IA5 string as a sequence */
+	msgb_wrap_with_TL(msg, GSM_0480_SEQUENCE_TAG);
+
+	/* Pre-pend the invoke ID */
+	msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id);
+
+	/* Wrap this up as a Return Result component */
+	msgb_wrap_with_TL(msg, GSM0480_CTYPE_RETURN_RESULT);
+
+	/* Wrap the component in a Facility message */
+	msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY);
+
+	/* And finally pre-pend the L3 header */
+	gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_NC_SS | req->transaction_id
+					| (1<<7);  /* TI direction = 1 */
+	gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE;
+
+	return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+int gsm0480_send_ussd_reject(struct gsm_subscriber_connection *conn,
+			     const struct msgb *in_msg,
+			     const struct ussd_request *req)
+{
+	struct msgb *msg = gsm48_msgb_alloc();
+	struct gsm48_hdr *gh;
+
+	/* First insert the problem code */
+	msgb_push_TLV1(msg, GSM_0480_PROBLEM_CODE_TAG_GENERAL,
+			GSM_0480_GEN_PROB_CODE_UNRECOGNISED);
+
+	/* Before it insert the invoke ID */
+	msgb_push_TLV1(msg, GSM0480_COMPIDTAG_INVOKE_ID, req->invoke_id);
+
+	/* Wrap this up as a Reject component */
+	msgb_wrap_with_TL(msg, GSM0480_CTYPE_REJECT);
+
+	/* Wrap the component in a Facility message */
+	msgb_wrap_with_TL(msg, GSM0480_IE_FACILITY);
+
+	/* And finally pre-pend the L3 header */
+	gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_NC_SS;
+	gh->proto_discr |= req->transaction_id | (1<<7);  /* TI direction = 1 */
+	gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE;
+
+	return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+int gsm0480_send_ussdNotify(struct gsm_subscriber_connection *conn, int level, const char *text)
+{
+	struct gsm48_hdr *gh;
+	struct msgb *msg;
+
+	msg = gsm0480_create_unstructuredSS_Notify(level, text);
+	if (!msg)
+		return -1;
+
+	gsm0480_wrap_invoke(msg, GSM0480_OP_CODE_USS_NOTIFY, 0);
+	gsm0480_wrap_facility(msg);
+
+	/* And finally pre-pend the L3 header */
+	gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_NC_SS;
+	gh->msg_type = GSM0480_MTYPE_REGISTER;
+
+	return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
+
+int gsm0480_send_releaseComplete(struct gsm_subscriber_connection *conn)
+{
+	struct gsm48_hdr *gh;
+	struct msgb *msg;
+
+	msg = gsm48_msgb_alloc();
+	if (!msg)
+		return -1;
+
+	gh = (struct gsm48_hdr *) msgb_push(msg, sizeof(*gh));
+	gh->proto_discr = GSM48_PDISC_NC_SS;
+	gh->msg_type = GSM0480_MTYPE_RELEASE_COMPLETE;
+
+	return gsm0808_submit_dtap(conn, msg, 0, 0);
+}
diff --git a/openbsc/src/msc/gsm_subscriber.c b/openbsc/src/msc/gsm_subscriber.c
new file mode 100644
index 0000000..db61f25
--- /dev/null
+++ b/openbsc/src/msc/gsm_subscriber.c
@@ -0,0 +1,410 @@
+/* The concept of a subscriber for the MSC, roughly HLR/VLR functionality */
+
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <osmocore/talloc.h>
+
+#include <osmocom/vty/vty.h>
+
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/debug.h>
+#include <openbsc/paging.h>
+#include <openbsc/signal.h>
+#include <openbsc/db.h>
+
+void *tall_sub_req_ctx;
+
+extern struct llist_head *subscr_bsc_active_subscriber(void);
+
+int gsm48_secure_channel(struct gsm_subscriber_connection *conn, int key_seq,
+                         gsm_cbfn *cb, void *cb_data);
+
+
+/*
+ * Struct for pending channel requests. This is managed in the
+ * llist_head requests of each subscriber. The reference counting
+ * should work in such a way that a subscriber with a pending request
+ * remains in memory.
+ */
+struct subscr_request {
+	struct llist_head entry;
+
+	/* back reference */
+	struct gsm_subscriber *subscr;
+
+	/* the requested channel type */
+	int channel_type;
+
+	/* what did we do */
+	int state;
+
+	/* the callback data */
+	gsm_cbfn *cbfn;
+	void *param;
+};
+
+enum {
+	REQ_STATE_INITIAL,
+	REQ_STATE_QUEUED,
+	REQ_STATE_PAGED,
+	REQ_STATE_FAILED_START,
+	REQ_STATE_DISPATCHED,
+};
+
+/*
+ * We got the channel assigned and can now hand this channel
+ * over to one of our callbacks.
+ */
+static int subscr_paging_dispatch(unsigned int hooknum, unsigned int event,
+                                  struct msgb *msg, void *data, void *param)
+{
+	struct subscr_request *request;
+	struct gsm_subscriber_connection *conn = data;
+	struct gsm_subscriber *subscr = param;
+	struct paging_signal_data sig_data;
+
+	/* There is no request anymore... */
+	if (llist_empty(&subscr->requests))
+		return -1;
+
+	/* Dispatch signal */
+	sig_data.subscr = subscr;
+	sig_data.bts	= conn ? conn->bts : NULL;
+	sig_data.conn	= conn;
+	sig_data.paging_result = event;
+	dispatch_signal(
+		SS_PAGING,
+		event == GSM_PAGING_SUCCEEDED ?
+			S_PAGING_SUCCEEDED : S_PAGING_EXPIRED,
+		&sig_data
+	);
+
+	/*
+	 * FIXME: What to do with paging requests coming during
+	 * this callback? We must be sure to not start paging when
+	 * we have an active connection to a subscriber and to make
+	 * the subscr_put_channel work as required...
+	 */
+	request = (struct subscr_request *)subscr->requests.next;
+	request->state = REQ_STATE_DISPATCHED;
+	llist_del(&request->entry);
+	subscr->in_callback = 1;
+	request->cbfn(hooknum, event, msg, data, request->param);
+	subscr->in_callback = 0;
+
+	if (event != GSM_PAGING_SUCCEEDED) {
+		/*
+		 *  This is a workaround for a bigger issue. We have
+		 *  issued paging that might involve multiple BTSes
+		 *  and one of them have failed now. We will stop the
+		 *  other paging requests as well as the next timeout
+		 *  would work on the next paging request and the queue
+		 *  will do bad things. This should be fixed by counting
+		 *  the outstanding results.
+		 */
+		paging_request_stop(NULL, subscr, NULL, NULL);
+		subscr_put_channel(subscr);
+	}
+
+	subscr_put(subscr);
+	talloc_free(request);
+	return 0;
+}
+
+static int subscr_paging_sec_cb(unsigned int hooknum, unsigned int event,
+                                struct msgb *msg, void *data, void *param)
+{
+	int rc;
+
+	switch (event) {
+		case GSM_SECURITY_AUTH_FAILED:
+			/* Dispatch as paging failure */
+			rc = subscr_paging_dispatch(
+				GSM_HOOK_RR_PAGING, GSM_PAGING_EXPIRED,
+				msg, data, param);
+			break;
+
+		case GSM_SECURITY_NOAVAIL:
+		case GSM_SECURITY_SUCCEEDED:
+			/* Dispatch as paging failure */
+			rc = subscr_paging_dispatch(
+				GSM_HOOK_RR_PAGING, GSM_PAGING_SUCCEEDED,
+				msg, data, param);
+			break;
+
+		default:
+			rc = -EINVAL;
+	}
+
+	return rc;
+}
+
+static int subscr_paging_cb(unsigned int hooknum, unsigned int event,
+                            struct msgb *msg, void *data, void *param)
+{
+	struct gsm_subscriber_connection *conn = data;
+	struct gsm48_hdr *gh;
+	struct gsm48_pag_resp *pr;
+
+	/* Other cases mean problem, dispatch direclty */
+	if (event != GSM_PAGING_SUCCEEDED)
+		return subscr_paging_dispatch(hooknum, event, msg, data, param);
+
+	/* Get paging response */
+	gh = msgb_l3(msg);
+	pr = (struct gsm48_pag_resp *)gh->data;
+
+	/* We _really_ have a channel, secure it now ! */
+	return gsm48_secure_channel(conn, pr->key_seq, subscr_paging_sec_cb, param);
+}
+
+
+static void subscr_send_paging_request(struct gsm_subscriber *subscr)
+{
+	struct subscr_request *request;
+	int rc;
+
+	assert(!llist_empty(&subscr->requests));
+
+	request = (struct subscr_request *)subscr->requests.next;
+	request->state = REQ_STATE_PAGED;
+	rc = paging_request(subscr->net, subscr, request->channel_type,
+			    subscr_paging_cb, subscr);
+
+	/* paging failed, quit now */
+	if (rc <= 0) {
+		request->state = REQ_STATE_FAILED_START;
+		subscr_paging_cb(GSM_HOOK_RR_PAGING, GSM_PAGING_BUSY,
+				 NULL, NULL, subscr);
+	}
+}
+
+void subscr_get_channel(struct gsm_subscriber *subscr,
+			int type, gsm_cbfn *cbfn, void *param)
+{
+	struct subscr_request *request;
+
+	request = talloc(tall_sub_req_ctx, struct subscr_request);
+	if (!request) {
+		if (cbfn)
+			cbfn(GSM_HOOK_RR_PAGING, GSM_PAGING_OOM,
+				NULL, NULL, param);
+		return;
+	}
+
+	memset(request, 0, sizeof(*request));
+	request->subscr = subscr_get(subscr);
+	request->channel_type = type;
+	request->cbfn = cbfn;
+	request->param = param;
+	request->state = REQ_STATE_INITIAL;
+
+	/*
+	 * FIXME: We might be able to assign more than one
+	 * channel, e.g. voice and SMS submit at the same
+	 * time.
+	 */
+	if (!subscr->in_callback && llist_empty(&subscr->requests)) {
+		/* add to the list, send a request */
+		llist_add_tail(&request->entry, &subscr->requests);
+		subscr_send_paging_request(subscr);
+	} else {
+		/* this will be picked up later, from subscr_put_channel */
+		llist_add_tail(&request->entry, &subscr->requests);
+		request->state = REQ_STATE_QUEUED;
+	}
+}
+
+void subscr_put_channel(struct gsm_subscriber *subscr)
+{
+	/*
+	 * FIXME: Continue with other requests now... by checking
+	 * the gsm_subscriber inside the gsm_lchan. Drop the ref count
+	 * of the lchan after having asked the next requestee to handle
+	 * the channel.
+	 */
+	/*
+	 * FIXME: is the lchan is of a different type we could still
+	 * issue an immediate assignment for another channel and then
+	 * close this one.
+	 */
+	/*
+	 * Currently we will drop the last ref of the lchan which
+	 * will result in a channel release on RSL and we will start
+	 * the paging. This should work most of the time as the MS
+	 * will listen to the paging requests before we timeout
+	 */
+
+	if (subscr && !llist_empty(&subscr->requests))
+		subscr_send_paging_request(subscr);
+}
+
+
+struct gsm_subscriber *subscr_get_by_tmsi(struct gsm_network *net,
+					  u_int32_t tmsi)
+{
+	char tmsi_string[14];
+	struct gsm_subscriber *subscr;
+
+	/* we might have a record in memory already */
+	llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) {
+		if (tmsi == subscr->tmsi)
+			return subscr_get(subscr);
+	}
+
+	sprintf(tmsi_string, "%u", tmsi);
+	return db_get_subscriber(net, GSM_SUBSCRIBER_TMSI, tmsi_string);
+}
+
+struct gsm_subscriber *subscr_get_by_imsi(struct gsm_network *net,
+					  const char *imsi)
+{
+	struct gsm_subscriber *subscr;
+
+	llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) {
+		if (strcmp(subscr->imsi, imsi) == 0)
+			return subscr_get(subscr);
+	}
+
+	return db_get_subscriber(net, GSM_SUBSCRIBER_IMSI, imsi);
+}
+
+struct gsm_subscriber *subscr_get_by_extension(struct gsm_network *net,
+					       const char *ext)
+{
+	struct gsm_subscriber *subscr;
+
+	llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) {
+		if (strcmp(subscr->extension, ext) == 0)
+			return subscr_get(subscr);
+	}
+
+	return db_get_subscriber(net, GSM_SUBSCRIBER_EXTENSION, ext);
+}
+
+struct gsm_subscriber *subscr_get_by_id(struct gsm_network *net,
+					unsigned long long id)
+{
+	struct gsm_subscriber *subscr;
+	char buf[32];
+	sprintf(buf, "%llu", id);
+
+	llist_for_each_entry(subscr, subscr_bsc_active_subscriber(), entry) {
+		if (subscr->id == id)
+			return subscr_get(subscr);
+	}
+
+	return db_get_subscriber(net, GSM_SUBSCRIBER_ID, buf);
+}
+
+
+int subscr_update(struct gsm_subscriber *s, struct gsm_bts *bts, int reason)
+{
+	int rc;
+
+	/* FIXME: Migrate pending requests from one BSC to another */
+	switch (reason) {
+	case GSM_SUBSCRIBER_UPDATE_ATTACHED:
+		s->net = bts->network;
+		/* Indicate "attached to LAC" */
+		s->lac = bts->location_area_code;
+		LOGP(DMM, LOGL_INFO, "Subscriber %s ATTACHED LAC=%u\n",
+			subscr_name(s), s->lac);
+		rc = db_sync_subscriber(s);
+		db_subscriber_update(s);
+		dispatch_signal(SS_SUBSCR, S_SUBSCR_ATTACHED, s);
+		break;
+	case GSM_SUBSCRIBER_UPDATE_DETACHED:
+		/* Only detach if we are currently in this area */
+		if (bts->location_area_code == s->lac)
+			s->lac = GSM_LAC_RESERVED_DETACHED;
+		LOGP(DMM, LOGL_INFO, "Subscriber %s DETACHED\n", subscr_name(s));
+		rc = db_sync_subscriber(s);
+		db_subscriber_update(s);
+		dispatch_signal(SS_SUBSCR, S_SUBSCR_DETACHED, s);
+		break;
+	default:
+		fprintf(stderr, "subscr_update with unknown reason: %d\n",
+			reason);
+		rc = db_sync_subscriber(s);
+		db_subscriber_update(s);
+		break;
+	};
+
+	return rc;
+}
+
+void subscr_update_from_db(struct gsm_subscriber *sub)
+{
+	db_subscriber_update(sub);
+}
+
+int subscr_pending_requests(struct gsm_subscriber *sub)
+{
+	struct subscr_request *req;
+	int pending = 0;
+
+	llist_for_each_entry(req, &sub->requests, entry)
+		pending += 1;
+
+	return pending;
+}
+
+int subscr_pending_clear(struct gsm_subscriber *sub)
+{
+	int deleted = 0;
+	struct subscr_request *req, *tmp;
+
+	llist_for_each_entry_safe(req, tmp, &sub->requests, entry) {
+		subscr_put(req->subscr);
+		llist_del(&req->entry);
+		talloc_free(req);
+		deleted += 1;
+	}
+
+	return deleted;
+}
+
+int subscr_pending_dump(struct gsm_subscriber *sub, struct vty *vty)
+{
+	struct subscr_request *req;
+
+	vty_out(vty, "Pending Requests for Subscriber %llu.%s", sub->id, VTY_NEWLINE);
+	llist_for_each_entry(req, &sub->requests, entry) {
+		vty_out(vty, "Channel type: %d State: %d Sub: %llu.%s",
+			req->channel_type, req->state, req->subscr->id, VTY_NEWLINE);
+	}
+
+	return 0;
+}
+
+int subscr_pending_kick(struct gsm_subscriber *sub)
+{
+	subscr_put_channel(sub);
+	return 0;
+}
diff --git a/openbsc/src/msc/mncc.c b/openbsc/src/msc/mncc.c
new file mode 100644
index 0000000..3630b91
--- /dev/null
+++ b/openbsc/src/msc/mncc.c
@@ -0,0 +1,110 @@
+/* mncc.c - utility routines for the MNCC API between the 04.08
+ *	    message parsing and the actual Call Control logic */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Andreas Eversberg <Andreas.Eversberg@versatel.de>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/debug.h>
+#include <openbsc/mncc.h>
+#include <osmocore/talloc.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/transaction.h>
+#include <openbsc/rtp_proxy.h>
+
+static struct mncc_names {
+	char *name;
+	int value;
+} mncc_names[] = {
+	{"MNCC_SETUP_REQ",	0x0101},
+	{"MNCC_SETUP_IND",	0x0102},
+	{"MNCC_SETUP_RSP",	0x0103},
+	{"MNCC_SETUP_CNF",	0x0104},
+	{"MNCC_SETUP_COMPL_REQ",0x0105},
+	{"MNCC_SETUP_COMPL_IND",0x0106},
+	{"MNCC_CALL_CONF_IND",	0x0107},
+	{"MNCC_CALL_PROC_REQ",	0x0108},
+	{"MNCC_PROGRESS_REQ",	0x0109},
+	{"MNCC_ALERT_REQ",	0x010a},
+	{"MNCC_ALERT_IND",	0x010b},
+	{"MNCC_NOTIFY_REQ",	0x010c},
+	{"MNCC_NOTIFY_IND",	0x010d},
+	{"MNCC_DISC_REQ",	0x010e},
+	{"MNCC_DISC_IND",	0x010f},
+	{"MNCC_REL_REQ",	0x0110},
+	{"MNCC_REL_IND",	0x0111},
+	{"MNCC_REL_CNF",	0x0112},
+	{"MNCC_FACILITY_REQ",	0x0113},
+	{"MNCC_FACILITY_IND",	0x0114},
+	{"MNCC_START_DTMF_IND",	0x0115},
+	{"MNCC_START_DTMF_RSP",	0x0116},
+	{"MNCC_START_DTMF_REJ",	0x0117},
+	{"MNCC_STOP_DTMF_IND",	0x0118},
+	{"MNCC_STOP_DTMF_RSP",	0x0119},
+	{"MNCC_MODIFY_REQ",	0x011a},
+	{"MNCC_MODIFY_IND",	0x011b},
+	{"MNCC_MODIFY_RSP",	0x011c},
+	{"MNCC_MODIFY_CNF",	0x011d},
+	{"MNCC_MODIFY_REJ",	0x011e},
+	{"MNCC_HOLD_IND",	0x011f},
+	{"MNCC_HOLD_CNF",	0x0120},
+	{"MNCC_HOLD_REJ",	0x0121},
+	{"MNCC_RETRIEVE_IND",	0x0122},
+	{"MNCC_RETRIEVE_CNF",	0x0123},
+	{"MNCC_RETRIEVE_REJ",	0x0124},
+	{"MNCC_USERINFO_REQ",	0x0125},
+	{"MNCC_USERINFO_IND",	0x0126},
+	{"MNCC_REJ_REQ",	0x0127},
+	{"MNCC_REJ_IND",	0x0128},
+
+	{"MNCC_BRIDGE",		0x0200},
+	{"MNCC_FRAME_RECV",	0x0201},
+	{"MNCC_FRAME_DROP",	0x0202},
+	{"MNCC_LCHAN_MODIFY",	0x0203},
+
+	{"GSM_TCH_FRAME",	0x0300},
+
+	{NULL, 0} };
+
+char *get_mncc_name(int value)
+{
+	int i;
+
+	for (i = 0; mncc_names[i].name; i++) {
+		if (mncc_names[i].value == value)
+			return mncc_names[i].name;
+	}
+
+	return "MNCC_Unknown";
+}
+
+void mncc_set_cause(struct gsm_mncc *data, int loc, int val)
+{
+	data->fields |= MNCC_F_CAUSE;
+	data->cause.location = loc;
+	data->cause.value = val;
+}
+
diff --git a/openbsc/src/msc/mncc_builtin.c b/openbsc/src/msc/mncc_builtin.c
new file mode 100644
index 0000000..0226b27
--- /dev/null
+++ b/openbsc/src/msc/mncc_builtin.c
@@ -0,0 +1,411 @@
+/* mncc_builtin.c - default, minimal built-in MNCC Application for
+ *		    standalone bsc_hack (netowrk-in-the-box mode) */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Andreas Eversberg <Andreas.Eversberg@versatel.de>
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/debug.h>
+#include <openbsc/mncc.h>
+#include <osmocore/talloc.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/transaction.h>
+#include <openbsc/rtp_proxy.h>
+
+void *tall_call_ctx;
+
+static LLIST_HEAD(call_list);
+
+static u_int32_t new_callref = 0x00000001;
+
+static void free_call(struct gsm_call *call)
+{
+	llist_del(&call->entry);
+	DEBUGP(DMNCC, "(call %x) Call removed.\n", call->callref);
+	talloc_free(call);
+}
+
+
+static struct gsm_call *get_call_ref(u_int32_t callref)
+{
+	struct gsm_call *callt;
+
+	llist_for_each_entry(callt, &call_list, entry) {
+		if (callt->callref == callref)
+			return callt;
+	}
+	return NULL;
+}
+
+
+/* on incoming call, look up database and send setup to remote subscr. */
+static int mncc_setup_ind(struct gsm_call *call, int msg_type,
+			  struct gsm_mncc *setup)
+{
+	struct gsm_mncc mncc;
+	struct gsm_call *remote;
+
+	memset(&mncc, 0, sizeof(struct gsm_mncc));
+	mncc.callref = call->callref;
+
+	/* already have remote call */
+	if (call->remote_ref)
+		return 0;
+	
+	/* transfer mode 1 would be packet mode, which was never specified */
+	if (setup->bearer_cap.mode != 0) {
+		LOGP(DMNCC, LOGL_NOTICE, "(call %x) We don't support "
+			"packet mode\n", call->callref);
+		mncc_set_cause(&mncc, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_BEARER_CA_UNAVAIL);
+		goto out_reject;
+	}
+
+	/* we currently only do speech */
+	if (setup->bearer_cap.transfer != GSM_MNCC_BCAP_SPEECH) {
+		LOGP(DMNCC, LOGL_NOTICE, "(call %x) We only support "
+			"voice calls\n", call->callref);
+		mncc_set_cause(&mncc, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_BEARER_CA_UNAVAIL);
+		goto out_reject;
+	}
+
+	/* create remote call */
+	if (!(remote = talloc(tall_call_ctx, struct gsm_call))) {
+		mncc_set_cause(&mncc, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+		goto out_reject;
+	}
+	llist_add_tail(&remote->entry, &call_list);
+	remote->net = call->net;
+	remote->callref = new_callref++;
+	DEBUGP(DMNCC, "(call %x) Creating new remote instance %x.\n",
+		call->callref, remote->callref);
+
+	/* link remote call */
+	call->remote_ref = remote->callref;
+	remote->remote_ref = call->callref;
+
+	/* modify mode */
+	memset(&mncc, 0, sizeof(struct gsm_mncc));
+	mncc.callref = call->callref;
+	mncc.lchan_mode = GSM48_CMODE_SPEECH_EFR;
+	DEBUGP(DMNCC, "(call %x) Modify channel mode.\n", call->callref);
+	mncc_tx_to_cc(call->net, MNCC_LCHAN_MODIFY, &mncc);
+
+	/* send call proceeding */
+	memset(&mncc, 0, sizeof(struct gsm_mncc));
+	mncc.callref = call->callref;
+	DEBUGP(DMNCC, "(call %x) Accepting call.\n", call->callref);
+	mncc_tx_to_cc(call->net, MNCC_CALL_PROC_REQ, &mncc);
+
+	/* send setup to remote */
+//	setup->fields |= MNCC_F_SIGNAL;
+//	setup->signal = GSM48_SIGNAL_DIALTONE;
+	setup->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Forwarding SETUP to remote.\n", call->callref);
+	return mncc_tx_to_cc(remote->net, MNCC_SETUP_REQ, setup);
+
+out_reject:
+	mncc_tx_to_cc(call->net, MNCC_REJ_REQ, &mncc);
+	free_call(call);
+	return 0;
+}
+
+static int mncc_alert_ind(struct gsm_call *call, int msg_type,
+			  struct gsm_mncc *alert)
+{
+	struct gsm_call *remote;
+
+	/* send alerting to remote */
+	if (!(remote = get_call_ref(call->remote_ref)))
+		return 0;
+	alert->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Forwarding ALERT to remote.\n", call->callref);
+	return mncc_tx_to_cc(remote->net, MNCC_ALERT_REQ, alert);
+}
+
+static int mncc_notify_ind(struct gsm_call *call, int msg_type,
+			   struct gsm_mncc *notify)
+{
+	struct gsm_call *remote;
+
+	/* send notify to remote */
+	if (!(remote = get_call_ref(call->remote_ref)))
+		return 0;
+	notify->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Forwarding NOTIF to remote.\n", call->callref);
+	return mncc_tx_to_cc(remote->net, MNCC_NOTIFY_REQ, notify);
+}
+
+static int mncc_setup_cnf(struct gsm_call *call, int msg_type,
+			  struct gsm_mncc *connect)
+{
+	struct gsm_mncc connect_ack, frame_recv;
+	struct gsm_network *net = call->net;
+	struct gsm_call *remote;
+	u_int32_t refs[2];
+
+	/* acknowledge connect */
+	memset(&connect_ack, 0, sizeof(struct gsm_mncc));
+	connect_ack.callref = call->callref;
+	DEBUGP(DMNCC, "(call %x) Acknowledge SETUP.\n", call->callref);
+	mncc_tx_to_cc(call->net, MNCC_SETUP_COMPL_REQ, &connect_ack);
+
+	/* send connect message to remote */
+	if (!(remote = get_call_ref(call->remote_ref)))
+		return 0;
+	connect->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Sending CONNECT to remote.\n", call->callref);
+	mncc_tx_to_cc(remote->net, MNCC_SETUP_RSP, connect);
+
+	/* bridge tch */
+	refs[0] = call->callref;
+	refs[1] = call->remote_ref;
+	DEBUGP(DMNCC, "(call %x) Bridging with remote.\n", call->callref);
+
+	/* in direct mode, we always have to bridge the channels */
+	if (ipacc_rtp_direct)
+		return mncc_tx_to_cc(call->net, MNCC_BRIDGE, refs);
+
+	/* proxy mode */
+	if (!net->handover.active) {
+		/* in the no-handover case, we can bridge, i.e. use
+		 * the old RTP proxy code */
+		return mncc_tx_to_cc(call->net, MNCC_BRIDGE, refs);
+	} else {
+		/* in case of handover, we need to re-write the RTP
+		 * SSRC, sequence and timestamp values and thus
+		 * need to enable RTP receive for both directions */
+		memset(&frame_recv, 0, sizeof(struct gsm_mncc));
+		frame_recv.callref = call->callref;
+		mncc_tx_to_cc(call->net, MNCC_FRAME_RECV, &frame_recv);
+		frame_recv.callref = call->remote_ref;
+		return mncc_tx_to_cc(call->net, MNCC_FRAME_RECV, &frame_recv);
+	}
+}
+
+static int mncc_disc_ind(struct gsm_call *call, int msg_type,
+			 struct gsm_mncc *disc)
+{
+	struct gsm_call *remote;
+
+	/* send release */
+	DEBUGP(DMNCC, "(call %x) Releasing call with cause %d\n",
+		call->callref, disc->cause.value);
+	mncc_tx_to_cc(call->net, MNCC_REL_REQ, disc);
+
+	/* send disc to remote */
+	if (!(remote = get_call_ref(call->remote_ref))) {
+		return 0;
+	}
+	disc->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Disconnecting remote with cause %d\n",
+		remote->callref, disc->cause.value);
+	return mncc_tx_to_cc(remote->net, MNCC_DISC_REQ, disc);
+}
+
+static int mncc_rel_ind(struct gsm_call *call, int msg_type, struct gsm_mncc *rel)
+{
+	struct gsm_call *remote;
+
+	/* send release to remote */
+	if (!(remote = get_call_ref(call->remote_ref))) {
+		free_call(call);
+		return 0;
+	}
+
+	rel->callref = remote->callref;
+	DEBUGP(DMNCC, "(call %x) Releasing remote with cause %d\n",
+		call->callref, rel->cause.value);
+
+	/*
+	 * Release this side of the call right now. Otherwise we end up
+	 * in this method for the other call and will also try to release
+	 * it and then we will end up with a double free and a crash
+	 */
+	free_call(call);
+	mncc_tx_to_cc(remote->net, MNCC_REL_REQ, rel);
+
+	return 0;
+}
+
+static int mncc_rel_cnf(struct gsm_call *call, int msg_type, struct gsm_mncc *rel)
+{
+	free_call(call);
+	return 0;
+}
+
+/* receiving a TCH/F frame from the BSC code */
+static int mncc_rcv_tchf(struct gsm_call *call, int msg_type,
+			 struct gsm_data_frame *dfr)
+{
+	struct gsm_trans *remote_trans;
+
+	remote_trans = trans_find_by_callref(call->net, call->remote_ref);
+
+	/* this shouldn't really happen */
+	if (!remote_trans || !remote_trans->conn) {
+		LOGP(DMNCC, LOGL_ERROR, "No transaction or transaction without lchan?!?\n");
+		return -EIO;
+	}
+
+	/* RTP socket of remote end has meanwhile died */
+	if (!remote_trans->conn->lchan->abis_ip.rtp_socket)
+		return -EIO;
+
+	return rtp_send_frame(remote_trans->conn->lchan->abis_ip.rtp_socket, dfr);
+}
+
+
+/* Internal MNCC handler input function (from CC -> MNCC -> here) */
+int int_mncc_recv(struct gsm_network *net, struct msgb *msg)
+{
+	void *arg = msgb_data(msg);
+	struct gsm_mncc *data = arg;
+	int msg_type = data->msg_type;
+	int callref;
+	struct gsm_call *call = NULL, *callt;
+	int rc = 0;
+
+	/* Special messages */
+	switch(msg_type) {
+	}
+	
+	/* find callref */
+	callref = data->callref;
+	llist_for_each_entry(callt, &call_list, entry) {
+		if (callt->callref == callref) {
+			call = callt;
+			break;
+		}
+	}
+
+	/* create callref, if setup is received */
+	if (!call) {
+		if (msg_type != MNCC_SETUP_IND)
+			goto out_free; /* drop */
+		/* create call */
+		if (!(call = talloc_zero(tall_call_ctx, struct gsm_call))) {
+			struct gsm_mncc rel;
+			
+			memset(&rel, 0, sizeof(struct gsm_mncc));
+			rel.callref = callref;
+			mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU,
+				       GSM48_CC_CAUSE_RESOURCE_UNAVAIL);
+			mncc_tx_to_cc(net, MNCC_REL_REQ, &rel);
+			goto out_free;
+		}
+		llist_add_tail(&call->entry, &call_list);
+		call->net = net;
+		call->callref = callref;
+		DEBUGP(DMNCC, "(call %x) Call created.\n", call->callref);
+	}
+
+	switch (msg_type) {
+	case GSM_TCHF_FRAME:
+	case GSM_TCHF_FRAME_EFR:
+		break;
+	default:
+		DEBUGP(DMNCC, "(call %x) Received message %s\n", call->callref,
+			get_mncc_name(msg_type));
+		break;
+	}
+
+	switch(msg_type) {
+	case MNCC_SETUP_IND:
+		rc = mncc_setup_ind(call, msg_type, arg);
+		break;
+	case MNCC_SETUP_CNF:
+		rc = mncc_setup_cnf(call, msg_type, arg);
+		break;
+	case MNCC_SETUP_COMPL_IND:
+		break;
+	case MNCC_CALL_CONF_IND:
+		/* we now need to MODIFY the channel */
+		data->lchan_mode = GSM48_CMODE_SPEECH_EFR;
+		mncc_tx_to_cc(call->net, MNCC_LCHAN_MODIFY, data);
+		break;
+	case MNCC_ALERT_IND:
+		rc = mncc_alert_ind(call, msg_type, arg);
+		break;
+	case MNCC_NOTIFY_IND:
+		rc = mncc_notify_ind(call, msg_type, arg);
+		break;
+	case MNCC_DISC_IND:
+		rc = mncc_disc_ind(call, msg_type, arg);
+		break;
+	case MNCC_REL_IND:
+	case MNCC_REJ_IND:
+		rc = mncc_rel_ind(call, msg_type, arg);
+		break;
+	case MNCC_REL_CNF:
+		rc = mncc_rel_cnf(call, msg_type, arg);
+		break;
+	case MNCC_FACILITY_IND:
+		break;
+	case MNCC_START_DTMF_IND:
+		break;
+	case MNCC_STOP_DTMF_IND:
+		break;
+	case MNCC_MODIFY_IND:
+		mncc_set_cause(data, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_SERV_OPT_UNIMPL);
+		DEBUGP(DMNCC, "(call %x) Rejecting MODIFY with cause %d\n",
+			call->callref, data->cause.value);
+		rc = mncc_tx_to_cc(net, MNCC_MODIFY_REJ, data);
+		break;
+	case MNCC_MODIFY_CNF:
+		break;
+	case MNCC_HOLD_IND:
+		mncc_set_cause(data, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_SERV_OPT_UNIMPL);
+		DEBUGP(DMNCC, "(call %x) Rejecting HOLD with cause %d\n",
+			call->callref, data->cause.value);
+		rc = mncc_tx_to_cc(net, MNCC_HOLD_REJ, data);
+		break;
+	case MNCC_RETRIEVE_IND:
+		mncc_set_cause(data, GSM48_CAUSE_LOC_PRN_S_LU,
+				GSM48_CC_CAUSE_SERV_OPT_UNIMPL);
+		DEBUGP(DMNCC, "(call %x) Rejecting RETRIEVE with cause %d\n",
+			call->callref, data->cause.value);
+		rc = mncc_tx_to_cc(net, MNCC_RETRIEVE_REJ, data);
+		break;
+	case GSM_TCHF_FRAME:
+	case GSM_TCHF_FRAME_EFR:
+		rc = mncc_rcv_tchf(call, msg_type, arg);
+		break;
+	default:
+		LOGP(DMNCC, LOGL_NOTICE, "(call %x) Message unhandled\n", callref);
+		break;
+	}
+
+out_free:
+	talloc_free(msg);
+
+	return rc;
+}
diff --git a/openbsc/src/msc/mncc_sock.c b/openbsc/src/msc/mncc_sock.c
new file mode 100644
index 0000000..2eef7c8
--- /dev/null
+++ b/openbsc/src/msc/mncc_sock.c
@@ -0,0 +1,337 @@
+/* mncc_sock.c: Tie the MNCC interface to a unix domain socket */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Andreas Eversberg <Andreas.Eversberg@versatel.de>
+ * 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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <osmocore/talloc.h>
+#include <osmocore/select.h>
+#include <osmocore/protocol/gsm_04_08.h>
+
+#include <openbsc/debug.h>
+#include <openbsc/mncc.h>
+#include <openbsc/gsm_data.h>
+
+struct mncc_sock_state {
+	struct gsm_network *net;
+	struct bsc_fd listen_bfd;	/* fd for listen socket */
+	struct bsc_fd conn_bfd;		/* fd for connection to lcr */
+};
+
+/* FIXME: avoid this */
+static struct mncc_sock_state *g_state;
+
+/* input from CC code into mncc_sock */
+int mncc_sock_from_cc(struct gsm_network *net, struct msgb *msg)
+{
+	struct gsm_mncc *mncc_in = (struct gsm_mncc *) msgb_data(msg);
+	int msg_type = mncc_in->msg_type;
+
+	/* Check if we currently have a MNCC handler connected */
+	if (g_state->conn_bfd.fd < 0) {
+		LOGP(DMNCC, LOGL_ERROR, "mncc_sock receives %s for external CC app "
+			"but socket is gone\n", get_mncc_name(msg_type));
+		if (msg_type != GSM_TCHF_FRAME &&
+		    msg_type != GSM_TCHF_FRAME_EFR) {
+			/* release the request */
+			struct gsm_mncc mncc_out;
+			memset(&mncc_out, 0, sizeof(mncc_out));
+			mncc_out.callref = mncc_in->callref;
+			mncc_set_cause(&mncc_out, GSM48_CAUSE_LOC_PRN_S_LU,
+					GSM48_CC_CAUSE_TEMP_FAILURE);
+			mncc_tx_to_cc(net, MNCC_REL_REQ, &mncc_out);
+		}
+		/* free the original message */
+		msgb_free(msg);
+		return -1;
+	}
+
+	/* FIXME: check for some maximum queue depth? */
+
+	/* Actually enqueue the message and mark socket write need */
+	msgb_enqueue(&net->upqueue, msg);
+	g_state->conn_bfd.when |= BSC_FD_WRITE;
+	return 0;
+}
+
+void mncc_sock_write_pending(void)
+{
+	g_state->conn_bfd.when |= BSC_FD_WRITE;
+}
+
+/* FIXME: move this to libosmocore */
+int osmo_unixsock_listen(struct bsc_fd *bfd, int type, const char *path);
+
+static void mncc_sock_close(struct mncc_sock_state *state)
+{
+	struct bsc_fd *bfd = &state->conn_bfd;
+
+	LOGP(DMNCC, LOGL_NOTICE, "MNCC Socket has LOST connection\n");
+
+	close(bfd->fd);
+	bfd->fd = -1;
+	bsc_unregister_fd(bfd);
+
+	/* re-enable the generation of ACCEPT for new connections */
+	state->listen_bfd.when |= BSC_FD_READ;
+
+	/* FIXME: make sure we don't enqueue anymore */
+
+	/* release all exisitng calls */
+	gsm0408_clear_all_trans(state->net, GSM48_PDISC_CC);
+
+	/* flush the queue */
+	while (!llist_empty(&state->net->upqueue)) {
+		struct msgb *msg = msgb_dequeue(&state->net->upqueue);
+		msgb_free(msg);
+	}
+}
+
+static int mncc_sock_read(struct bsc_fd *bfd)
+{
+	struct mncc_sock_state *state = (struct mncc_sock_state *)bfd->data;
+	struct gsm_mncc *mncc_prim;
+	struct msgb *msg;
+	int rc;
+
+	msg = msgb_alloc(sizeof(*mncc_prim)+256, "mncc_sock_rx");
+	if (!msg)
+		return -ENOMEM;
+
+	mncc_prim = (struct gsm_mncc *) msg->tail;
+
+	rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0);
+	if (rc == 0)
+		goto close;
+
+	if (rc < 0) {
+		if (errno == EAGAIN)
+			return 0;
+		goto close;
+	}
+
+	rc = mncc_tx_to_cc(state->net, mncc_prim->msg_type, mncc_prim);
+
+	/* as we always synchronously process the message in mncc_send() and
+	 * its callbacks, we can free the message here. */
+	msgb_free(msg);
+
+	return rc;
+
+close:
+	msgb_free(msg);
+	mncc_sock_close(state);
+	return -1;
+}
+
+static int mncc_sock_write(struct bsc_fd *bfd)
+{
+	struct mncc_sock_state *state = bfd->data;
+	struct gsm_network *net = state->net;
+	int rc;
+
+	while (!llist_empty(&net->upqueue)) {
+		struct msgb *msg, *msg2;
+		struct gsm_mncc *mncc_prim;
+
+		/* peek at the beginning of the queue */
+		msg = llist_entry(net->upqueue.next, struct msgb, list);
+		mncc_prim = (struct gsm_mncc *)msg->data;
+
+		bfd->when &= ~BSC_FD_WRITE;
+
+		/* try to send it over the socket */
+		rc = write(bfd->fd, msgb_data(msg), msgb_length(msg));
+		if (rc == 0)
+			goto close;
+		if (rc < 0) {
+			if (errno == EAGAIN) {
+				bfd->when |= BSC_FD_WRITE;
+				break;
+			}
+			goto close;
+		}
+		/* _after_ we send it, we can deueue */
+		msg2 = msgb_dequeue(&net->upqueue);
+		assert(msg == msg2);
+		msgb_free(msg);
+	}
+	return 0;
+
+close:
+	mncc_sock_close(state);
+
+	return -1;
+}
+
+static int mncc_sock_cb(struct bsc_fd *bfd, unsigned int flags)
+{
+	int rc = 0;
+
+	if (flags & BSC_FD_READ)
+		rc = mncc_sock_read(bfd);
+	if (rc < 0)
+		return rc;
+
+	if (flags & BSC_FD_WRITE)
+		rc = mncc_sock_write(bfd);
+
+	return rc;
+}
+
+/* accept a new connection */
+static int mncc_sock_accept(struct bsc_fd *bfd, unsigned int flags)
+{
+	struct mncc_sock_state *state = (struct mncc_sock_state *)bfd->data;
+	struct bsc_fd *conn_bfd = &state->conn_bfd;
+	struct sockaddr_un un_addr;
+	socklen_t len;
+	int rc;
+
+	len = sizeof(un_addr);
+	rc = accept(bfd->fd, (struct sockaddr *) &un_addr, &len);
+	if (rc < 0) {
+		LOGP(DMNCC, LOGL_ERROR, "Failed to accept a new connection\n");
+		return -1;
+	}
+
+	if (conn_bfd->fd >= 0) {
+		LOGP(DMNCC, LOGL_NOTICE, "MNCC app connects but we already have "
+			"another active connection ?!?\n");
+		/* We already have one MNCC app connected, this is all we support */
+		state->listen_bfd.when &= ~BSC_FD_READ;
+		close(rc);
+		return 0;
+	}
+
+	conn_bfd->fd = rc;
+	conn_bfd->when = BSC_FD_READ;
+	conn_bfd->cb = mncc_sock_cb;
+	conn_bfd->data = state;
+
+	if (bsc_register_fd(conn_bfd) != 0) {
+		LOGP(DMNCC, LOGL_ERROR, "Failed to register new connection fd\n");
+		close(conn_bfd->fd);
+		conn_bfd->fd = -1;
+		state->listen_bfd.when |= ~BSC_FD_READ;
+		return -1;
+	}
+
+	LOGP(DMNCC, LOGL_NOTICE, "MNCC Socket has connection with external "
+		"call control application\n");
+
+	return 0;
+}
+
+
+int mncc_sock_init(struct gsm_network *net)
+{
+	struct mncc_sock_state *state;
+	struct bsc_fd *bfd;
+	int rc;
+
+	state = talloc_zero(tall_bsc_ctx, struct mncc_sock_state);
+	if (!state)
+		return -ENOMEM;
+
+	state->net = net;
+	state->conn_bfd.fd = -1;
+
+	bfd = &state->listen_bfd;
+
+	rc = osmo_unixsock_listen(bfd, SOCK_SEQPACKET, "/tmp/bsc_mncc");
+	if (rc < 0) {
+		LOGP(DMNCC, LOGL_ERROR, "Could not create unix socket: %s\n",
+			strerror(errno));
+		talloc_free(state);
+		return rc;
+	}
+
+	bfd->when = BSC_FD_READ;
+	bfd->cb = mncc_sock_accept;
+	bfd->data = state;
+
+	rc = bsc_register_fd(bfd);
+	if (rc < 0) {
+		LOGP(DMNCC, LOGL_ERROR, "Could not register listen fd: %d\n", rc);
+		close(bfd->fd);
+		talloc_free(state);
+		return rc;
+	}
+
+	g_state = state;
+
+	return 0;
+}
+
+/* FIXME: move this to libosmocore */
+int osmo_unixsock_listen(struct bsc_fd *bfd, int type, const char *path)
+{
+	struct sockaddr_un local;
+	unsigned int namelen;
+	int rc;
+
+	bfd->fd = socket(AF_UNIX, type, 0);
+
+	if (bfd->fd < 0) {
+		fprintf(stderr, "Failed to create Unix Domain Socket.\n");
+		return -1;
+	}
+
+	local.sun_family = AF_UNIX;
+	strncpy(local.sun_path, path, sizeof(local.sun_path));
+	local.sun_path[sizeof(local.sun_path) - 1] = '\0';
+	unlink(local.sun_path);
+
+	/* we use the same magic that X11 uses in Xtranssock.c for
+	 * calculating the proper length of the sockaddr */
+#if defined(BSD44SOCKETS) || defined(__UNIXWARE__)
+	local.sun_len = strlen(local.sun_path);
+#endif
+#if defined(BSD44SOCKETS) || defined(SUN_LEN)
+	namelen = SUN_LEN(&local);
+#else
+	namelen = strlen(local.sun_path) +
+		  offsetof(struct sockaddr_un, sun_path);
+#endif
+
+	rc = bind(bfd->fd, (struct sockaddr *) &local, namelen);
+	if (rc != 0) {
+		fprintf(stderr, "Failed to bind the unix domain socket. '%s'\n",
+			local.sun_path);
+		return -1;
+	}
+
+	if (listen(bfd->fd, 0) != 0) {
+		fprintf(stderr, "Failed to listen.\n");
+		return -1;
+	}
+
+	return 0;
+}
diff --git a/openbsc/src/msc/osmo_msc.c b/openbsc/src/msc/osmo_msc.c
new file mode 100644
index 0000000..8c86dcc
--- /dev/null
+++ b/openbsc/src/msc/osmo_msc.c
@@ -0,0 +1,104 @@
+/* main MSC management 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 Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/bsc_api.h>
+#include <openbsc/debug.h>
+#include <openbsc/transaction.h>
+
+#include <openbsc/gsm_04_11.h>
+
+static void msc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci)
+{
+	int sapi = dlci & 0x7;
+
+	if (sapi == UM_SAPI_SMS)
+		gsm411_sapi_n_reject(conn);
+}
+
+static int msc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause)
+{
+	gsm0408_clear_request(conn, cause);
+	if (conn->put_channel) {
+		conn->put_channel = 0;
+		subscr_put_channel(conn->subscr);
+	}
+	return 1;
+}
+
+static int msc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg,
+			uint16_t chosen_channel)
+{
+	gsm0408_new_conn(conn);
+	gsm0408_dispatch(conn, msg);
+
+	/* TODO: do better */
+	return BSC_API_CONN_POL_ACCEPT;
+}
+
+static void msc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg)
+{
+	gsm0408_dispatch(conn, msg);
+}
+
+static struct bsc_api msc_handler = {
+	.sapi_n_reject = msc_sapi_n_reject,
+	.clear_request = msc_clear_request,
+	.compl_l3 = msc_compl_l3,
+	.dtap  = msc_dtap,
+};
+
+struct bsc_api *msc_bsc_api() {
+	return &msc_handler;
+}
+
+/* lchan release handling */
+void msc_release_connection(struct gsm_subscriber_connection *conn)
+{
+	struct gsm_trans *trans;
+
+	/* skip when we are in release, e.g. due an error */
+	if (conn->in_release)
+		return;
+
+	/* skip releasing of silent calls as they have no transaction */
+	if (conn->silent_call)
+		return;
+
+	/* check if there is a pending operation */
+	if (conn->loc_operation || conn->sec_operation || conn->anch_operation)
+		return;
+
+	llist_for_each_entry(trans, &conn->bts->network->trans_list, entry) {
+		if (trans->conn == conn)
+			return;
+	}
+
+	/* no more connections, asking to release the channel */
+	conn->in_release = 1;
+	gsm0808_clear(conn);
+	if (conn->put_channel) {
+		conn->put_channel = 0;
+		subscr_put_channel(conn->subscr);
+	}
+	subscr_con_free(conn);
+}
diff --git a/openbsc/src/msc/rrlp.c b/openbsc/src/msc/rrlp.c
new file mode 100644
index 0000000..ae5ca47
--- /dev/null
+++ b/openbsc/src/msc/rrlp.c
@@ -0,0 +1,105 @@
+/* Radio Resource LCS (Location) Protocol, GMS TS 04.31 */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include <sys/types.h>
+
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/signal.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/chan_alloc.h>
+
+/* RRLP msPositionReq, nsBased,
+ *	Accuracy=60, Method=gps, ResponseTime=2, oneSet */
+static const u_int8_t ms_based_pos_req[] = { 0x40, 0x01, 0x78, 0xa8 };
+
+/* RRLP msPositionReq, msBasedPref,
+	Accuracy=60, Method=gpsOrEOTD, ResponseTime=5, multipleSets */
+static const u_int8_t ms_pref_pos_req[]  = { 0x40, 0x02, 0x79, 0x50 };
+
+/* RRLP msPositionReq, msAssistedPref,
+	Accuracy=60, Method=gpsOrEOTD, ResponseTime=5, multipleSets */
+static const u_int8_t ass_pref_pos_req[] = { 0x40, 0x03, 0x79, 0x50 };
+
+static int send_rrlp_req(struct gsm_subscriber_connection *conn)
+{
+	struct gsm_network *net = conn->bts->network;
+	const u_int8_t *req;
+
+	switch (net->rrlp.mode) {
+	case RRLP_MODE_MS_BASED:
+		req = ms_based_pos_req;
+		break;
+	case RRLP_MODE_MS_PREF:
+		req = ms_pref_pos_req;
+		break;
+	case RRLP_MODE_ASS_PREF:
+		req = ass_pref_pos_req;
+		break;
+	case RRLP_MODE_NONE:
+	default:
+		return 0;
+	}
+
+	return gsm48_send_rr_app_info(conn, 0x00,
+				      sizeof(ms_based_pos_req), req);
+}
+
+static int subscr_sig_cb(unsigned int subsys, unsigned int signal,
+			 void *handler_data, void *signal_data)
+{
+	struct gsm_subscriber *subscr;
+	struct gsm_subscriber_connection *conn;
+
+	switch (signal) {
+	case S_SUBSCR_ATTACHED:
+		/* A subscriber has attached. */
+		subscr = signal_data;
+		conn = connection_for_subscr(subscr);
+		if (!conn)
+			break;
+		send_rrlp_req(conn);
+		break;
+	}
+	return 0;
+}
+
+static int paging_sig_cb(unsigned int subsys, unsigned int signal,
+			 void *handler_data, void *signal_data)
+{
+	struct paging_signal_data *psig_data = signal_data;
+
+	switch (signal) {
+	case S_PAGING_SUCCEEDED:
+		/* A subscriber has attached. */
+		send_rrlp_req(psig_data->conn);
+		break;
+	case S_PAGING_EXPIRED:
+		break;
+	}
+	return 0;
+}
+
+void on_dso_load_rrlp(void)
+{
+	register_signal_handler(SS_SUBSCR, subscr_sig_cb, NULL);
+	register_signal_handler(SS_PAGING, paging_sig_cb, NULL);
+}
diff --git a/openbsc/src/msc/silent_call.c b/openbsc/src/msc/silent_call.c
new file mode 100644
index 0000000..64ebdfd
--- /dev/null
+++ b/openbsc/src/msc/silent_call.c
@@ -0,0 +1,143 @@
+/* GSM silent call feature */
+
+/*
+ * (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <osmocore/msgb.h>
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/paging.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/abis_rsl.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/osmo_msc.h>
+
+/* paging of the requested subscriber has completed */
+static int paging_cb_silent(unsigned int hooknum, unsigned int event,
+			    struct msgb *msg, void *_conn, void *_data)
+{
+	struct gsm_subscriber_connection *conn = _conn;
+	struct scall_signal_data sigdata;
+	int rc = 0;
+
+	if (hooknum != GSM_HOOK_RR_PAGING)
+		return -EINVAL;
+
+	DEBUGP(DSMS, "paging_cb_silent: ");
+
+	sigdata.conn = conn;
+	sigdata.data = _data;
+
+	switch (event) {
+	case GSM_PAGING_SUCCEEDED:
+		DEBUGPC(DSMS, "success, using Timeslot %u on ARFCN %u\n",
+			conn->lchan->ts->nr, conn->lchan->ts->trx->arfcn);
+		conn->silent_call = 1;
+		/* increment lchan reference count */
+		dispatch_signal(SS_SCALL, S_SCALL_SUCCESS, &sigdata);
+		break;
+	case GSM_PAGING_EXPIRED:
+	case GSM_PAGING_BUSY:
+	case GSM_PAGING_OOM:
+		DEBUGP(DSMS, "expired\n");
+		dispatch_signal(SS_SCALL, S_SCALL_EXPIRED, &sigdata);
+		break;
+	default:
+		rc = -EINVAL;
+		break;
+	}
+
+	return rc;
+}
+
+/* receive a layer 3 message from a silent call */
+int silent_call_rx(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	/* FIXME: do something like sending it through a UDP port */
+	return 0;
+}
+
+struct msg_match {
+	u_int8_t pdisc;
+	u_int8_t msg_type;
+};
+
+/* list of messages that are handled inside OpenBSC, even in a silent call */
+static const struct msg_match silent_call_accept[] = {
+	{ GSM48_PDISC_MM, GSM48_MT_MM_LOC_UPD_REQUEST },
+	{ GSM48_PDISC_MM, GSM48_MT_MM_CM_SERV_REQ },
+};
+
+/* decide if we need to reroute a message as part of a silent call */
+int silent_call_reroute(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	u_int8_t pdisc = gh->proto_discr & 0x0f;
+	int i;
+
+	/* if we're not part of a silent call, never reroute */
+	if (!conn->silent_call)
+		return 0;
+
+	/* check if we are a special message that is handled in openbsc */
+	for (i = 0; i < ARRAY_SIZE(silent_call_accept); i++) {
+		if (silent_call_accept[i].pdisc == pdisc &&
+		    silent_call_accept[i].msg_type == gh->msg_type)
+			return 0;
+	}
+
+	/* otherwise, reroute */
+	return 1;
+}
+
+
+/* initiate a silent call with a given subscriber */
+int gsm_silent_call_start(struct gsm_subscriber *subscr, void *data, int type)
+{
+	int rc;
+
+	rc = paging_request(subscr->net, subscr, type,
+			    paging_cb_silent, data);
+	return rc;
+}
+
+/* end a silent call with a given subscriber */
+int gsm_silent_call_stop(struct gsm_subscriber *subscr)
+{
+	struct gsm_subscriber_connection *conn;
+
+	conn = connection_for_subscr(subscr);
+	if (!conn)
+		return -EINVAL;
+
+	/* did we actually establish a silent call for this guy? */
+	if (!conn->silent_call)
+		return -EINVAL;
+
+	conn->silent_call = 0;
+	msc_release_connection(conn);
+
+	return 0;
+}
diff --git a/openbsc/src/msc/sms_queue.c b/openbsc/src/msc/sms_queue.c
new file mode 100644
index 0000000..079755d
--- /dev/null
+++ b/openbsc/src/msc/sms_queue.c
@@ -0,0 +1,479 @@
+/* SMS queue to continously attempt to deliver SMS */
+/*
+ * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * The difficulty of such a queue is to send a lot of SMS without
+ * overloading the paging subsystem and the database and other users
+ * of the MSC. To make the best use we would need to know the number
+ * of pending paging requests, then throttle the number of SMS we
+ * want to send and such.
+ * We will start with a very simple SMS Queue and then try to speed
+ * things up by collecting data from other parts of the system.
+ */
+
+#include <openbsc/sms_queue.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/db.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/signal.h>
+
+#include <osmocore/talloc.h>
+
+#include <osmocom/vty/vty.h>
+
+/*
+ * One pending SMS that we wait for.
+ */
+struct gsm_sms_pending {
+	struct llist_head entry;
+
+	struct gsm_subscriber *subscr;
+	unsigned long long sms_id;
+	int failed_attempts;
+	int resend;
+};
+
+struct gsm_sms_queue {
+	struct timer_list resend_pending;
+	struct timer_list push_queue;
+	struct gsm_network *network;
+	int max_fail;
+	int max_pending;
+	int pending;
+
+	struct llist_head pending_sms;
+	unsigned long long last_subscr_id;
+};
+
+static int sms_subscr_cb(unsigned int, unsigned int, void *, void *);
+static int sms_sms_cb(unsigned int, unsigned int, void *, void *);
+
+static struct gsm_sms_pending *sms_find_pending(struct gsm_sms_queue *smsq,
+						struct gsm_sms *sms)
+{
+	struct gsm_sms_pending *pending;
+
+	llist_for_each_entry(pending, &smsq->pending_sms, entry) {
+		if (pending->sms_id == sms->id)
+			return pending;
+	}
+
+	return NULL;
+}
+
+static int sms_is_in_pending(struct gsm_sms_queue *smsq, struct gsm_sms *sms)
+{
+	return sms_find_pending(smsq, sms) != NULL;
+}
+
+static int sms_subscriber_is_pending(struct gsm_sms_queue *smsq,
+				     struct gsm_subscriber *subscr)
+{
+	struct gsm_sms_pending *pending;
+
+	llist_for_each_entry(pending, &smsq->pending_sms, entry) {
+		if (pending->subscr == subscr)
+			return 1;
+	}
+
+	return 0;
+}
+
+static struct gsm_sms_pending *sms_pending_from(struct gsm_sms_queue *smsq,
+						struct gsm_sms *sms)
+{
+	struct gsm_sms_pending *pending;
+
+	pending = talloc_zero(smsq, struct gsm_sms_pending);
+	if (!pending)
+		return NULL;
+
+	pending->subscr = subscr_get(sms->receiver);
+	pending->sms_id = sms->id;
+	return pending;
+}
+
+static void sms_pending_free(struct gsm_sms_pending *pending)
+{
+	subscr_put(pending->subscr);
+	llist_del(&pending->entry);
+	talloc_free(pending);
+}
+
+static void sms_pending_resend(struct gsm_sms_pending *pending)
+{
+	struct gsm_sms_queue *smsq;
+	LOGP(DSMS, LOGL_DEBUG,
+	     "Scheduling resend of SMS %llu.\n", pending->sms_id);
+
+	pending->resend = 1;
+
+	smsq = pending->subscr->net->sms_queue;
+	if (bsc_timer_pending(&smsq->resend_pending))
+		return;
+
+	bsc_schedule_timer(&smsq->resend_pending, 1, 0);
+}
+
+static void sms_pending_failed(struct gsm_sms_pending *pending, int paging_error)
+{
+	struct gsm_sms_queue *smsq;
+
+	LOGP(DSMS, LOGL_NOTICE, "Sending SMS %llu failed %d times.\n",
+	     pending->sms_id, pending->failed_attempts);
+
+	smsq = pending->subscr->net->sms_queue;
+	if (++pending->failed_attempts < smsq->max_fail)
+		return sms_pending_resend(pending);
+
+	if (paging_error) {
+		LOGP(DSMS, LOGL_NOTICE,
+		     "Subscriber %llu is not reachable. Setting LAC=0.\n", pending->subscr->id);
+		pending->subscr->lac = GSM_LAC_RESERVED_DETACHED;
+		db_sync_subscriber(pending->subscr);
+
+		/* Workaround a failing sync */
+		db_subscriber_update(pending->subscr);
+	}
+
+	sms_pending_free(pending);
+	smsq->pending -= 1;
+	sms_queue_trigger(smsq);
+}
+
+/*
+ * Resend all SMS that are scheduled for a resend. This is done to
+ * avoid an immediate failure.
+ */
+static void sms_resend_pending(void *_data)
+{
+	struct gsm_sms_pending *pending, *tmp;
+	struct gsm_sms_queue *smsq = _data;
+
+	llist_for_each_entry_safe(pending, tmp, &smsq->pending_sms, entry) {
+		struct gsm_sms *sms;
+		if (!pending->resend)
+			continue;
+
+		sms = db_sms_get(smsq->network, pending->sms_id);
+
+		/* the sms is gone? Move to the next */
+		if (!sms) {
+			sms_pending_free(pending);
+			smsq->pending -= 1;
+			sms_queue_trigger(smsq);
+		} else {
+			pending->resend = 0;
+			gsm411_send_sms_subscr(sms->receiver, sms);
+		}
+	}
+}
+
+static struct gsm_sms *take_next_sms(struct gsm_sms_queue *smsq)
+{
+	struct gsm_sms *sms;
+
+	sms = db_sms_get_unsent_by_subscr(smsq->network, smsq->last_subscr_id, 10);
+	if (sms) {
+		smsq->last_subscr_id = sms->receiver->id + 1;
+		return sms;
+	}
+
+	/* need to wrap around */
+	smsq->last_subscr_id = 0;
+	sms = db_sms_get_unsent_by_subscr(smsq->network,
+					  smsq->last_subscr_id, 10);
+	if (sms)
+		smsq->last_subscr_id = sms->receiver->id + 1;
+	return sms;
+}
+
+/**
+ * I will submit up to max_pending - pending SMS to the
+ * subsystem.
+ */
+static void sms_submit_pending(void *_data)
+{
+	struct gsm_sms_queue *smsq = _data;
+	int attempts = smsq->max_pending - smsq->pending;
+	int initialized = 0;
+	unsigned long long first_sub = 0;
+	int attempted = 0, rounds = 0;
+
+	LOGP(DSMS, LOGL_NOTICE, "Attempting to send %d SMS\n", attempts);
+
+	do {
+		struct gsm_sms_pending *pending;
+		struct gsm_sms *sms;
+
+
+		sms = take_next_sms(smsq);
+		if (!sms)
+			break;
+
+		rounds += 1;
+
+		/*
+		 * This code needs to detect a loop. It assumes that no SMS
+		 * will vanish during the time this is executed. We will remember
+		 * the id of the first GSM subscriber we see and then will
+		 * compare this. The Database code should make sure that we will
+		 * see all other subscribers first before seeing this one again.
+		 *
+		 * It is always scary to have an infinite loop like this.
+		 */
+		if (!initialized) {
+			first_sub = sms->receiver->id;
+			initialized = 1;
+		} else if (first_sub == sms->receiver->id) {
+			sms_free(sms);
+			break;
+		}
+
+		/* no need to send a pending sms */
+		if (sms_is_in_pending(smsq, sms)) {
+			LOGP(DSMS, LOGL_DEBUG,
+			     "SMSqueue with pending sms: %llu. Skipping\n", sms->id);
+			sms_free(sms);
+			continue;
+		}
+
+		/* no need to send a SMS with the same receiver */
+		if (sms_subscriber_is_pending(smsq, sms->receiver)) {
+			LOGP(DSMS, LOGL_DEBUG,
+			     "SMSqueue with pending sub: %llu. Skipping\n", sms->receiver->id);
+			sms_free(sms);
+			continue;
+		}
+
+		pending = sms_pending_from(smsq, sms);
+		if (!pending) {
+			LOGP(DSMS, LOGL_ERROR,
+			     "Failed to create pending SMS entry.\n");
+			sms_free(sms);
+			continue;
+		}
+
+		attempted += 1;
+		smsq->pending += 1;
+		llist_add_tail(&pending->entry, &smsq->pending_sms);
+		gsm411_send_sms_subscr(sms->receiver, sms);
+	} while (attempted < attempts && rounds < 1000);
+
+	LOGP(DSMS, LOGL_DEBUG, "SMSqueue added %d messages in %d rounds\n", attempted, rounds);
+}
+
+/*
+ * Kick off the queue again.
+ */
+int sms_queue_trigger(struct gsm_sms_queue *smsq)
+{
+	if (bsc_timer_pending(&smsq->push_queue))
+		return 0;
+
+	bsc_schedule_timer(&smsq->push_queue, 1, 0);
+	return 0;
+}
+
+int sms_queue_start(struct gsm_network *network, int max_pending)
+{
+	struct gsm_sms_queue *sms = talloc_zero(network, struct gsm_sms_queue);
+	if (!sms) {
+		LOGP(DMSC, LOGL_ERROR, "Failed to create the SMS queue.\n");
+		return -1;
+	}
+
+	register_signal_handler(SS_SUBSCR, sms_subscr_cb, network);
+	register_signal_handler(SS_SMS, sms_sms_cb, network);
+
+	network->sms_queue = sms;
+	INIT_LLIST_HEAD(&sms->pending_sms);
+	sms->max_fail = 1;
+	sms->network = network;
+	sms->max_pending = max_pending;
+	sms->push_queue.data = sms;
+	sms->push_queue.cb = sms_submit_pending;
+	sms->resend_pending.data = sms;
+	sms->resend_pending.cb = sms_resend_pending;
+
+	sms_submit_pending(sms);
+
+	return 0;
+}
+
+static int sub_ready_for_sm(struct gsm_subscriber *subscr)
+{
+	struct gsm_subscriber_connection *conn;
+	struct gsm_sms *sms;
+
+	/* A subscriber has attached. Check if there are
+	 * any pending SMS for him to be delivered */
+	conn = connection_for_subscr(subscr);
+	if (!conn)
+		return -1;
+	sms = db_sms_get_unsent_for_subscr(subscr);
+	if (!sms)
+		return -1;
+	gsm411_send_sms(conn, sms);
+	return 0;
+}
+
+static int sms_subscr_cb(unsigned int subsys, unsigned int signal,
+			 void *handler_data, void *signal_data)
+{
+	struct gsm_subscriber *subscr = signal_data;
+
+	if (signal != S_SUBSCR_ATTACHED)
+		return 0;
+
+	/* this is readyForSM */
+	return sub_ready_for_sm(subscr);
+}
+
+static int sms_sms_cb(unsigned int subsys, unsigned int signal,
+		      void *handler_data, void *signal_data)
+{
+	struct gsm_network *network = handler_data;
+	struct sms_signal_data *sig_sms = signal_data;
+	struct gsm_sms_pending *pending;
+
+	/* We got a new SMS and maybe should launch the queue again. */
+	if (signal == S_SMS_SUBMITTED || signal == S_SMS_SMMA) {
+		sms_queue_trigger(network->sms_queue);
+		return 0;
+	}
+
+	if (!sig_sms->sms)
+		return -1;
+
+
+	/*
+	 * Find the entry of our queue. The SMS subsystem will submit
+	 * sms that are not in our control as we just have a channel
+	 * open anyway.
+	 */
+	pending = sms_find_pending(network->sms_queue, sig_sms->sms);
+	if (!pending)
+		return 0;
+
+	switch (signal) {
+	case S_SMS_DELIVERED:
+		/*
+		 * Create place for a new SMS but keep the pending data
+		 * so we will not attempt to send the SMS for this subscriber
+		 * as we still have an open channel and will attempt to submit
+		 * SMS to it anyway.
+		 */
+		network->sms_queue->pending -= 1;
+		sms_submit_pending(network->sms_queue);
+		sms_pending_free(pending);
+		break;
+	case S_SMS_MEM_EXCEEDED:
+		network->sms_queue->pending -= 1;
+		sms_pending_free(pending);
+		sms_queue_trigger(network->sms_queue);
+		break;
+	case S_SMS_UNKNOWN_ERROR:
+		/*
+		 * There can be many reasons for this failure. E.g. the paging
+		 * timed out, the subscriber was not paged at all, or there was
+		 * a protocol error. The current strategy is to try sending the
+		 * next SMS for busy/oom and to retransmit when we have paged.
+		 *
+		 * When the paging expires three times we will disable the
+		 * subscriber. If we have some kind of other transmit error we
+		 * should flag the SMS as bad.
+		 */
+		switch (sig_sms->paging_result) {
+		case 0:
+			/* BAD SMS? */
+			db_sms_inc_deliver_attempts(sig_sms->sms);
+			sms_pending_failed(pending, 0);
+			break;
+		case GSM_PAGING_EXPIRED:
+			sms_pending_failed(pending, 1);
+			break;
+
+		case GSM_PAGING_OOM:
+		case GSM_PAGING_BUSY:
+			network->sms_queue->pending -= 1;
+			sms_pending_free(pending);
+			sms_queue_trigger(network->sms_queue);
+			break;
+		default:
+			LOGP(DSMS, LOGL_ERROR, "Unhandled result: %d\n",
+			     sig_sms->paging_result);
+		}
+		break;
+	default:
+		LOGP(DSMS, LOGL_ERROR, "Unhandled result: %d\n",
+		     sig_sms->paging_result);
+	}
+
+	return 0;
+}
+
+/* VTY helper functions */
+int sms_queue_stats(struct gsm_sms_queue *smsq, struct vty *vty)
+{
+	struct gsm_sms_pending *pending;
+
+	vty_out(vty, "SMSqueue with max_pending: %d pending: %d%s",
+		smsq->max_pending, smsq->pending, VTY_NEWLINE);
+
+	llist_for_each_entry(pending, &smsq->pending_sms, entry)
+		vty_out(vty, " SMS Pending for Subscriber: %llu SMS: %llu Failed: %d.%s",
+			pending->subscr->id, pending->sms_id,
+			pending->failed_attempts, VTY_NEWLINE);
+	return 0;
+}
+
+int sms_queue_set_max_pending(struct gsm_sms_queue *smsq, int max_pending)
+{
+	LOGP(DSMS, LOGL_NOTICE, "SMSqueue old max: %d new: %d\n",
+	     smsq->max_pending, max_pending);
+	smsq->max_pending = max_pending;
+	return 0;
+}
+
+int sms_queue_set_max_failure(struct gsm_sms_queue *smsq, int max_fail)
+{
+	LOGP(DSMS, LOGL_NOTICE, "SMSqueue max failure old: %d new: %d\n",
+	     smsq->max_fail, max_fail);
+	smsq->max_fail = max_fail;
+	return 0;
+}
+
+int sms_queue_clear(struct gsm_sms_queue *smsq)
+{
+	struct gsm_sms_pending *pending, *tmp;
+
+	llist_for_each_entry_safe(pending, tmp, &smsq->pending_sms, entry) {
+		LOGP(DSMS, LOGL_NOTICE,
+		     "SMSqueue clearing for sub %llu\n", pending->subscr->id);
+		sms_pending_free(pending);
+	}
+
+	smsq->pending = 0;
+	return 0;
+}
diff --git a/openbsc/src/msc/token_auth.c b/openbsc/src/msc/token_auth.c
new file mode 100644
index 0000000..3404dd4
--- /dev/null
+++ b/openbsc/src/msc/token_auth.c
@@ -0,0 +1,153 @@
+/* SMS based token authentication for ad-hoc GSM networks */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <osmocore/talloc.h>
+#include <openbsc/signal.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/gsm_04_08.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/db.h>
+
+#define TOKEN_SMS_TEXT "HAR 2009 GSM.  Register at http://har2009.gnumonks.org/ " \
+			"Your IMSI is %s, auth token is %08X, phone no is %s."
+
+static char *build_sms_string(struct gsm_subscriber *subscr, u_int32_t token)
+{
+	char *sms_str;
+	unsigned int len;
+
+	len = strlen(subscr->imsi) + 8 + strlen(TOKEN_SMS_TEXT);
+	sms_str = talloc_size(tall_bsc_ctx, len);
+	if (!sms_str)
+		return NULL;
+
+	snprintf(sms_str, len, TOKEN_SMS_TEXT, subscr->imsi, token,
+		 subscr->extension);
+	sms_str[len-1] = '\0';
+
+	return sms_str;
+}
+
+static int token_subscr_cb(unsigned int subsys, unsigned int signal,
+			void *handler_data, void *signal_data)
+{
+	struct gsm_subscriber *subscr = signal_data;
+	struct gsm_sms *sms;
+	int rc = 0;
+
+	if (signal != S_SUBSCR_ATTACHED)
+		return 0;
+
+	if (subscr->net->auth_policy != GSM_AUTH_POLICY_TOKEN)
+		return 0;
+
+	if (subscr->flags & GSM_SUBSCRIBER_FIRST_CONTACT) {
+		u_int32_t token;
+		char *sms_str;
+
+		/* we've seen this subscriber for the first time. */
+		rc = db_subscriber_alloc_token(subscr, &token);
+		if (rc != 0) {
+			rc = -EIO;
+			goto unauth;
+		}
+
+		sms_str = build_sms_string(subscr, token);
+		if (!sms_str) {
+			rc = -ENOMEM;
+			goto unauth;
+		}
+
+		sms = sms_from_text(subscr, 0, sms_str);
+		talloc_free(sms_str);
+		if (!sms) {
+			rc = -ENOMEM;
+			goto unauth;
+		}
+
+		rc = gsm411_send_sms_subscr(subscr, sms);
+
+		/* FIXME: else, delete the subscirber from database */
+unauth:
+
+		/* make sure we don't allow him in again unless he clicks the web UI */
+		subscr->authorized = 0;
+		db_sync_subscriber(subscr);
+		if (rc) {
+			struct gsm_subscriber_connection *conn = connection_for_subscr(subscr);
+			if (conn) {
+				u_int8_t auth_rand[16];
+				/* kick the subscriber off the network */
+				gsm48_tx_mm_auth_req(conn, auth_rand, 0);
+				gsm48_tx_mm_auth_rej(conn);
+				/* FIXME: close the channel early ?*/
+				//gsm48_send_rr_Release(lchan);
+			}
+		}
+	}
+
+	return rc;
+}
+
+static int token_sms_cb(unsigned int subsys, unsigned int signal,
+			void *handler_data, void *signal_data)
+{
+	struct sms_signal_data *sig = signal_data;
+	struct gsm_sms *sms = sig->sms;;
+	struct gsm_subscriber_connection *conn;
+	u_int8_t auth_rand[16];
+
+
+	if (signal != S_SMS_DELIVERED)
+		return 0;
+
+
+	/* these are not the droids we've been looking for */
+	if (!sms->receiver ||
+	    !(sms->receiver->flags & GSM_SUBSCRIBER_FIRST_CONTACT))
+		return 0;
+
+
+	if (sms->receiver->net->auth_policy != GSM_AUTH_POLICY_TOKEN)
+		return 0;
+
+
+	conn = connection_for_subscr(sms->receiver);
+	if (conn) {
+		/* kick the subscriber off the network */
+		gsm48_tx_mm_auth_req(conn, auth_rand, 0);
+		gsm48_tx_mm_auth_rej(conn);
+		/* FIXME: close the channel early ?*/
+		//gsm48_send_rr_Release(lchan);
+	}
+
+	return 0;
+}
+
+//static __attribute__((constructor)) void on_dso_load_token(void)
+void on_dso_load_token(void)
+{
+	register_signal_handler(SS_SUBSCR, token_subscr_cb, NULL);
+	register_signal_handler(SS_SMS, token_sms_cb, NULL);
+}
diff --git a/openbsc/src/msc/ussd.c b/openbsc/src/msc/ussd.c
new file mode 100644
index 0000000..72f26bd
--- /dev/null
+++ b/openbsc/src/msc/ussd.c
@@ -0,0 +1,79 @@
+/* Network-specific handling of mobile-originated USSDs. */
+
+/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2009 by Mike Haben <michael.haben@btinternet.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/* This module defines the network-specific handling of mobile-originated
+   USSD messages. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+#include <openbsc/osmo_msc.h>
+
+/* Declarations of USSD strings to be recognised */
+const char USSD_TEXT_OWN_NUMBER[] = "*#100#";
+
+/* Forward declarations of network-specific handler functions */
+static int send_own_number(struct gsm_subscriber_connection *conn, const struct msgb *msg, const struct ussd_request *req);
+
+
+/* Entrypoint - handler function common to all mobile-originated USSDs */
+int handle_rcv_ussd(struct gsm_subscriber_connection *conn, struct msgb *msg)
+{
+	int rc;
+	struct ussd_request req;
+	struct gsm48_hdr *gh;
+
+	memset(&req, 0, sizeof(req));
+	gh = msgb_l3(msg);
+	rc = gsm0480_decode_ussd_request(gh, msgb_l3len(msg), &req);
+	if (req.text[0] == 0xFF)  /* Release-Complete */
+		return 0;
+
+	if (strstr(USSD_TEXT_OWN_NUMBER, req.text) != NULL) {
+		DEBUGP(DMM, "USSD: Own number requested\n");
+		rc = send_own_number(conn, msg, &req);
+	} else {
+		DEBUGP(DMM, "Unhandled USSD %s\n", req.text);
+		rc = gsm0480_send_ussd_reject(conn, msg, &req);
+	}
+
+	/* check if we can release it */
+	msc_release_connection(conn);
+	return rc;
+}
+
+/* A network-specific handler function */
+static int send_own_number(struct gsm_subscriber_connection *conn, const struct msgb *msg, const struct ussd_request *req)
+{
+	char *own_number = conn->subscr->extension;
+	char response_string[GSM_EXTENSION_LENGTH + 20];
+
+	/* Need trailing CR as EOT character */
+	snprintf(response_string, sizeof(response_string), "Your extension is %s\r", own_number);
+	return gsm0480_send_ussd_response(conn, msg, response_string, req);
+}
diff --git a/openbsc/src/msc/vty_interface_layer3.c b/openbsc/src/msc/vty_interface_layer3.c
new file mode 100644
index 0000000..a38d15b
--- /dev/null
+++ b/openbsc/src/msc/vty_interface_layer3.c
@@ -0,0 +1,790 @@
+/* OpenBSC interface to quagga VTY */
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009 by Holger Hans Peter Freyther
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/vty.h>
+
+#include <arpa/inet.h>
+
+#include <osmocore/linuxlist.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/silent_call.h>
+#include <openbsc/gsm_04_11.h>
+#include <openbsc/e1_input.h>
+#include <openbsc/abis_nm.h>
+#include <osmocore/gsm_utils.h>
+#include <osmocore/utils.h>
+#include <openbsc/db.h>
+#include <osmocore/talloc.h>
+#include <openbsc/signal.h>
+#include <openbsc/debug.h>
+#include <openbsc/vty.h>
+#include <openbsc/gsm_04_80.h>
+#include <openbsc/chan_alloc.h>
+#include <openbsc/sms_queue.h>
+
+extern struct gsm_network *gsmnet_from_vty(struct vty *v);
+
+static void subscr_dump_full_vty(struct vty *vty, struct gsm_subscriber *subscr, int pending)
+{
+	int rc;
+	struct gsm_auth_info ainfo;
+	struct gsm_auth_tuple atuple;
+
+	vty_out(vty, "    ID: %llu, Authorized: %d%s", subscr->id,
+		subscr->authorized, VTY_NEWLINE);
+	if (subscr->name)
+		vty_out(vty, "    Name: '%s'%s", subscr->name, VTY_NEWLINE);
+	if (subscr->extension)
+		vty_out(vty, "    Extension: %s%s", subscr->extension,
+			VTY_NEWLINE);
+	vty_out(vty, "    LAC: %d/0x%x%s",
+		subscr->lac, subscr->lac, VTY_NEWLINE);
+	if (subscr->imsi)
+		vty_out(vty, "    IMSI: %s%s", subscr->imsi, VTY_NEWLINE);
+	if (subscr->tmsi != GSM_RESERVED_TMSI)
+		vty_out(vty, "    TMSI: %08X%s", subscr->tmsi,
+			VTY_NEWLINE);
+
+	rc = db_get_authinfo_for_subscr(&ainfo, subscr);
+	if (!rc) {
+		vty_out(vty, "    A3A8 algorithm id: %d%s",
+			ainfo.auth_algo, VTY_NEWLINE);
+		vty_out(vty, "    A3A8 Ki: %s%s",
+			hexdump(ainfo.a3a8_ki, ainfo.a3a8_ki_len),
+			VTY_NEWLINE);
+	}
+
+	rc = db_get_lastauthtuple_for_subscr(&atuple, subscr);
+	if (!rc) {
+		vty_out(vty, "    A3A8 last tuple (used %d times):%s",
+			atuple.use_count, VTY_NEWLINE);
+		vty_out(vty, "     seq # : %d%s",
+			atuple.key_seq, VTY_NEWLINE);
+		vty_out(vty, "     RAND  : %s%s",
+			hexdump(atuple.rand, sizeof(atuple.rand)),
+			VTY_NEWLINE);
+		vty_out(vty, "     SRES  : %s%s",
+			hexdump(atuple.sres, sizeof(atuple.sres)),
+			VTY_NEWLINE);
+		vty_out(vty, "     Kc    : %s%s",
+			hexdump(atuple.kc, sizeof(atuple.kc)),
+			VTY_NEWLINE);
+	}
+	if (pending)
+		vty_out(vty, "    Pending: %d%s",
+			subscr_pending_requests(subscr), VTY_NEWLINE);
+
+	vty_out(vty, "    Use count: %u%s", subscr->use_count, VTY_NEWLINE);
+}
+
+
+/* Subscriber */
+DEFUN(show_subscr_cache,
+      show_subscr_cache_cmd,
+      "show subscriber cache",
+	SHOW_STR "Display contents of subscriber cache\n")
+{
+	struct gsm_subscriber *subscr;
+
+	llist_for_each_entry(subscr, &active_subscribers, entry) {
+		vty_out(vty, "  Subscriber:%s", VTY_NEWLINE);
+		subscr_dump_full_vty(vty, subscr, 0);
+	}
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(sms_send_pend,
+      sms_send_pend_cmd,
+      "sms send pending",
+      "Send all pending SMS")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_sms *sms;
+	int id = 0;
+
+	while (1) {
+		sms = db_sms_get_unsent_by_subscr(gsmnet, id, UINT_MAX);
+		if (!sms)
+			break;
+
+		gsm411_send_sms_subscr(sms->receiver, sms);
+
+		id = sms->receiver->id + 1;
+	}
+
+	return CMD_SUCCESS;
+}
+
+static int _send_sms_str(struct gsm_subscriber *receiver, char *str,
+			 u_int8_t tp_pid)
+{
+	struct gsm_sms *sms;
+
+	sms = sms_from_text(receiver, 0, str);
+	sms->protocol_id = tp_pid;
+
+	/* store in database for the queue */
+	if (db_sms_store(sms) != 0) {
+		LOGP(DSMS, LOGL_ERROR, "Failed to store SMS in Database\n");
+		sms_free(sms);
+		return CMD_WARNING;
+	}
+
+	sms_free(sms);
+	sms_queue_trigger(receiver->net->sms_queue);
+	return CMD_SUCCESS;
+}
+
+static struct gsm_subscriber *get_subscr_by_argv(struct gsm_network *gsmnet,
+						 const char *type,
+						 const char *id)
+{
+	if (!strcmp(type, "extension"))
+		return subscr_get_by_extension(gsmnet, id);
+	else if (!strcmp(type, "imsi"))
+		return subscr_get_by_imsi(gsmnet, id);
+	else if (!strcmp(type, "tmsi"))
+		return subscr_get_by_tmsi(gsmnet, atoi(id));
+	else if (!strcmp(type, "id"))
+		return subscr_get_by_id(gsmnet, atoi(id));
+
+	return NULL;
+}
+#define SUBSCR_TYPES "(extension|imsi|tmsi|id)"
+#define SUBSCR_HELP "Operations on a Subscriber\n"			\
+	"Identify subscriber by his extension (phone number)\n"		\
+	"Identify subscriber by his IMSI\n"				\
+	"Identify subscriber by his TMSI\n"				\
+	"Identify subscriber by his database ID\n"			\
+	"Identifier for the subscriber\n"
+
+DEFUN(show_subscr,
+      show_subscr_cmd,
+      "show subscriber " SUBSCR_TYPES " ID",
+	SHOW_STR SUBSCR_HELP)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+				get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	subscr_dump_full_vty(vty, subscr, 1);
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_send_pending_sms,
+      subscriber_send_pending_sms_cmd,
+      "subscriber " SUBSCR_TYPES " ID sms pending send",
+	SUBSCR_HELP "SMS Operations\n" "Send pending SMS\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	struct gsm_sms *sms;
+
+	sms = db_sms_get_unsent_by_subscr(gsmnet, subscr->id, UINT_MAX);
+	if (sms)
+		gsm411_send_sms_subscr(sms->receiver, sms);
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_send_sms,
+      subscriber_send_sms_cmd,
+      "subscriber " SUBSCR_TYPES " ID sms send .LINE",
+	SUBSCR_HELP "SMS Operations\n" "Send SMS\n" "Actual SMS Text")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	char *str;
+	int rc;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+	str = argv_concat(argv, argc, 2);
+	rc = _send_sms_str(subscr, str, 0);
+	talloc_free(str);
+
+	subscr_put(subscr);
+
+	return rc;
+}
+
+DEFUN(subscriber_silent_sms,
+      subscriber_silent_sms_cmd,
+      "subscriber " SUBSCR_TYPES " ID silent-sms send .LINE",
+	SUBSCR_HELP
+	"Silent SMS Operation\n" "Send Silent SMS\n" "Actual SMS text\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	char *str;
+	int rc;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	str = argv_concat(argv, argc, 2);
+	rc = _send_sms_str(subscr, str, 64);
+	talloc_free(str);
+
+	subscr_put(subscr);
+
+	return rc;
+}
+
+#define CHAN_TYPES "(any|tch/f|tch/any|sdcch)"
+#define CHAN_TYPE_HELP 			\
+		"Any channel\n"		\
+		"TCH/F channel\n"	\
+		"Any TCH channel\n"	\
+		"SDCCH channel\n"
+
+DEFUN(subscriber_silent_call_start,
+      subscriber_silent_call_start_cmd,
+      "subscriber " SUBSCR_TYPES " ID silent-call start (any|tch/f|tch/any|sdcch)",
+	SUBSCR_HELP "Silent call operation\n" "Start silent call\n"
+	CHAN_TYPE_HELP)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	int rc, type;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcmp(argv[2], "tch/f"))
+		type = RSL_CHANNEED_TCH_F;
+	else if (!strcmp(argv[2], "tch/any"))
+		type = RSL_CHANNEED_TCH_ForH;
+	else if (!strcmp(argv[2], "sdcch"))
+		type = RSL_CHANNEED_SDCCH;
+	else
+		type = RSL_CHANNEED_ANY;	/* Defaults to ANY */
+
+	rc = gsm_silent_call_start(subscr, vty, type);
+	if (rc <= 0) {
+		vty_out(vty, "%% Subscriber not attached%s",
+			VTY_NEWLINE);
+		subscr_put(subscr);
+		return CMD_WARNING;
+	}
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_silent_call_stop,
+      subscriber_silent_call_stop_cmd,
+      "subscriber " SUBSCR_TYPES " ID silent-call stop",
+	SUBSCR_HELP "Silent call operation\n" "Stop silent call\n"
+	CHAN_TYPE_HELP)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	int rc;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	rc = gsm_silent_call_stop(subscr);
+	if (rc < 0) {
+		subscr_put(subscr);
+		return CMD_WARNING;
+	}
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_ussd_notify,
+      subscriber_ussd_notify_cmd,
+      "subscriber " SUBSCR_TYPES " ID ussd-notify (0|1|2) .TEXT",
+      SUBSCR_HELP "USSD Notify\n"
+      "Subscriber ID\n"
+      "Alerting Level\n"
+      "Text Message to send\n")
+{
+	char *text;
+	struct gsm_subscriber_connection *conn;
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	int level;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	level = atoi(argv[2]);
+	text = argv_concat(argv, argc, 3);
+	if (!text) {
+		subscr_put(subscr);
+		return CMD_WARNING;
+	}
+
+	conn = connection_for_subscr(subscr);
+	if (!conn) {
+		vty_out(vty, "%% An active connection is required for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		subscr_put(subscr);
+		talloc_free(text);
+		return CMD_WARNING;
+	}
+
+	gsm0480_send_ussdNotify(conn, level, text);
+	gsm0480_send_releaseComplete(conn);
+
+	subscr_put(subscr);
+	talloc_free(text);
+	return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_authorizde,
+      ena_subscr_authorized_cmd,
+      "subscriber " SUBSCR_TYPES " ID authorized (0|1)",
+	SUBSCR_HELP "(De-)Authorize subscriber in HLR\n"
+	"Subscriber should NOT be authorized\n"
+	"Subscriber should be authorized\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	subscr->authorized = atoi(argv[2]);
+	db_sync_subscriber(subscr);
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_name,
+      ena_subscr_name_cmd,
+      "subscriber " SUBSCR_TYPES " ID name .NAME",
+	SUBSCR_HELP "Set the name of the subscriber\n"
+	"Name of the Subscriber\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	char *name;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	name = argv_concat(argv, argc, 2);
+	if (!name) {
+		subscr_put(subscr);
+		return CMD_WARNING;
+	}
+
+	strncpy(subscr->name, name, sizeof(subscr->name));
+	talloc_free(name);
+	db_sync_subscriber(subscr);
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_extension,
+      ena_subscr_extension_cmd,
+      "subscriber " SUBSCR_TYPES " ID extension EXTENSION",
+	SUBSCR_HELP "Set the extension (phone number) of the subscriber\n"
+	"Extension (phone number)\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	const char *name = argv[2];
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	strncpy(subscr->extension, name, sizeof(subscr->name));
+	db_sync_subscriber(subscr);
+
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_clear,
+      ena_subscr_clear_cmd,
+      "subscriber " SUBSCR_TYPES " ID clear-requests",
+	SUBSCR_HELP "Clear the paging requests for this subscriber\n")
+{
+	int del;
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	del = subscr_pending_clear(subscr);
+	vty_out(vty, "Cleared %d pending requests.%s", del, VTY_NEWLINE);
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_pend,
+      ena_subscr_pend_cmd,
+      "subscriber " SUBSCR_TYPES " ID show-pending",
+	SUBSCR_HELP "Clear the paging requests for this subscriber\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	subscr_pending_dump(subscr, vty);
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+DEFUN(ena_subscr_kick,
+      ena_subscr_kick_cmd,
+      "subscriber " SUBSCR_TYPES " ID kick-pending",
+	SUBSCR_HELP "Clear the paging requests for this subscriber\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	subscr_pending_kick(subscr);
+	subscr_put(subscr);
+
+	return CMD_SUCCESS;
+}
+
+#define A3A8_ALG_TYPES "(none|xor|comp128v1)"
+#define A3A8_ALG_HELP 			\
+	"Use No A3A8 algorithm\n"	\
+	"Use XOR algorithm\n"		\
+	"Use COMP128v1 algorithm\n"
+
+DEFUN(ena_subscr_a3a8,
+      ena_subscr_a3a8_cmd,
+      "subscriber " SUBSCR_TYPES " ID a3a8 " A3A8_ALG_TYPES " [KI]",
+      SUBSCR_HELP "Set a3a8 parameters for the subscriber\n"
+      A3A8_ALG_HELP "Encryption Key Ki\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr =
+			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	const char *alg_str = argv[2];
+	const char *ki_str = argc == 4 ? argv[3] : NULL;
+	struct gsm_auth_info ainfo;
+	int rc, minlen, maxlen;
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	if (!strcasecmp(alg_str, "none")) {
+		ainfo.auth_algo = AUTH_ALGO_NONE;
+		minlen = maxlen = 0;
+	} else if (!strcasecmp(alg_str, "xor")) {
+		ainfo.auth_algo = AUTH_ALGO_XOR;
+		minlen = A38_XOR_MIN_KEY_LEN;
+		maxlen = A38_XOR_MAX_KEY_LEN;
+	} else if (!strcasecmp(alg_str, "comp128v1")) {
+		ainfo.auth_algo = AUTH_ALGO_COMP128v1;
+		minlen = maxlen = A38_COMP128_KEY_LEN;
+	} else {
+		/* Unknown method */
+		subscr_put(subscr);
+		return CMD_WARNING;
+	}
+
+	if (ki_str) {
+		rc = hexparse(ki_str, ainfo.a3a8_ki, sizeof(ainfo.a3a8_ki));
+		if ((rc > maxlen) || (rc < minlen)) {
+			subscr_put(subscr);
+			return CMD_WARNING;
+		}
+		ainfo.a3a8_ki_len = rc;
+	} else {
+		ainfo.a3a8_ki_len = 0;
+		if (minlen) {
+			subscr_put(subscr);
+			return CMD_WARNING;
+		}
+	}
+
+	rc = db_sync_authinfo_for_subscr(
+		ainfo.auth_algo == AUTH_ALGO_NONE ? NULL : &ainfo,
+		subscr);
+
+	/* the last tuple probably invalid with the new auth settings */
+	db_sync_lastauthtuple_for_subscr(NULL, subscr);
+	subscr_put(subscr);
+
+	return rc ? CMD_WARNING : CMD_SUCCESS;
+}
+
+DEFUN(subscriber_purge,
+      subscriber_purge_cmd,
+      "subscriber purge-inactive",
+      "Operations on a Subscriber\n" "Purge subscribers with a zero use count.\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+	int purged;
+
+	purged = subscr_purge_inactive(net);
+	vty_out(vty, "%d subscriber(s) were purged.%s", purged, VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+DEFUN(subscriber_update,
+      subscriber_update_cmd,
+      "subscriber " SUBSCR_TYPES " ID update",
+      SUBSCR_HELP "Update the subscriber data from the dabase.\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+
+	if (!subscr) {
+		vty_out(vty, "%% No subscriber found for %s %s%s",
+			argv[0], argv[1], VTY_NEWLINE);
+		return CMD_WARNING;
+	}
+
+	subscr_update_from_db(subscr);
+	subscr_put(subscr);
+	return CMD_SUCCESS;
+}
+
+static int scall_cbfn(unsigned int subsys, unsigned int signal,
+			void *handler_data, void *signal_data)
+{
+	struct scall_signal_data *sigdata = signal_data;
+	struct vty *vty = sigdata->data;
+
+	switch (signal) {
+	case S_SCALL_SUCCESS:
+		vty_out(vty, "%% silent call on ARFCN %u timeslot %u%s",
+			sigdata->conn->lchan->ts->trx->arfcn, sigdata->conn->lchan->ts->nr,
+			VTY_NEWLINE);
+		break;
+	case S_SCALL_EXPIRED:
+		vty_out(vty, "%% silent call expired paging%s", VTY_NEWLINE);
+		break;
+	}
+	return 0;
+}
+
+DEFUN(show_stats,
+      show_stats_cmd,
+      "show statistics",
+	SHOW_STR "Display network statistics\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+
+	openbsc_vty_print_statistics(vty, net);
+	vty_out(vty, "Channel Requests        : %lu total, %lu no channel%s",
+		counter_get(net->stats.chreq.total),
+		counter_get(net->stats.chreq.no_channel), VTY_NEWLINE);
+	vty_out(vty, "Location Update         : %lu attach, %lu normal, %lu periodic%s",
+		counter_get(net->stats.loc_upd_type.attach),
+		counter_get(net->stats.loc_upd_type.normal),
+		counter_get(net->stats.loc_upd_type.periodic), VTY_NEWLINE);
+	vty_out(vty, "IMSI Detach Indications : %lu%s",
+		counter_get(net->stats.loc_upd_type.detach), VTY_NEWLINE);
+	vty_out(vty, "Location Update Response: %lu accept, %lu reject%s",
+		counter_get(net->stats.loc_upd_resp.accept),
+		counter_get(net->stats.loc_upd_resp.reject), VTY_NEWLINE);
+	vty_out(vty, "Handover                : %lu attempted, %lu no_channel, %lu timeout, "
+		"%lu completed, %lu failed%s",
+		counter_get(net->stats.handover.attempted),
+		counter_get(net->stats.handover.no_channel),
+		counter_get(net->stats.handover.timeout),
+		counter_get(net->stats.handover.completed),
+		counter_get(net->stats.handover.failed), VTY_NEWLINE);
+	vty_out(vty, "SMS MO                  : %lu submitted, %lu no receiver%s",
+		counter_get(net->stats.sms.submitted),
+		counter_get(net->stats.sms.no_receiver), VTY_NEWLINE);
+	vty_out(vty, "SMS MT                  : %lu delivered, %lu no memory, %lu other error%s",
+		counter_get(net->stats.sms.delivered),
+		counter_get(net->stats.sms.rp_err_mem),
+		counter_get(net->stats.sms.rp_err_other), VTY_NEWLINE);
+	vty_out(vty, "MO Calls                : %lu setup, %lu connect ack%s",
+		counter_get(net->stats.call.mo_setup),
+		counter_get(net->stats.call.mo_connect_ack), VTY_NEWLINE);
+	vty_out(vty, "MT Calls                : %lu setup, %lu connect%s",
+		counter_get(net->stats.call.mt_setup),
+		counter_get(net->stats.call.mt_connect), VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
+DEFUN(show_smsqueue,
+      show_smsqueue_cmd,
+      "show sms-queue",
+      SHOW_STR "Display SMSqueue statistics\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+
+	sms_queue_stats(net->sms_queue, vty);
+	return CMD_SUCCESS;
+}
+
+DEFUN(smsqueue_trigger,
+      smsqueue_trigger_cmd,
+      "sms-queue trigger",
+      "SMS Queue\n" "Trigger sending messages\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+
+	sms_queue_trigger(net->sms_queue);
+	return CMD_SUCCESS;
+}
+
+DEFUN(smsqueue_max,
+      smsqueue_max_cmd,
+      "sms-queue max-pending <1-500>",
+      "SMS Queue\n" "SMS to attempt to deliver at the same time\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+
+	sms_queue_set_max_pending(net->sms_queue, atoi(argv[0]));
+	return CMD_SUCCESS;
+}
+
+DEFUN(smsqueue_clear,
+      smsqueue_clear_cmd,
+      "sms-queue clear",
+      "SMS Queue\n" "Clear the queue of pending SMS\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+
+	sms_queue_clear(net->sms_queue);
+	return CMD_SUCCESS;
+}
+
+DEFUN(smsqueue_fail,
+      smsqueue_fail_cmd,
+      "sms-queue max-failure <1-500>",
+      "SMS Queue\n" "Set maximum amount of failures\n")
+{
+	struct gsm_network *net = gsmnet_from_vty(vty);
+
+	sms_queue_set_max_failure(net->sms_queue, atoi(argv[0]));
+	return CMD_SUCCESS;
+}
+
+int bsc_vty_init_extra(void)
+{
+	register_signal_handler(SS_SCALL, scall_cbfn, NULL);
+
+	install_element_ve(&show_subscr_cmd);
+	install_element_ve(&show_subscr_cache_cmd);
+
+	install_element_ve(&sms_send_pend_cmd);
+
+	install_element_ve(&subscriber_send_sms_cmd);
+	install_element_ve(&subscriber_silent_sms_cmd);
+	install_element_ve(&subscriber_silent_call_start_cmd);
+	install_element_ve(&subscriber_silent_call_stop_cmd);
+	install_element_ve(&subscriber_ussd_notify_cmd);
+	install_element_ve(&subscriber_update_cmd);
+	install_element_ve(&show_stats_cmd);
+	install_element_ve(&show_smsqueue_cmd);
+
+	install_element(ENABLE_NODE, &ena_subscr_name_cmd);
+	install_element(ENABLE_NODE, &ena_subscr_extension_cmd);
+	install_element(ENABLE_NODE, &ena_subscr_authorized_cmd);
+	install_element(ENABLE_NODE, &ena_subscr_a3a8_cmd);
+	install_element(ENABLE_NODE, &ena_subscr_clear_cmd);
+	install_element(ENABLE_NODE, &ena_subscr_pend_cmd);
+	install_element(ENABLE_NODE, &ena_subscr_kick_cmd);
+	install_element(ENABLE_NODE, &subscriber_purge_cmd);
+	install_element(ENABLE_NODE, &smsqueue_trigger_cmd);
+	install_element(ENABLE_NODE, &smsqueue_max_cmd);
+	install_element(ENABLE_NODE, &smsqueue_clear_cmd);
+	install_element(ENABLE_NODE, &smsqueue_fail_cmd);
+	install_element(ENABLE_NODE, &subscriber_send_pending_sms_cmd);
+
+	return 0;
+}