Use libvlr in libmsc (large refactoring)

Original libvlr code is by Harald Welte <laforge@gnumonks.org>,
polished and tweaked by Neels Hofmeyr <nhofmeyr@sysmocom.de>.

This is a long series of trial-and-error development collapsed in one patch.
This may be split in smaller commits if reviewers prefer that. If we can keep
it as one, we have saved ourselves the additional separation work.

SMS:

The SQL based lookup of SMS for attached subscribers no longer works since the
SQL database no longer has the subscriber data. Replace with a round-robin on
the SMS recipient MSISDNs paired with a VLR subscriber RAM lookup whether the
subscriber is currently attached.

If there are many SMS for not-attached subscribers in the SMS database, this
will become inefficient: a DB hit returns a pending SMS, the RAM lookup will
reveal that the subscriber is not attached, after which the DB is hit for the
next SMS. It would become more efficient e.g. by having an MSISDN based hash
list for the VLR subscribers and by marking non-attached SMS recipients in the
SMS database so that they can be excluded with the SQL query already.

There is a sanity limit to do at most 100 db hits per attempt to find a pending
SMS. So if there are more than 100 stored SMS waiting for their recipients to
actually attach to the MSC, it may take more than one SMS queue trigger to
deliver SMS for subscribers that are actually attached.

This is not very beautiful, but is merely intended to carry us over to a time
when we have a proper separate SMSC entity.

Introduce gsm_subscriber_connection ref-counting in libmsc.

Remove/Disable VTY and CTRL commands to create subscribers, which is now a task
of the OsmoHLR. Adjust the python tests accordingly.

Remove VTY cmd subscriber-keep-in-ram.

Use OSMO_GSUP_PORT = 4222 instead of 2222. See
I4222e21686c823985be8ff1f16b1182be8ad6175.

So far use the LAC from conn->bts, will be replaced by conn->lac in
Id3705236350d5f69e447046b0a764bbabc3d493c.

Related: OS#1592 OS#1974
Change-Id: I639544a6cdda77a3aafc4e3446a55393f60e4050
diff --git a/src/gprs/sgsn_main.c b/src/gprs/sgsn_main.c
index 04f2825..41cebef 100644
--- a/src/gprs/sgsn_main.c
+++ b/src/gprs/sgsn_main.c
@@ -40,6 +40,8 @@
 #include <osmocom/core/logging.h>
 #include <osmocom/core/stats.h>
 
+#include <osmocom/gsm/gsup.h>
+
 #include <osmocom/gprs/gprs_ns.h>
 #include <osmocom/gprs/gprs_bssgp.h>
 
@@ -83,6 +85,7 @@
 	.cfg = {
 		.gtp_statedir = "./",
 		.auth_policy = SGSN_AUTH_POLICY_CLOSED,
+		.gsup_server_port = OSMO_GSUP_PORT,
 	},
 };
 struct sgsn_instance *sgsn = &sgsn_inst;
diff --git a/src/libbsc/abis_rsl.c b/src/libbsc/abis_rsl.c
index 6ae790f..7ae3eeb 100644
--- a/src/libbsc/abis_rsl.c
+++ b/src/libbsc/abis_rsl.c
@@ -160,7 +160,7 @@
 		     gsm_ts_and_pchan_name(lchan->ts), log_name, chan_nr);
 
 	if (lchan->conn)
-		log_set_context(LOG_CTX_VLR_SUBSCR, lchan->conn->subscr);
+		log_set_context(LOG_CTX_VLR_SUBSCR, lchan->conn->vsub);
 
 	return lchan;
 }
@@ -1382,8 +1382,6 @@
 	if (lchan && lchan->conn) {
 		if (lchan->conn->bsub)
 			name = bsc_subscr_name(lchan->conn->bsub);
-		else if (lchan->conn->subscr)
-			name = lchan->conn->subscr->imsi;
 		else
 			name = lchan->name;
 	}
diff --git a/src/libbsc/bsc_api.c b/src/libbsc/bsc_api.c
index 7613cac..947644e 100644
--- a/src/libbsc/bsc_api.c
+++ b/src/libbsc/bsc_api.c
@@ -32,6 +32,7 @@
 #include <openbsc/debug.h>
 #include <openbsc/gsm_04_08.h>
 #include <openbsc/trau_mux.h>
+#include <openbsc/vlr.h>
 
 #include <osmocom/gsm/protocol/gsm_08_08.h>
 #include <osmocom/gsm/gsm48.h>
@@ -273,13 +274,14 @@
 	if (!conn)
 		return;
 
+	if (conn->network->bsc_api->conn_cleanup)
+		conn->network->bsc_api->conn_cleanup(conn);
 
-	if (conn->subscr) {
-		subscr_put(conn->subscr);
-		conn->subscr = NULL;
+	if (conn->vsub) {
+		LOGP(DNM, LOGL_ERROR, "conn->vsub should have been cleared.\n");
+		conn->vsub = NULL;
 	}
 
-
 	if (conn->ho_lchan) {
 		LOGP(DNM, LOGL_ERROR, "The ho_lchan should have been cleared.\n");
 		conn->ho_lchan->conn = NULL;
diff --git a/src/libbsc/bsc_init.c b/src/libbsc/bsc_init.c
index ec87a7b..b7135f1 100644
--- a/src/libbsc/bsc_init.c
+++ b/src/libbsc/bsc_init.c
@@ -38,6 +38,7 @@
 #include <openbsc/e1_config.h>
 #include <openbsc/common_bsc.h>
 #include <openbsc/pcu_if.h>
+#include <openbsc/osmo_msc.h>
 #include <limits.h>
 
 /* global pointer to the gsm network data structure */
@@ -512,6 +513,12 @@
 	bsc_gsmnet->name_long = talloc_strdup(bsc_gsmnet, "OpenBSC");
 	bsc_gsmnet->name_short = talloc_strdup(bsc_gsmnet, "OpenBSC");
 
+	/* TODO: move to libmsc when gsm_network is split between libbsc and
+	 * libmsc */
+	bsc_gsmnet->gsup_server_addr_str = talloc_strdup(bsc_gsmnet,
+							 MSC_HLR_REMOTE_IP_DEFAULT);
+	bsc_gsmnet->gsup_server_port = MSC_HLR_REMOTE_PORT_DEFAULT;
+
 	return 0;
 }
 
diff --git a/src/libbsc/bsc_vty.c b/src/libbsc/bsc_vty.c
index 9fc2895..3bd56ea 100644
--- a/src/libbsc/bsc_vty.c
+++ b/src/libbsc/bsc_vty.c
@@ -54,8 +54,8 @@
 #include <openbsc/bsc_msc_data.h>
 #include <openbsc/osmo_bsc_rf.h>
 #include <openbsc/pcu_if.h>
-
 #include <openbsc/common_cs.h>
+#include <openbsc/vlr.h>
 
 #include <inttypes.h>
 
@@ -173,10 +173,6 @@
 	if (net->authorized_reg_str)
 		vty_out(vty, ", authorized regexp: %s", net->authorized_reg_str);
 	vty_out(vty, "%s", VTY_NEWLINE);
-	vty_out(vty, "  Auto create subscriber: %s%s",
-		net->auto_create_subscr ? "yes" : "no", VTY_NEWLINE);
-	vty_out(vty, "  Auto assign extension: %s%s",
-		net->auto_assign_exten ? "yes" : "no", VTY_NEWLINE);
 	vty_out(vty, "  Location updating reject cause: %u%s",
 		net->reject_cause, VTY_NEWLINE);
 	vty_out(vty, "  Encryption: A5/%u%s", net->a5_encryption,
@@ -801,6 +797,9 @@
 	vty_out(vty, " location updating reject cause %u%s",
 		gsmnet->reject_cause, VTY_NEWLINE);
 	vty_out(vty, " encryption a5 %u%s", gsmnet->a5_encryption, VTY_NEWLINE);
+	vty_out(vty, " authentication %s%s",
+		gsmnet->authentication_required ?  "required" : "optional",
+		VTY_NEWLINE);
 	vty_out(vty, " neci %u%s", gsmnet->neci, VTY_NEWLINE);
 	vty_out(vty, " paging any use tch %d%s", gsmnet->pag_any_tch, VTY_NEWLINE);
 	vty_out(vty, " rrlp mode %s%s", rrlp_mode_name(gsmnet->rrlp.mode),
@@ -833,8 +832,6 @@
 	vty_out(vty, " timer t3141 %u%s", gsmnet->T3141, VTY_NEWLINE);
 	vty_out(vty, " dyn_ts_allow_tch_f %d%s",
 		gsmnet->dyn_ts_allow_tch_f ? 1 : 0, VTY_NEWLINE);
-	vty_out(vty, " subscriber-keep-in-ram %d%s",
-		gsmnet->subscr_group->keep_subscr, VTY_NEWLINE);
 	if (gsmnet->tz.override != 0) {
 		if (gsmnet->tz.dst)
 			vty_out(vty, " timezone %d %d %d%s",
@@ -1018,21 +1015,24 @@
 	return CMD_SUCCESS;
 }
 
-static void subscr_dump_vty(struct vty *vty, struct gsm_subscriber *subscr)
+static void subscr_dump_vty(struct vty *vty, struct vlr_subscr *vsub)
 {
-	vty_out(vty, "    ID: %llu, Authorized: %d%s", subscr->id,
-		subscr->authorized, VTY_NEWLINE);
-	if (strlen(subscr->name))
-		vty_out(vty, "    Name: '%s'%s", subscr->name, VTY_NEWLINE);
-	if (strlen(subscr->extension))
-		vty_out(vty, "    Extension: %s%s", subscr->extension,
+	OSMO_ASSERT(vsub);
+	if (strlen(vsub->name))
+		vty_out(vty, "    Name: '%s'%s", vsub->name, VTY_NEWLINE);
+	if (strlen(vsub->msisdn))
+		vty_out(vty, "    Extension: %s%s", vsub->msisdn,
 			VTY_NEWLINE);
-	vty_out(vty, "    IMSI: %s%s", subscr->imsi, VTY_NEWLINE);
-	if (subscr->tmsi != GSM_RESERVED_TMSI)
-		vty_out(vty, "    TMSI: %08X%s", subscr->tmsi,
+	if (strlen(vsub->imsi))
+		vty_out(vty, "    IMSI: %s%s", vsub->imsi, VTY_NEWLINE);
+	if (vsub->tmsi != GSM_RESERVED_TMSI)
+		vty_out(vty, "    TMSI: %08X%s", vsub->tmsi,
+			VTY_NEWLINE);
+	if (vsub->tmsi_new != GSM_RESERVED_TMSI)
+		vty_out(vty, "    new TMSI: %08X%s", vsub->tmsi_new,
 			VTY_NEWLINE);
 
-	vty_out(vty, "    Use count: %u%s", subscr->use_count, VTY_NEWLINE);
+	vty_out(vty, "    Use count: %u%s", vsub->use_count, VTY_NEWLINE);
 }
 
 static void bsc_subscr_dump_vty(struct vty *vty, struct bsc_subscr *bsub)
@@ -1160,9 +1160,9 @@
 	vty_out(vty, "  Channel Mode / Codec: %s%s",
 		get_value_string(gsm48_cmode_names, lchan->tch_mode),
 		VTY_NEWLINE);
-	if (lchan->conn && lchan->conn->subscr) {
+	if (lchan->conn && lchan->conn->vsub) {
 		vty_out(vty, "  Subscriber:%s", VTY_NEWLINE);
-		subscr_dump_vty(vty, lchan->conn->subscr);
+		subscr_dump_vty(vty, lchan->conn->vsub);
 	} else
 		vty_out(vty, "  No Subscriber%s", VTY_NEWLINE);
 	if (is_ipaccess_bts(lchan->ts->trx->bts)) {
diff --git a/src/libbsc/handover_logic.c b/src/libbsc/handover_logic.c
index 4dd913b..c03563f 100644
--- a/src/libbsc/handover_logic.c
+++ b/src/libbsc/handover_logic.c
@@ -40,6 +40,7 @@
 #include <osmocom/core/talloc.h>
 #include <openbsc/transaction.h>
 #include <openbsc/trau_mux.h>
+#include <openbsc/vlr.h>
 
 struct bsc_handover {
 	struct llist_head list;
@@ -260,7 +261,7 @@
 
 	net = new_lchan->ts->trx->bts->network;
 	LOGP(DHO, LOGL_INFO, "Subscriber %s HO from BTS %u->%u on ARFCN "
-	     "%u->%u\n", subscr_name(ho->old_lchan->conn->subscr),
+	     "%u->%u\n", vlr_subscr_name(ho->old_lchan->conn->vsub),
 	     ho->old_lchan->ts->trx->bts->nr, new_lchan->ts->trx->bts->nr,
 	     ho->old_lchan->ts->trx->arfcn, new_lchan->ts->trx->arfcn);
 
diff --git a/src/libbsc/paging.c b/src/libbsc/paging.c
index 78e39c5..8c1445c 100644
--- a/src/libbsc/paging.c
+++ b/src/libbsc/paging.c
@@ -50,6 +50,7 @@
 #include <openbsc/gsm_data.h>
 #include <openbsc/chan_alloc.h>
 #include <openbsc/bsc_api.h>
+#include <openbsc/vlr.h>
 
 void *tall_paging_ctx;
 
diff --git a/src/libcommon-cs/common_cs.c b/src/libcommon-cs/common_cs.c
index 7905802..fc9caaf 100644
--- a/src/libcommon-cs/common_cs.c
+++ b/src/libcommon-cs/common_cs.c
@@ -49,20 +49,10 @@
 	if (!net)
 		return NULL;
 
-	net->subscr_group = talloc_zero(net, struct gsm_subscriber_group);
-	if (!net->subscr_group) {
-		talloc_free(net);
-		return NULL;
-	}
-
 	if (gsm_parse_reg(net, &net->authorized_regexp, &net->authorized_reg_str, 1,
 			  &default_regexp) != 0)
 		return NULL;
 
-	net->subscr_group->net = net;
-	net->auto_create_subscr = true;
-	net->auto_assign_exten = true;
-
 	net->country_code = country_code;
 	net->network_code = network_code;
 
@@ -78,8 +68,6 @@
 	net->active_calls = osmo_counter_alloc("msc.active_calls");
 
 	net->mncc_recv = mncc_recv;
-	net->ext_min = GSM_MIN_EXTEN;
-	net->ext_max = GSM_MAX_EXTEN;
 
 	net->dyn_ts_allow_tch_f = true;
 
diff --git a/src/libcommon-cs/common_cs_vty.c b/src/libcommon-cs/common_cs_vty.c
index bcc001d..86b4c53 100644
--- a/src/libcommon-cs/common_cs_vty.c
+++ b/src/libcommon-cs/common_cs_vty.c
@@ -168,6 +168,20 @@
 	return CMD_SUCCESS;
 }
 
+DEFUN(cfg_net_authentication,
+      cfg_net_authentication_cmd,
+      "authentication (optional|required)",
+	"Whether to enforce MS authentication in 2G\n"
+	"Allow MS to attach via 2G BSC without authentication\n"
+	"Always do authentication\n")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	gsmnet->authentication_required = (argv[0][0] == 'r') ? true : false;
+
+	return CMD_SUCCESS;
+}
+
 DEFUN(cfg_net_rrlp_mode, cfg_net_rrlp_mode_cmd,
       "rrlp mode (none|ms-based|ms-preferred|ass-preferred)",
 	"Radio Resource Location Protocol\n"
@@ -209,17 +223,6 @@
 	return CMD_SUCCESS;
 }
 
-DEFUN(cfg_net_subscr_keep,
-      cfg_net_subscr_keep_cmd,
-      "subscriber-keep-in-ram (0|1)",
-      "Keep unused subscribers in RAM.\n"
-      "Delete unused subscribers\n" "Keep unused subscribers\n")
-{
-	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
-	gsmnet->subscr_group->keep_subscr = atoi(argv[0]);
-	return CMD_SUCCESS;
-}
-
 DEFUN(cfg_net_timezone,
       cfg_net_timezone_cmd,
       "timezone <-19-19> (0|15|30|45)",
@@ -301,9 +304,9 @@
 	install_element(GSMNET_NODE, &cfg_net_authorize_regexp_cmd);
 	install_element(GSMNET_NODE, &cfg_net_reject_cause_cmd);
 	install_element(GSMNET_NODE, &cfg_net_encryption_cmd);
+	install_element(GSMNET_NODE, &cfg_net_authentication_cmd);
 	install_element(GSMNET_NODE, &cfg_net_rrlp_mode_cmd);
 	install_element(GSMNET_NODE, &cfg_net_mm_info_cmd);
-	install_element(GSMNET_NODE, &cfg_net_subscr_keep_cmd);
 	install_element(GSMNET_NODE, &cfg_net_timezone_cmd);
 	install_element(GSMNET_NODE, &cfg_net_timezone_dst_cmd);
 	install_element(GSMNET_NODE, &cfg_net_no_timezone_cmd);
diff --git a/src/libcommon/debug.c b/src/libcommon/debug.c
index 088902a..dc79a84 100644
--- a/src/libcommon/debug.c
+++ b/src/libcommon/debug.c
@@ -32,7 +32,6 @@
 #include <osmocom/core/logging.h>
 #include <osmocom/gprs/gprs_msgb.h>
 #include <openbsc/gsm_data.h>
-#include <openbsc/gsm_subscriber.h>
 #include <openbsc/debug.h>
 
 /* default categories */
@@ -189,13 +188,13 @@
 
 static int filter_fn(const struct log_context *ctx, struct log_target *tar)
 {
-	const struct gsm_subscriber *subscr = ctx->ctx[LOG_CTX_VLR_SUBSCR];
+	const struct vlr_subscr *vsub = ctx->ctx[LOG_CTX_VLR_SUBSCR];
 	const struct bsc_subscr *bsub = ctx->ctx[LOG_CTX_BSC_SUBSCR];
 	const struct gprs_nsvc *nsvc = ctx->ctx[LOG_CTX_GB_NSVC];
 	const struct gprs_nsvc *bvc = ctx->ctx[LOG_CTX_GB_BVC];
 
 	if ((tar->filter_map & (1 << LOG_FLT_VLR_SUBSCR)) != 0
-	    && subscr && subscr == tar->filter_data[LOG_FLT_VLR_SUBSCR])
+	    && vsub && vsub == tar->filter_data[LOG_FLT_VLR_SUBSCR])
 		return 1;
 
 	if ((tar->filter_map & (1 << LOG_FLT_BSC_SUBSCR)) != 0
@@ -220,21 +219,3 @@
 	.cat = default_categories,
 	.num_cat = ARRAY_SIZE(default_categories),
 };
-
-void log_set_filter_vlr_subscr(struct log_target *target,
-			       struct gsm_subscriber *vlr_subscr)
-{
-	struct gsm_subscriber **fsub = (void*)&target->filter_data[LOG_FLT_VLR_SUBSCR];
-
-	/* free the old data */
-	if (*fsub) {
-		subscr_put(*fsub);
-		*fsub = NULL;
-	}
-
-	if (vlr_subscr) {
-		target->filter_map |= (1 << LOG_FLT_VLR_SUBSCR);
-		*fsub = subscr_get(vlr_subscr);
-	} else
-		target->filter_map &= ~(1 << LOG_FLT_VLR_SUBSCR);
-}
diff --git a/src/libcommon/gsm_data.c b/src/libcommon/gsm_data.c
index db7de08..7c717a4 100644
--- a/src/libcommon/gsm_data.c
+++ b/src/libcommon/gsm_data.c
@@ -471,3 +471,13 @@
 		cell_options->radio_link_timeout = (value >> 2) - 1;
 	}
 }
+
+bool classmark_is_r99(struct gsm_classmark *cm)
+{
+	int rev_lev = 0;
+	if (cm->classmark1_set)
+		rev_lev = cm->classmark1.rev_lev;
+	else if (cm->classmark2_len > 0)
+		rev_lev = (cm->classmark2[0] >> 5) & 0x3;
+	return rev_lev >= 2;
+}
diff --git a/src/libcommon/gsm_subscriber_base.c b/src/libcommon/gsm_subscriber_base.c
index 1ecdee5..018ed21 100644
--- a/src/libcommon/gsm_subscriber_base.c
+++ b/src/libcommon/gsm_subscriber_base.c
@@ -31,133 +31,34 @@
 #include <osmocom/core/utils.h>
 #include <openbsc/gsm_subscriber.h>
 #include <openbsc/debug.h>
+#include <openbsc/vlr.h>
 
 LLIST_HEAD(active_subscribers);
 void *tall_subscr_ctx;
 
-/* for the gsm_subscriber.c */
-struct llist_head *subscr_bsc_active_subscribers(void)
+/* return static buffer with printable name of VLR subscriber */
+const char *vlr_subscr_name(struct vlr_subscr *vsub)
 {
-	return &active_subscribers;
-}
-
-
-char *subscr_name(struct gsm_subscriber *subscr)
-{
-	if (!subscr)
+	static char buf[32];
+	if (!vsub)
 		return "unknown";
-
-	if (strlen(subscr->name))
-		return subscr->name;
-
-	return subscr->imsi;
+	if (vsub->msisdn[0])
+		snprintf(buf, sizeof(buf), "MSISDN:%s", vsub->msisdn);
+	else if (vsub->imsi[0])
+		snprintf(buf, sizeof(buf), "IMSI:%s", vsub->imsi);
+	else if (vsub->tmsi != GSM_RESERVED_TMSI)
+		snprintf(buf, sizeof(buf), "TMSI:0x%08x", vsub->tmsi);
+	else if (vsub->tmsi_new != GSM_RESERVED_TMSI)
+		snprintf(buf, sizeof(buf), "TMSI(new):0x%08x", vsub->tmsi_new);
+	else
+		return "unknown";
+	buf[sizeof(buf)-1] = '\0';
+	return buf;
 }
 
-struct gsm_subscriber *subscr_alloc(void)
+const char *vlr_subscr_msisdn_or_name(struct vlr_subscr *vsub)
 {
-	struct gsm_subscriber *s;
-
-	s = talloc_zero(tall_subscr_ctx, struct gsm_subscriber);
-	if (!s)
-		return NULL;
-
-	llist_add_tail(&s->entry, &active_subscribers);
-	s->use_count = 1;
-	s->tmsi = GSM_RESERVED_TMSI;
-
-	INIT_LLIST_HEAD(&s->requests);
-
-	return s;
-}
-
-static void subscr_free(struct gsm_subscriber *subscr)
-{
-	llist_del(&subscr->entry);
-	talloc_free(subscr);
-}
-
-void subscr_direct_free(struct gsm_subscriber *subscr)
-{
-	OSMO_ASSERT(subscr->use_count == 1);
-	subscr_free(subscr);
-}
-
-struct gsm_subscriber *subscr_get(struct gsm_subscriber *subscr)
-{
-	subscr->use_count++;
-	DEBUGP(DREF, "subscr %s usage increases usage to: %d\n",
-			subscr->extension, subscr->use_count);
-	return subscr;
-}
-
-struct gsm_subscriber *subscr_put(struct gsm_subscriber *subscr)
-{
-	subscr->use_count--;
-	DEBUGP(DREF, "subscr %s usage decreased usage to: %d\n",
-			subscr->extension, subscr->use_count);
-	if (subscr->use_count <= 0 &&
-	    !((subscr->group && subscr->group->keep_subscr) ||
-	      subscr->keep_in_ram))
-		subscr_free(subscr);
-	return NULL;
-}
-
-struct gsm_subscriber *subscr_get_or_create(struct gsm_subscriber_group *sgrp,
-					    const char *imsi)
-{
-	struct gsm_subscriber *subscr;
-
-	llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) {
-		if (strcmp(subscr->imsi, imsi) == 0 && subscr->group == sgrp)
-			return subscr_get(subscr);
-	}
-
-	subscr = subscr_alloc();
-	if (!subscr)
-		return NULL;
-
-	osmo_strlcpy(subscr->imsi, imsi, sizeof(subscr->imsi));
-	subscr->group = sgrp;
-	return subscr;
-}
-
-struct gsm_subscriber *subscr_active_by_tmsi(struct gsm_subscriber_group *sgrp,
-					     uint32_t tmsi)
-{
-	struct gsm_subscriber *subscr;
-
-	llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) {
-		if (subscr->tmsi == tmsi && subscr->group == sgrp)
-			return subscr_get(subscr);
-	}
-
-	return NULL;
-}
-
-struct gsm_subscriber *subscr_active_by_imsi(struct gsm_subscriber_group *sgrp,
-					     const char *imsi)
-{
-	struct gsm_subscriber *subscr;
-
-	llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) {
-		if (strcmp(subscr->imsi, imsi) == 0 && subscr->group == sgrp)
-			return subscr_get(subscr);
-	}
-
-	return NULL;
-}
-
-int subscr_purge_inactive(struct gsm_subscriber_group *sgrp)
-{
-	struct gsm_subscriber *subscr, *tmp;
-	int purged = 0;
-
-	llist_for_each_entry_safe(subscr, tmp, subscr_bsc_active_subscribers(), entry) {
-		if (subscr->group == sgrp && subscr->use_count <= 0) {
-			subscr_free(subscr);
-			purged += 1;
-		}
-	}
-
-	return purged;
+	if (!vsub || !vsub->msisdn[0])
+		return vlr_subscr_name(vsub);
+	return vsub->msisdn;
 }
diff --git a/src/libcommon/gsup_client.c b/src/libcommon/gsup_client.c
index 46f25bb..258f230 100644
--- a/src/libcommon/gsup_client.c
+++ b/src/libcommon/gsup_client.c
@@ -72,12 +72,12 @@
 	rc = ipa_client_conn_open(gsupc->link);
 
 	if (rc >= 0) {
-		LOGP(DLGSUP, LOGL_INFO, "GSUP connecting to %s:%d\n",
+		LOGP(DLGSUP, LOGL_NOTICE, "GSUP connecting to %s:%d\n",
 		     gsupc->link->addr, gsupc->link->port);
 		return 0;
 	}
 
-	LOGP(DLGSUP, LOGL_INFO, "GSUP failed to connect to %s:%d: %s\n",
+	LOGP(DLGSUP, LOGL_ERROR, "GSUP failed to connect to %s:%d: %s\n",
 	     gsupc->link->addr, gsupc->link->port, strerror(-rc));
 
 	if (rc == -EBADF || rc == -ENOTSOCK || rc == -EAFNOSUPPORT ||
@@ -331,11 +331,15 @@
 int gsup_client_send(struct gsup_client *gsupc, struct msgb *msg)
 {
 	if (!gsupc) {
+		LOGP(DGPRS, LOGL_NOTICE, "No GSUP client, unable to "
+			"send %s\n", msgb_hexdump(msg));
 		msgb_free(msg);
 		return -ENOTCONN;
 	}
 
 	if (!gsupc->is_connected) {
+		LOGP(DGPRS, LOGL_NOTICE, "GSUP not connected, unable to "
+			"send %s\n", msgb_hexdump(msg));
 		msgb_free(msg);
 		return -EAGAIN;
 	}
diff --git a/src/libcommon/gsup_test_client.c b/src/libcommon/gsup_test_client.c
index 1b39670..b6a8d6b 100644
--- a/src/libcommon/gsup_test_client.c
+++ b/src/libcommon/gsup_test_client.c
@@ -271,7 +271,7 @@
 {
 	unsigned long long i;
 	char *server_host = "127.0.0.1";
-	uint16_t server_port = 2222;
+	uint16_t server_port = OSMO_GSUP_PORT;
 
 	osmo_init_logging(&gsup_test_client_log_info);
 
diff --git a/src/libmsc/Makefile.am b/src/libmsc/Makefile.am
index 9d966db..bb2a4a1 100644
--- a/src/libmsc/Makefile.am
+++ b/src/libmsc/Makefile.am
@@ -35,13 +35,13 @@
 	rrlp.c \
 	silent_call.c \
 	sms_queue.c \
-	token_auth.c \
 	ussd.c \
 	vty_interface_layer3.c \
 	transaction.c \
 	osmo_msc.c \
 	ctrl_commands.c \
 	meas_feed.c \
+	subscr_conn.c \
 	$(NULL)
 
 if BUILD_SMPP
diff --git a/src/libmsc/auth.c b/src/libmsc/auth.c
index 19def1e..9064ce6 100644
--- a/src/libmsc/auth.c
+++ b/src/libmsc/auth.c
@@ -40,118 +40,3 @@
 	OSMO_VALUE_STRING(AUTH_DO_AUTH),
 	{ 0, NULL }
 };
-
-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,
-			osmo_hexdump(ainfo->a3a8_ki, ainfo->a3a8_ki_len));
-		return -1;
-	}
-
-	for (i=0; i<4; i++)
-		atuple->vec.sres[i] = atuple->vec.rand[i] ^ ainfo->a3a8_ki[i];
-	for (i=4; i<12; i++)
-		atuple->vec.kc[i-4] = atuple->vec.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,
-			osmo_hexdump(ainfo->a3a8_ki, ainfo->a3a8_ki_len));
-		return -1;
-	}
-
-	comp128(ainfo->a3a8_ki, atuple->vec.rand, atuple->vec.sres, atuple->vec.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 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 %s, skipping auth\n",
-		     subscr_name(subscr));
-		return rc == -ENOENT ? AUTH_NOT_AVAIL : AUTH_ERROR;
-	}
-
-	/* 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) &&
-	    (key_seq == atuple->key_seq) &&
-	    (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 */
-	if (rc != 0) {
-		/* If db_get_lastauthtuple_for_subscr() returned nothing, make
-		 * sure the atuple memory is initialized to zero and thus start
-		 * off with key_seq = 0. */
-		memset(atuple, 0, sizeof(*atuple));
-	} else {
-		/* If db_get_lastauthtuple_for_subscr() returned a previous
-		 * tuple, use the next key_seq. */
-		atuple->key_seq = (atuple->key_seq + 1) % 7;
-	}
-	atuple->use_count = 1;
-
-	if (RAND_bytes(atuple->vec.rand, sizeof(atuple->vec.rand)) != 1) {
-		LOGP(DMM, LOGL_NOTICE, "RAND_bytes failed, can't generate new auth tuple\n");
-		return AUTH_ERROR;
-	}
-
-	switch (ainfo.auth_algo) {
-	case AUTH_ALGO_NONE:
-		DEBUGP(DMM, "No authentication for subscriber\n");
-		return AUTH_NOT_AVAIL;
-
-	case AUTH_ALGO_XOR:
-		if (_use_xor(&ainfo, atuple))
-			return AUTH_NOT_AVAIL;
-		break;
-
-	case AUTH_ALGO_COMP128v1:
-		if (_use_comp128_v1(&ainfo, atuple))
-			return AUTH_NOT_AVAIL;
-		break;
-
-	default:
-		DEBUGP(DMM, "Unsupported auth type algo_id=%d\n",
-			ainfo.auth_algo);
-		return AUTH_NOT_AVAIL;
-	}
-
-        db_sync_lastauthtuple_for_subscr(atuple, subscr);
-
-	DEBUGP(DMM, "Need to do authentication and ciphering\n");
-	return AUTH_DO_AUTH_THEN_CIPH;
-}
-
diff --git a/src/libmsc/ctrl_commands.c b/src/libmsc/ctrl_commands.c
index c99dde4..9d1f0d4 100644
--- a/src/libmsc/ctrl_commands.c
+++ b/src/libmsc/ctrl_commands.c
@@ -25,129 +25,20 @@
 #include <openbsc/gsm_subscriber.h>
 #include <openbsc/db.h>
 #include <openbsc/debug.h>
+#include <openbsc/vlr.h>
 
 #include <stdbool.h>
 
-static bool alg_supported(const char *alg)
-{
-	/*
-	 * TODO: share this with the vty_interface and extend to all
-	 * algorithms supported by libosmocore now. Make it table based
-	 * as well.
-	 */
-	if (strcasecmp(alg, "none") == 0)
-		return true;
-	if (strcasecmp(alg, "xor") == 0)
-		return true;
-	if (strcasecmp(alg, "comp128v1") == 0)
-		return true;
-	return false;
-}
+static struct gsm_network *msc_ctrl_net = NULL;
 
 static int verify_subscriber_modify(struct ctrl_cmd *cmd, const char *value, void *d)
 {
-	char *tmp, *imsi, *msisdn, *alg, *ki, *saveptr = NULL;
-	int rc = 0;
-
-	tmp = talloc_strdup(cmd, value);
-	if (!tmp)
-		return 1;
-
-	imsi = strtok_r(tmp, ",", &saveptr);
-	msisdn = strtok_r(NULL, ",", &saveptr);
-	alg = strtok_r(NULL, ",", &saveptr);
-	ki = strtok_r(NULL, ",", &saveptr);
-
-	if (!imsi || !msisdn)
-		rc = 1;
-	else if (strlen(imsi) > GSM23003_IMSI_MAX_DIGITS)
-		rc = 1;
-	else if (strlen(msisdn) >= GSM_EXTENSION_LENGTH)
-		rc = 1;
-	else if (alg) {
-		if (!alg_supported(alg))
-			rc = 1;
-		else if (strcasecmp(alg, "none") != 0 && !ki)
-			rc = 1;
-	}
-
-	talloc_free(tmp);
-	return rc;
+	return 0;
 }
 
 static int set_subscriber_modify(struct ctrl_cmd *cmd, void *data)
 {
-	struct gsm_network *net = cmd->node;
-	char *tmp, *imsi, *msisdn, *alg, *ki, *saveptr = NULL;
-	struct gsm_subscriber* subscr;
-	int rc;
-
-	tmp = talloc_strdup(cmd, cmd->value);
-	if (!tmp)
-		return 1;
-
-	imsi = strtok_r(tmp, ",", &saveptr);
-	msisdn = strtok_r(NULL, ",", &saveptr);
-	alg = strtok_r(NULL, ",", &saveptr);
-	ki = strtok_r(NULL, ",", &saveptr);
-
-	subscr = subscr_get_by_imsi(net->subscr_group, imsi);
-	if (!subscr)
-		subscr = subscr_create_subscriber(net->subscr_group, imsi);
-	if (!subscr)
-		goto fail;
-
-	subscr->authorized = 1;
-	osmo_strlcpy(subscr->extension, msisdn, sizeof(subscr->extension));
-
-	/* put it back to the db */
-	rc = db_sync_subscriber(subscr);
-	db_subscriber_update(subscr);
-
-	/* handle optional ciphering */
-	if (alg) {
-		if (strcasecmp(alg, "none") == 0)
-			db_sync_authinfo_for_subscr(NULL, subscr);
-		else {
-			struct gsm_auth_info ainfo = { 0, };
-			/* the verify should make sure that this is okay */
-			OSMO_ASSERT(alg);
-			OSMO_ASSERT(ki);
-
-			if (strcasecmp(alg, "xor") == 0)
-				ainfo.auth_algo = AUTH_ALGO_XOR;
-			else if (strcasecmp(alg, "comp128v1") == 0)
-				ainfo.auth_algo = AUTH_ALGO_COMP128v1;
-
-			rc = osmo_hexparse(ki, ainfo.a3a8_ki, sizeof(ainfo.a3a8_ki));
-			if (rc < 0) {
-				subscr_put(subscr);
-				talloc_free(tmp);
-				cmd->reply = "Failed to parse KI";
-				return CTRL_CMD_ERROR;
-			}
-
-			ainfo.a3a8_ki_len = rc;
-			db_sync_authinfo_for_subscr(&ainfo, subscr);
-			rc = 0;
-		}
-		db_sync_lastauthtuple_for_subscr(NULL, subscr);
-	}
-	subscr_put(subscr);
-
-	talloc_free(tmp);
-
-	if (rc != 0) {
-		cmd->reply = "Failed to store the record in the DB";
-		return CTRL_CMD_ERROR;
-	}
-
-	cmd->reply = "OK";
-	return CTRL_CMD_REPLY;
-
-fail:
-	talloc_free(tmp);
-	cmd->reply = "Failed to create subscriber";
+	cmd->reply = "Command moved to osmo-hlr, no longer available here";
 	return CTRL_CMD_ERROR;
 }
 
@@ -155,55 +46,40 @@
 
 static int set_subscriber_delete(struct ctrl_cmd *cmd, void *data)
 {
-	int was_used = 0;
-	int rc;
-	struct gsm_subscriber *subscr;
-	struct gsm_network *net = cmd->node;
-
-	subscr = subscr_get_by_imsi(net->subscr_group, cmd->value);
-	if (!subscr) {
-		cmd->reply = "Failed to find subscriber";
-		return CTRL_CMD_ERROR;
-	}
-
-	if (subscr->use_count != 1) {
-		LOGP(DCTRL, LOGL_NOTICE, "Going to remove active subscriber.\n");
-		was_used = 1;
-	}
-
-	rc = db_subscriber_delete(subscr);
-	subscr_put(subscr);
-
-	if (rc != 0) {
-		cmd->reply = "Failed to remove subscriber";
-		return CTRL_CMD_ERROR;
-	}
-
-	cmd->reply = was_used ? "Removed active subscriber" : "Removed";
-	return CTRL_CMD_REPLY;
+	cmd->reply = "Command moved to osmo-hlr, no longer available here";
+	return CTRL_CMD_ERROR;
 }
 CTRL_CMD_DEFINE_WO_NOVRF(subscriber_delete, "subscriber-delete-v1");
 
-static void list_cb(struct gsm_subscriber *subscr, void *d)
-{
-	char **data = (char **) d;
-	*data = talloc_asprintf_append(*data, "%s,%s\n",
-				subscr->imsi, subscr->extension);
-}
-
 static int get_subscriber_list(struct ctrl_cmd *cmd, void *d)
 {
+	struct vlr_subscr *vsub;
+
+	if (!msc_ctrl_net) {
+		cmd->reply = "MSC CTRL commands not initialized";
+		return CTRL_CMD_ERROR;
+	}
+
+	if (!msc_ctrl_net->vlr) {
+		cmd->reply = "VLR not initialized";
+		return CTRL_CMD_ERROR;
+	}
+
 	cmd->reply = talloc_strdup(cmd, "");
 
-	db_subscriber_list_active(list_cb, &cmd->reply);
-	printf("%s\n", cmd->reply);
+	llist_for_each_entry(vsub, &msc_ctrl_net->vlr->subscribers, list) {
+		cmd->reply = talloc_asprintf_append(cmd->reply, "%s,%s\n",
+						    vsub->imsi, vsub->msisdn);
+	}
+	printf("%s\n", cmd->reply); /* <-- what? */
 	return CTRL_CMD_REPLY;
 }
 CTRL_CMD_DEFINE_RO(subscriber_list, "subscriber-list-active-v1");
 
-int msc_ctrl_cmds_install(void)
+int msc_ctrl_cmds_install(struct gsm_network *net)
 {
 	int rc = 0;
+	msc_ctrl_net = net;
 
 	rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_subscriber_modify);
 	rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_subscriber_delete);
diff --git a/src/libmsc/db.c b/src/libmsc/db.c
index 5fe2a3c..28e9782 100644
--- a/src/libmsc/db.c
+++ b/src/libmsc/db.c
@@ -34,6 +34,7 @@
 #include <openbsc/gsm_04_11.h>
 #include <openbsc/db.h>
 #include <openbsc/debug.h>
+#include <openbsc/vlr.h>
 
 #include <osmocom/gsm/protocol/gsm_23_003.h>
 #include <osmocom/core/talloc.h>
@@ -43,9 +44,6 @@
 
 #include <openssl/rand.h>
 
-/* Semi-Private-Interface (SPI) for the subscriber code */
-void subscr_direct_free(struct gsm_subscriber *subscr);
-
 static char *db_basename = NULL;
 static char *db_dirname = NULL;
 static dbi_conn conn;
@@ -227,23 +225,33 @@
 {
 	struct gsm_sms *sms = sms_alloc();
 	long long unsigned int sender_id;
-	struct gsm_subscriber *sender;
 	const char *text, *daddr;
 	const unsigned char *user_data;
 	char buf[32];
+	char *quoted;
+	dbi_result result2;
+	const char *extension;
 
 	if (!sms)
 		return NULL;
 
 	sms->id = dbi_result_get_ulonglong(result, "id");
 
+	/* find extension by id, assuming that the subscriber still exists in
+	 * the db */
 	sender_id = dbi_result_get_ulonglong(result, "sender_id");
 	snprintf(buf, sizeof(buf), "%llu", sender_id);
-	sender = db_get_subscriber(GSM_SUBSCRIBER_ID, buf);
-	OSMO_ASSERT(sender);
-	osmo_strlcpy(sms->src.addr, sender->extension, sizeof(sms->src.addr));
-	subscr_direct_free(sender);
-	sender = NULL;
+
+	dbi_conn_quote_string_copy(conn, buf, &quoted);
+	result2 = dbi_conn_queryf(conn,
+				  "SELECT extension FROM Subscriber "
+				  "WHERE id = %s ", quoted);
+	free(quoted);
+	extension = dbi_result_get_string(result2, "extension");
+	if (extension)
+		osmo_strlcpy(sms->src.addr, extension, sizeof(sms->src.addr));
+	dbi_result_free(result2);
+	/* got the extension */
 
 	sms->reply_path_req = dbi_result_get_ulonglong(result, "reply_path_req");
 	sms->status_rep_req = dbi_result_get_ulonglong(result, "status_rep_req");
@@ -518,913 +526,6 @@
 	return 0;
 }
 
-struct gsm_subscriber *db_create_subscriber(const char *imsi, uint64_t smin,
-					    uint64_t smax, bool alloc_exten)
-{
-	dbi_result result;
-	struct gsm_subscriber *subscr;
-
-	/* Is this subscriber known in the db? */
-	subscr = db_get_subscriber(GSM_SUBSCRIBER_IMSI, imsi);
-	if (subscr) {
-		subscr_put(subscr);
-		return NULL;
-	}
-
-	subscr = subscr_alloc();
-	if (!subscr)
-		return NULL;
-	subscr->flags |= GSM_SUBSCRIBER_FIRST_CONTACT;
-	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_put(subscr);
-		return NULL;
-	}
-	subscr->id = dbi_conn_sequence_last(conn, NULL);
-	osmo_strlcpy(subscr->imsi, imsi, sizeof(subscr->imsi));
-	dbi_result_free(result);
-	LOGP(DDB, LOGL_INFO, "New Subscriber: ID %llu, IMSI %s\n", subscr->id, subscr->imsi);
-	if (alloc_exten)
-		db_subscriber_alloc_exten(subscr, smin, smax);
-	return subscr;
-}
-
-osmo_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)
-		osmo_strlcpy(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);
-	if (cm2)
-		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);
-	if (cm3)
-		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);
-	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->vec.rand))
-		goto err_size;
-
-	blob = dbi_result_get_binary(result, "rand");
-	memcpy(atuple->vec.rand, blob, len);
-
-	len = dbi_result_get_field_length(result, "sres");
-	if (len != sizeof(atuple->vec.sres))
-		goto err_size;
-
-	blob = dbi_result_get_binary(result, "sres");
-	memcpy(atuple->vec.sres, blob, len);
-
-	len = dbi_result_get_field_length(result, "kc");
-	if (len != sizeof(atuple->vec.kc))
-		goto err_size;
-
-	blob = dbi_result_get_binary(result, "kc");
-	memcpy(atuple->vec.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->vec.rand, sizeof(atuple->vec.rand), &rand_str);
-	dbi_conn_quote_binary_copy(conn,
-		atuple->vec.sres, sizeof(atuple->vec.sres), &sres_str);
-	dbi_conn_quote_binary_copy(conn,
-		atuple->vec.kc, sizeof(atuple->vec.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)
-		osmo_strlcpy(subscr->imsi, string, sizeof(subscr->imsi));
-
-	string = dbi_result_get_string(result, "tmsi");
-	if (string)
-		subscr->tmsi = tmsi_from_string(string);
-
-	string = dbi_result_get_string(result, "name");
-	if (string)
-		osmo_strlcpy(subscr->name, string, sizeof(subscr->name));
-
-	string = dbi_result_get_string(result, "extension");
-	if (string)
-		osmo_strlcpy(subscr->extension, string, sizeof(subscr->extension));
-
-	subscr->lac = dbi_result_get_ulonglong(result, "lac");
-
-	if (!dbi_result_field_is_null(result, "expire_lu"))
-		subscr->expire_lu = dbi_result_get_datetime(result, "expire_lu");
-	else
-		subscr->expire_lu = GSM_SUBSCRIBER_NO_EXPIRATION;
-
-	subscr->authorized = dbi_result_get_ulonglong(result, "authorized");
-
-}
-
-#define BASE_QUERY "SELECT * FROM Subscriber "
-struct gsm_subscriber *db_get_subscriber(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->id = dbi_result_get_ulonglong(result, "id");
-
-	db_set_from_query(subscr, result);
-	DEBUGP(DDB, "Found Subscriber: ID %llu, IMSI %s, NAME '%s', TMSI %x, 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);
-	if (subscriber->extension[0] != '\0')
-		dbi_conn_quote_string_copy(conn,
-					   subscriber->extension, &q_extension);
-	else
-		q_extension = strdup("NULL");
-	
-	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");
-
-	if (subscriber->expire_lu == GSM_SUBSCRIBER_NO_EXPIRATION) {
-		result = dbi_conn_queryf(conn,
-			"UPDATE Subscriber "
-			"SET updated = datetime('now'), "
-			"name = %s, "
-			"extension = %s, "
-			"authorized = %i, "
-			"tmsi = %s, "
-			"lac = %i, "
-			"expire_lu = NULL "
-			"WHERE imsi = %s ",
-			q_name,
-			q_extension,
-			subscriber->authorized,
-			q_tmsi,
-			subscriber->lac,
-			subscriber->imsi);
-	} else {
-		result = dbi_conn_queryf(conn,
-			"UPDATE Subscriber "
-			"SET updated = datetime('now'), "
-			"name = %s, "
-			"extension = %s, "
-			"authorized = %i, "
-			"tmsi = %s, "
-			"lac = %i, "
-			"expire_lu = datetime(%i, 'unixepoch') "
-			"WHERE imsi = %s ",
-			q_name,
-			q_extension,
-			subscriber->authorized,
-			q_tmsi,
-			subscriber->lac,
-			(int) subscriber->expire_lu,
-			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_subscriber_delete(struct gsm_subscriber *subscr)
-{
-	dbi_result result;
-
-	result = dbi_conn_queryf(conn,
-			"DELETE FROM AuthKeys WHERE subscriber_id=%llu",
-			subscr->id);
-	if (!result) {
-		LOGP(DDB, LOGL_ERROR,
-			"Failed to delete Authkeys for %llu\n", subscr->id);
-		return -1;
-	}
-	dbi_result_free(result);
-
-	result = dbi_conn_queryf(conn,
-			"DELETE FROM AuthLastTuples WHERE subscriber_id=%llu",
-			subscr->id);
-	if (!result) {
-		LOGP(DDB, LOGL_ERROR,
-			"Failed to delete AuthLastTuples for %llu\n", subscr->id);
-		return -1;
-	}
-	dbi_result_free(result);
-
-	result = dbi_conn_queryf(conn,
-			"DELETE FROM AuthToken WHERE subscriber_id=%llu",
-			subscr->id);
-	if (!result) {
-		LOGP(DDB, LOGL_ERROR,
-			"Failed to delete AuthToken for %llu\n", subscr->id);
-		return -1;
-	}
-	dbi_result_free(result);
-
-	result = dbi_conn_queryf(conn,
-			"DELETE FROM EquipmentWatch WHERE subscriber_id=%llu",
-			subscr->id);
-	if (!result) {
-		LOGP(DDB, LOGL_ERROR,
-			"Failed to delete EquipmentWatch for %llu\n", subscr->id);
-		return -1;
-	}
-	dbi_result_free(result);
-
-	if (subscr->extension[0] != '\0') {
-		result = dbi_conn_queryf(conn,
-			    "DELETE FROM SMS WHERE src_addr=%s OR dest_addr=%s",
-					 subscr->extension, subscr->extension);
-		if (!result) {
-			LOGP(DDB, LOGL_ERROR,
-			     "Failed to delete SMS for %llu\n", subscr->id);
-			return -1;
-		}
-		dbi_result_free(result);
-	}
-
-	result = dbi_conn_queryf(conn,
-			"DELETE FROM VLR WHERE subscriber_id=%llu",
-			subscr->id);
-	if (!result) {
-		LOGP(DDB, LOGL_ERROR,
-			"Failed to delete VLR for %llu\n", subscr->id);
-		return -1;
-	}
-	dbi_result_free(result);
-
-	result = dbi_conn_queryf(conn,
-			"DELETE FROM ApduBlobs WHERE subscriber_id=%llu",
-			subscr->id);
-	if (!result) {
-		LOGP(DDB, LOGL_ERROR,
-			"Failed to delete ApduBlobs for %llu\n", subscr->id);
-		return -1;
-	}
-	dbi_result_free(result);
-
-	result = dbi_conn_queryf(conn,
-			"DELETE FROM Subscriber WHERE id=%llu",
-			subscr->id);
-	if (!result) {
-		LOGP(DDB, LOGL_ERROR,
-			"Failed to delete Subscriber for %llu\n", subscr->id);
-		return -1;
-	}
-	dbi_result_free(result);
-
-	return 0;
-}
-
-/**
- * List all the authorized and non-expired subscribers. The callback will
- * be called one by one. The subscr argument is not fully initialize and
- * subscr_get/subscr_put must not be called. The passed in pointer will be
- * deleted after the callback by the database call.
- */
-int db_subscriber_list_active(void (*cb)(struct gsm_subscriber*,void*), void *closure)
-{
-	dbi_result result;
-
-	result = dbi_conn_query(conn,
-		       "SELECT * from Subscriber WHERE LAC != 0 AND authorized = 1");
-	if (!result) {
-		LOGP(DDB, LOGL_ERROR, "Failed to list active subscribers\n");
-		return -1;
-	}
-
-	while (dbi_result_next_row(result)) {
-		struct gsm_subscriber *subscr;
-
-		subscr = subscr_alloc();
-		subscr->id = dbi_result_get_ulonglong(result, "id");
-		db_set_from_query(subscr, result);
-		cb(subscr, closure);
-		OSMO_ASSERT(subscr->use_count == 1);
-		llist_del(&subscr->entry);
-		talloc_free(subscr);
-	}
-
-	dbi_result_free(result);
-	return 0;
-}
-
-int db_sync_equipment(struct gsm_equipment *equip)
-{
-	dbi_result result;
-	unsigned char *cm2, *cm3;
-	char *q_imei;
-	uint8_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",
-			osmo_hexdump(equip->classmark2, equip->classmark2_len));
-	if (equip->classmark3_len)
-		DEBUGPC(DDB, ", classmark3=%s",
-			osmo_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_expire(void *priv, void (*callback)(void *priv, long long unsigned int id))
-{
-	dbi_result result;
-
-	result = dbi_conn_query(conn,
-			"SELECT id "
-			"FROM Subscriber "
-			"WHERE lac != 0 AND "
-				"( expire_lu is NOT NULL "
-				"AND expire_lu < datetime('now') ) "
-			"LIMIT 1");
-	if (!result) {
-		LOGP(DDB, LOGL_ERROR, "Failed to get expired subscribers\n");
-		return -EIO;
-	}
-
-	while (dbi_result_next_row(result))
-		callback(priv, dbi_result_get_ulonglong(result, "id"));
-
-	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 (;;) {
-		if (RAND_bytes((uint8_t *) &subscriber->tmsi, sizeof(subscriber->tmsi)) != 1) {
-			LOGP(DDB, LOGL_ERROR, "RAND_bytes failed\n");
-			return 1;
-		}
-		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, uint64_t smin,
-			      uint64_t smax)
-{
-	dbi_result result = NULL;
-	uint64_t try;
-
-	for (;;) {
-		try = (rand() % (smax - smin + 1) + smin);
-		result = dbi_conn_queryf(conn,
-			"SELECT * FROM Subscriber "
-			"WHERE extension = %"PRIu64,
-			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, "%"PRIu64, try);
-	DEBUGP(DDB, "Allocated extension %"PRIu64 " 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, uint32_t *token)
-{
-	dbi_result result;
-	uint32_t try;
-
-	for (;;) {
-		if (RAND_bytes((uint8_t *) &try, sizeof(try)) != 1) {
-			LOGP(DDB, LOGL_ERROR, "RAND_bytes failed\n");
-			return 1;
-		}
-		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[GSM23003_IMEISV_NUM_DIGITS])
-{
-	unsigned long long equipment_id, watch_id;
-	dbi_result result;
-
-	osmo_strlcpy(subscriber->equipment.imei, imei, sizeof(subscriber->equipment.imei));
-
-	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)
 {
@@ -1500,7 +601,7 @@
 	daddr = dbi_result_get_string(result, "dest_addr");
 	if (daddr)
 		osmo_strlcpy(sms->dst.addr, daddr, sizeof(sms->dst.addr));
-	sms->receiver = subscr_get_by_extension(net->subscr_group, sms->dst.addr);
+	sms->receiver = vlr_subscr_find_by_msisdn(net->vlr, sms->dst.addr);
 
 	sms->src.npi = dbi_result_get_ulonglong(result, "src_npi");
 	sms->src.ton = dbi_result_get_ulonglong(result, "src_ton");
@@ -1542,50 +643,21 @@
 	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)
+struct gsm_sms *db_sms_get_next_unsent(struct gsm_network *net,
+				       unsigned long long min_sms_id,
+				       unsigned int max_failed)
 {
 	dbi_result result;
 	struct gsm_sms *sms;
 
 	result = dbi_conn_queryf(conn,
-		"SELECT SMS.* "
-			"FROM SMS JOIN Subscriber ON "
-				"SMS.dest_addr = Subscriber.extension "
-			"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;
+		"SELECT * FROM SMS"
+		" WHERE sent IS NULL"
+		" AND id >= %llu"
+		" AND deliver_attempts <= %u"
+		" ORDER BY id LIMIT 1",
+		min_sms_id, max_failed);
 
-	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.dest_addr = Subscriber.extension "
-			"WHERE Subscriber.id >= %llu AND SMS.sent IS NULL "
-				"AND Subscriber.lac > 0 AND SMS.deliver_attempts < %u "
-			"ORDER BY Subscriber.id, SMS.id LIMIT 1",
-		min_subscr_id, failed);
 	if (!result)
 		return NULL;
 
@@ -1602,19 +674,23 @@
 }
 
 /* retrieve the next unsent SMS for a given subscriber */
-struct gsm_sms *db_sms_get_unsent_for_subscr(struct gsm_subscriber *subscr)
+struct gsm_sms *db_sms_get_unsent_for_subscr(struct vlr_subscr *vsub,
+					     unsigned int max_failed)
 {
+	struct gsm_network *net = vsub->vlr->user_ctx;
 	dbi_result result;
 	struct gsm_sms *sms;
 
+	if (!vsub->lu_complete)
+		return NULL;
+
 	result = dbi_conn_queryf(conn,
-		"SELECT SMS.* "
-			"FROM SMS JOIN Subscriber ON "
-				"SMS.dest_addr = Subscriber.extension "
-			"WHERE Subscriber.id = %llu AND SMS.sent IS NULL "
-				"AND Subscriber.lac > 0 "
-			"ORDER BY SMS.id LIMIT 1",
-		subscr->id);
+		"SELECT * FROM SMS"
+		" WHERE sent IS NULL"
+		" AND dest_addr=%s"
+		" AND deliver_attempts <= %u"
+		" ORDER BY id LIMIT 1",
+		vsub->msisdn, max_failed);
 	if (!result)
 		return NULL;
 
@@ -1623,7 +699,36 @@
 		return NULL;
 	}
 
-	sms = sms_from_result(subscr->group->net, result);
+	sms = sms_from_result(net, result);
+
+	dbi_result_free(result);
+
+	return sms;
+}
+
+struct gsm_sms *db_sms_get_next_unsent_rr_msisdn(struct gsm_network *net,
+						 const char *last_msisdn,
+						 unsigned int max_failed)
+{
+	dbi_result result;
+	struct gsm_sms *sms;
+
+	result = dbi_conn_queryf(conn,
+		"SELECT * FROM SMS"
+		" WHERE sent IS NULL"
+		" AND dest_addr > '%s'"
+		" AND deliver_attempts <= %u"
+		" ORDER BY dest_addr, id LIMIT 1",
+		last_msisdn, max_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);
 
@@ -1667,26 +772,20 @@
 	return 0;
 }
 
-int db_apdu_blob_store(struct gsm_subscriber *subscr,
-			uint8_t apdu_id_flags, uint8_t len,
-			uint8_t *apdu)
+/* Drop all pending SMS to or from the given extension */
+int db_sms_delete_by_msisdn(const char *msisdn)
 {
 	dbi_result result;
-	unsigned char *q_apdu;
-
-	dbi_conn_quote_binary_copy(conn, apdu, len, &q_apdu);
-
+	if (!msisdn || !*msisdn)
+		return 0;
 	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;
-
+		    "DELETE FROM SMS WHERE src_addr=%s OR dest_addr=%s",
+		    msisdn, msisdn);
+	if (!result) {
+		LOGP(DDB, LOGL_ERROR,
+		     "Failed to delete SMS for %s\n", msisdn);
+		return -1;
+	}
 	dbi_result_free(result);
 	return 0;
 }
diff --git a/src/libmsc/gsm_04_08.c b/src/libmsc/gsm_04_08.c
index fb02de2..6cea242 100644
--- a/src/libmsc/gsm_04_08.c
+++ b/src/libmsc/gsm_04_08.c
@@ -1,7 +1,7 @@
 /* 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-2016 by Harald Welte <laforge@gnumonks.org>
  * (C) 2008-2012 by Holger Hans Peter Freyther <zecke@selfish.org>
  *
  * All Rights Reserved
@@ -58,6 +58,7 @@
 #include <openbsc/mncc_int.h>
 #include <osmocom/abis/e1_input.h>
 #include <osmocom/core/bitvec.h>
+#include <openbsc/vlr.h>
 
 #include <osmocom/gsm/gsm48.h>
 #include <osmocom/gsm/gsm0480.h>
@@ -75,11 +76,10 @@
 
 static int tch_rtp_signal(struct gsm_lchan *lchan, int signal);
 
-static int gsm0408_loc_upd_acc(struct gsm_subscriber_connection *conn);
+static int gsm0408_loc_upd_acc(struct gsm_subscriber_connection *conn,
+			       uint32_t send_tmsi);
 static int gsm48_tx_simple(struct gsm_subscriber_connection *conn,
 			   uint8_t pdisc, uint8_t msg_type);
-static void schedule_reject(struct gsm_subscriber_connection *conn);
-static void release_anchor(struct gsm_subscriber_connection *conn);
 
 struct gsm_lai {
 	uint16_t mcc;
@@ -169,298 +169,7 @@
 	return gsm48_conn_sendmsg(ss_notify, trans->conn, trans);
 }
 
-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);
-}
-
-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->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_ALREADY;
-	} 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_THEN_CIPH) {
-		/* Start authentication */
-		return gsm48_tx_mm_auth_req(conn, op->atuple.vec.rand, NULL,
-					    op->atuple.key_seq);
-	} else if (rc == AUTH_DO_CIPH) {
-		/* Start ciphering directly */
-		return gsm0808_cipher_mode(conn, net->a5_encryption,
-		                           op->atuple.vec.kc, 8, 0);
-	}
-
-	return -EINVAL; /* not reached */
-}
-
-static bool subscr_regexp_check(const struct gsm_network *net, const char *imsi)
-{
-	if (!net->authorized_reg_str)
-		return false;
-
-	if (regexec(&net->authorized_regexp, imsi, 0, NULL, 0) != REG_NOMATCH)
-		return true;
-
-	return false;
-}
-
-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->group->net->auth_policy) {
-	case GSM_AUTH_POLICY_CLOSED:
-		return subscriber->authorized;
-	case GSM_AUTH_POLICY_REGEXP:
-		if (subscriber->authorized)
-			return 1;
-		if (subscr_regexp_check(subscriber->group->net,
-					subscriber->imsi))
-			subscriber->authorized = 1;
-		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, int release)
-{
-	if (!conn->loc_operation)
-		return;
-
-	/* No need to keep the connection up */
-	release_anchor(conn);
-
-	osmo_timer_del(&conn->loc_operation->updating_timer);
-	talloc_free(conn->loc_operation);
-	conn->loc_operation = NULL;
-	if (release)
-		msc_release_connection(conn);
-}
-
-static void loc_updating_failure(struct gsm_subscriber_connection *conn, int release)
-{
-	if (!conn->loc_operation)
-		return;
-	LOGP(DMM, LOGL_ERROR, "Location Updating failed for %s\n",
-	     subscr_name(conn->subscr));
-	rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_FAILED]);
-	_release_loc_updating_req(conn, release);
-}
-
-static void loc_updating_success(struct gsm_subscriber_connection *conn, int release)
-{
-	if (!conn->loc_operation)
-		return;
-	LOGP(DMM, LOGL_INFO, "Location Updating completed for %s\n",
-	     subscr_name(conn->subscr));
-	rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_COMPLETED]);
-	_release_loc_updating_req(conn, release);
-}
-
-static void allocate_loc_updating_req(struct gsm_subscriber_connection *conn)
-{
-	if (conn->loc_operation)
-		LOGP(DMM, LOGL_ERROR, "Connection already had operation.\n");
-	loc_updating_failure(conn, 0);
-
-	conn->loc_operation = talloc_zero(tall_locop_ctx,
-					   struct gsm_loc_updating_operation);
-}
-
-static int finish_lu(struct gsm_subscriber_connection *conn)
-{
-	int rc = 0;
-	int avoid_tmsi = conn->network->avoid_tmsi;
-
-	/* We're all good */
-	if (avoid_tmsi) {
-		conn->subscr->tmsi = GSM_RESERVED_TMSI;
-		db_sync_subscriber(conn->subscr);
-	} else {
-		db_subscriber_alloc_tmsi(conn->subscr);
-	}
-
-	rc = gsm0408_loc_upd_acc(conn);
-	if (conn->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).
-	 * If avoid_tmsi == true, we don't send a TMSI, we don't
-	 * expect a reply and Location Updating is done.
-	 */
-	if (avoid_tmsi)
-		loc_updating_success(conn, 1);
-
-	return rc;
-}
-
-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:
-			loc_updating_failure(conn, 1);
-			break;
-
-		case GSM_SECURITY_ALREADY:
-			LOGP(DMM, LOGL_ERROR, "We don't expect LOCATION "
-				"UPDATING after CM SERVICE REQUEST\n");
-			/* fall through */
-
-		case GSM_SECURITY_NOAVAIL:
-		case GSM_SECURITY_SUCCEEDED:
-			rc = finish_lu(conn);
-			break;
-
-		default:
-			rc = -EINVAL;
-	};
-
-	return rc;
-}
-
-static int gsm0408_authorize(struct gsm_subscriber_connection *conn, struct msgb *msg)
-{
-	if (!conn->loc_operation)
-		return 0;
-
-	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.
-	 */
-	loc_updating_failure(conn, 0);
-
-	/* 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
-	 * connection. The transaction code will inform the CC or SMS
-	 * facilities that will send the release indications. As part of
-	 * the CC REL_IND the remote leg might be released and this will
-	 * trigger the call to trans_free. This is something the llist
-	 * macro can not handle and we will need to re-iterate the list.
-	 *
-	 * TODO: Move the trans_list into the subscriber connection and
-	 * create a pending list for MT transactions. These exist before
-	 * we have a subscriber connection.
-	 */
-restart:
-	llist_for_each_entry_safe(trans, temp, &conn->network->trans_list, entry) {
-		if (trans->conn == conn) {
-			trans_free(trans);
-			goto restart;
-		}
-	}
-}
-
+/* clear all transactions globally; used in case of MNCC socket disconnect */
 void gsm0408_clear_all_trans(struct gsm_network *net, int protocol)
 {
 	struct gsm_trans *trans, *temp;
@@ -490,14 +199,15 @@
 	msg->lchan = conn->lchan;
 
 	LOGP(DMM, LOGL_INFO, "Subscriber %s: LOCATION UPDATING REJECT "
-	     "LAC=%u BTS=%u\n", subscr_name(conn->subscr),
+	     "LAC=%u BTS=%u\n", vlr_subscr_name(conn->vsub),
 	     bts->location_area_code, bts->nr);
 
 	return gsm48_conn_sendmsg(msg, conn, NULL);
 }
 
 /* Chapter 9.2.13 : Send LOCATION UPDATE ACCEPT */
-static int gsm0408_loc_upd_acc(struct gsm_subscriber_connection *conn)
+static int gsm0408_loc_upd_acc(struct gsm_subscriber_connection *conn,
+			       uint32_t send_tmsi)
 {
 	struct msgb *msg = gsm48_msgb_alloc_name("GSM 04.08 LOC UPD ACC");
 	struct gsm48_hdr *gh;
@@ -515,16 +225,27 @@
 			   conn->network->network_code,
 			   conn->bts->location_area_code);
 
-	if (conn->subscr->tmsi == GSM_RESERVED_TMSI) {
+	if (send_tmsi == GSM_RESERVED_TMSI) {
+		/* we did not allocate a TMSI to the MS, so we need to
+		 * include the IMSI in order for the MS to delete any
+		 * old TMSI that might still be allocated */
 		uint8_t mi[10];
 		int len;
-		len = gsm48_generate_mid_from_imsi(mi, conn->subscr->imsi);
+		len = gsm48_generate_mid_from_imsi(mi, conn->vsub->imsi);
 		mid = msgb_put(msg, len);
 		memcpy(mid, mi, len);
 	} else {
+		/* Include the TMSI, which means that the MS will send a
+		 * TMSI REALLOCATION COMPLETE, and we should wait for
+		 * that until T3250 expiration */
 		mid = msgb_put(msg, GSM48_MID_TMSI_LEN);
-		gsm48_generate_mid_from_tmsi(mid, conn->subscr->tmsi);
+		gsm48_generate_mid_from_tmsi(mid, send_tmsi);
 	}
+	/* TODO: Follow-on proceed */
+	/* TODO: CTS permission */
+	/* TODO: Equivalent PLMNs */
+	/* TODO: Emergency Number List */
+	/* TODO: Per-MS T3312 */
 
 	DEBUGP(DMM, "-> LOCATION UPDATE ACCEPT\n");
 
@@ -547,82 +268,29 @@
 	return gsm48_conn_sendmsg(msg, conn, NULL);
 }
 
-static struct gsm_subscriber *subscr_create(const struct gsm_network *net,
-					    const char *imsi)
-{
-	if (!net->auto_create_subscr)
-		return NULL;
-
-	if (!subscr_regexp_check(net, imsi))
-		return NULL;
-
-	return subscr_create_subscriber(net->subscr_group, imsi);
-}
-
 /* 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_network *net = conn->network;
 	uint8_t mi_type = gh->data[1] & GSM_MI_TYPE_MASK;
 	char mi_string[GSM48_MI_SIZE];
 
+	if (!conn->vsub) {
+		LOGP(DMM, LOGL_ERROR,
+		     "Rx MM Identity Response: invalid: no subscriber\n");
+		return -EINVAL;
+	}
+
 	gsm48_mi_to_string(mi_string, sizeof(mi_string), &gh->data[1], gh->data[0]);
 	DEBUGP(DMM, "IDENTITY RESPONSE: MI(%s)=%s\n",
 		gsm48_mi_type_name(mi_type), mi_string);
 
 	osmo_signal_dispatch(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->subscr_group,
-							  mi_string);
-			if (!conn->subscr)
-				conn->subscr = subscr_create(net, mi_string);
-		}
-		if (!conn->subscr && conn->loc_operation) {
-			gsm0408_loc_upd_rej(conn, net->reject_cause);
-			loc_updating_failure(conn, 1);
-			return 0;
-		}
-		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);
+	return vlr_subscr_rx_id_resp(conn->vsub, gh->data+1, gh->data[0]);
 }
 
-
-static void loc_upd_rej_cb(void *data)
-{
-	struct gsm_subscriber_connection *conn = data;
-
-	LOGP(DMM, LOGL_DEBUG, "Location Updating Request procedure timedout.\n");
-	gsm0408_loc_upd_rej(conn, conn->network->reject_cause);
-	loc_updating_failure(conn, 1);
-}
-
-static void schedule_reject(struct gsm_subscriber_connection *conn)
-{
-	osmo_timer_setup(&conn->loc_operation->updating_timer, loc_upd_rej_cb,
-			 conn);
-	osmo_timer_schedule(&conn->loc_operation->updating_timer, 5, 0);
-}
-
+/* FIXME: to libosmogsm */
 static const struct value_string lupd_names[] = {
 	{ GSM48_LUPD_NORMAL, "NORMAL" },
 	{ GSM48_LUPD_PERIODIC, "PERIODIC" },
@@ -630,14 +298,23 @@
 	{ 0, NULL }
 };
 
-/* Chapter 9.2.15: Receive Location Updating Request */
-static int mm_rx_loc_upd_req(struct gsm_subscriber_connection *conn, struct msgb *msg)
+/* Chapter 9.2.15: Receive Location Updating Request.
+ * Keep this function non-static for direct invocation by unit tests. */
+int mm_rx_loc_upd_req(struct gsm_subscriber_connection *conn, struct msgb *msg)
 {
+	static const enum subscr_conn_from conn_from_lu = SUBSCR_CONN_FROM_LU;
+	struct gsm_network *net = conn->network;
 	struct gsm48_hdr *gh = msgb_l3(msg);
 	struct gsm48_loc_upd_req *lu;
-	struct gsm_subscriber *subscr = NULL;
 	uint8_t mi_type;
 	char mi_string[GSM48_MI_SIZE];
+	enum vlr_lu_type vlr_lu_type = VLR_LU_TYPE_REGULAR;
+
+	uint32_t tmsi;
+	char *imsi;
+	struct osmo_location_area_id old_lai, new_lai;
+	struct osmo_fsm_inst *lu_fsm;
+	int rc;
 
  	lu = (struct gsm48_loc_upd_req *) gh->data;
 
@@ -645,97 +322,95 @@
 
 	gsm48_mi_to_string(mi_string, sizeof(mi_string), lu->mi, lu->mi_len);
 
-	DEBUGPC(DMM, "MI(%s)=%s type=%s ", gsm48_mi_type_name(mi_type),
-		mi_string, get_value_string(lupd_names, lu->type));
+	rc = msc_create_conn_fsm(conn, mi_string);
+	if (rc)
+		/* logging already happened in msc_create_conn_fsm() */
+		return rc;
+
+	conn->classmark.classmark1 = lu->classmark1;
+	conn->classmark.classmark1_set = true;
+
+	DEBUGP(DMM, "LOCATION UPDATING REQUEST: MI(%s)=%s type=%s\n",
+	       gsm48_mi_type_name(mi_type), mi_string,
+	       get_value_string(lupd_names, lu->type));
 
 	osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_IDENTITY, &lu->mi_len);
 
 	switch (lu->type) {
 	case GSM48_LUPD_NORMAL:
 		rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_NORMAL]);
+		vlr_lu_type = VLR_LU_TYPE_REGULAR;
 		break;
 	case GSM48_LUPD_IMSI_ATT:
 		rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_ATTACH]);
+		vlr_lu_type = VLR_LU_TYPE_IMSI_ATTACH;
 		break;
 	case GSM48_LUPD_PERIODIC:
 		rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_PERIODIC]);
+		vlr_lu_type = VLR_LU_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;
+	/* TODO: 10.5.1.6 MS Classmark for UMTS / Classmark 2 */
+	/* TODO: 10.5.3.14 Aditional update parameters (CS fallback calls) */
+	/* TODO: 10.5.7.8 Device properties */
+	/* TODO: 10.5.1.15 MS network feature support */
 
 	switch (mi_type) {
 	case GSM_MI_TYPE_IMSI:
-		DEBUGPC(DMM, "\n");
-		/* we always want the IMEI, too */
-		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(conn->network->subscr_group, mi_string);
-		if (!subscr)
-			subscr = subscr_create(conn->network, mi_string);
-		if (!subscr) {
-			gsm0408_loc_upd_rej(conn, conn->network->reject_cause);
-			loc_updating_failure(conn, 0); /* FIXME: set release == true? */
-			return 0;
-		}
+		tmsi = GSM_RESERVED_TMSI;
+		imsi = 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(conn->network->subscr_group,
-					    tmsi_from_string(mi_string));
-		if (!subscr) {
-			/* send IDENTITY REQUEST message to get IMSI */
-			mm_tx_identity_req(conn, GSM_MI_TYPE_IMSI);
-			conn->loc_operation->waiting_for_imsi = 1;
-		}
-		/* we always want the IMEI, too */
-		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");
+		tmsi = tmsi_from_string(mi_string);
+		imsi = NULL;
 		break;
 	default:
 		DEBUGPC(DMM, "unknown mobile identity type\n");
+		tmsi = GSM_RESERVED_TMSI;
+		imsi = NULL;
 		break;
 	}
 
-	/* schedule the reject timer */
-	schedule_reject(conn);
+	gsm48_decode_lai(&lu->lai, &old_lai.plmn.mcc,
+			 &old_lai.plmn.mnc, &old_lai.lac);
+	new_lai.plmn.mcc = conn->network->country_code;
+	new_lai.plmn.mnc = conn->network->network_code;
+	new_lai.lac = conn->bts->location_area_code;
+	DEBUGP(DMM, "LU/new-LAC: %u/%u\n", old_lai.lac, new_lai.lac);
 
-	if (!subscr) {
-		DEBUGPC(DRR, "<- Can't find any subscriber for this ID\n");
-		/* FIXME: request id? close channel? */
-		return -EINVAL;
+	lu_fsm = vlr_loc_update(conn->conn_fsm,
+				SUBSCR_CONN_E_ACCEPTED,
+				SUBSCR_CONN_E_CN_CLOSE,
+				(void*)&conn_from_lu,
+				net->vlr, conn, vlr_lu_type, tmsi, imsi,
+				&old_lai, &new_lai,
+				conn->network->authentication_required,
+				conn->network->a5_encryption,
+				classmark_is_r99(&conn->classmark),
+				conn->via_ran == RAN_UTRAN_IU,
+				net->vlr->cfg.assign_tmsi);
+	if (!lu_fsm) {
+		DEBUGP(DRR, "%s: Can't start LU FSM\n", mi_string);
+		return 0;
 	}
 
-	conn->subscr = subscr;
-	conn->subscr->equipment.classmark1 = lu->classmark1;
+	/* From vlr_loc_update() we expect an implicit dispatch of
+	 * VLR_ULA_E_UPDATE_LA, and thus we expect msc_vlr_subscr_assoc() to
+	 * already have been called and completed. Has an error occured? */
 
-	/* 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 (!conn->vsub || conn->vsub->lu_fsm != lu_fsm) {
+		LOGP(DRR, LOGL_ERROR,
+		     "%s: internal error during Location Updating attempt\n",
+		     mi_string);
+		return -EIO;
+	}
+
+	return 0;
 }
 
 /* Turn int into semi-octet representation: 98 => 0x89 */
+/* FIXME: libosmocore/libosmogsm */
 static uint8_t bcdify(uint8_t value)
 {
         uint8_t ret;
@@ -939,53 +614,46 @@
 	return gsm48_tx_simple(conn, GSM48_PDISC_MM, GSM48_MT_MM_AUTH_REJ);
 }
 
-/*
- * At the 30C3 phones miss their periodic update
- * interval a lot and then remain unreachable. In case
- * we still know the TMSI we can just attach it again.
- */
-static void implit_attach(struct gsm_subscriber_connection *conn)
+static int msc_vlr_tx_cm_serv_acc(void *msc_conn_ref);
+static int msc_vlr_tx_cm_serv_rej(void *msc_conn_ref, enum vlr_proc_arq_result result);
+
+static int cm_serv_reuse_conn(struct gsm_subscriber_connection *conn, const uint8_t *mi_lv)
 {
-	if (conn->subscr->lac != GSM_LAC_RESERVED_DETACHED)
-		return;
+	uint8_t mi_type;
+	char mi_string[GSM48_MI_SIZE];
+	uint32_t tmsi;
 
-	subscr_update(conn->subscr, conn->bts,
-		      GSM_SUBSCRIBER_UPDATE_ATTACHED);
-}
+	gsm48_mi_to_string(mi_string, sizeof(mi_string), mi_lv+1, mi_lv[0]);
+	mi_type = mi_lv[1] & GSM_MI_TYPE_MASK;
 
+	switch (mi_type) {
+	case GSM_MI_TYPE_IMSI:
+		if (vlr_subscr_matches_imsi(conn->vsub, mi_string))
+			goto accept_reuse;
+		break;
+	case GSM_MI_TYPE_TMSI:
+		tmsi = osmo_load32be(mi_lv+2);
+		if (vlr_subscr_matches_tmsi(conn->vsub, tmsi))
+			goto accept_reuse;
+		break;
+	case GSM_MI_TYPE_IMEI:
+		if (vlr_subscr_matches_imei(conn->vsub, mi_string))
+			goto accept_reuse;
+		break;
+	default:
+		break;
+	}
 
-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;
+	LOGP(DMM, LOGL_ERROR, "%s: CM Service Request with mismatching mobile identity: %s %s\n",
+	     vlr_subscr_name(conn->vsub), gsm48_mi_type_name(mi_type), mi_string);
+	msc_vlr_tx_cm_serv_rej(conn, VLR_PR_ARQ_RES_ILLEGAL_SUBSCR);
+	return -EINVAL;
 
-	/* auth failed or succeeded, the timer was stopped */
-	conn->expire_timer_stopped = 1;
-
-	switch (event) {
-		case GSM_SECURITY_AUTH_FAILED:
-			/* Nothing to do */
-			break;
-
-		case GSM_SECURITY_NOAVAIL:
-		case GSM_SECURITY_ALREADY:
-			rc = gsm48_tx_mm_serv_ack(conn);
-			implit_attach(conn);
-			break;
-
-		case GSM_SECURITY_SUCCEEDED:
-			/* nothing to do. CIPHER MODE COMMAND is
-			 * implicit CM SERV ACK */
-			implit_attach(conn);
-			break;
-
-		default:
-			rc = -EINVAL;
-	};
-
-	return rc;
+accept_reuse:
+	DEBUGP(DMM, "%s: re-using already accepted connection\n",
+	       vlr_subscr_name(conn->vsub));
+	conn->received_cm_service_request = true;
+	return conn->network->vlr->ops.tx_cm_serv_acc(conn);
 }
 
 /*
@@ -996,14 +664,17 @@
  * 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
+ *
+ * Keep this function non-static for direct invocation by unit tests.
  */
-static int gsm48_rx_mm_serv_req(struct gsm_subscriber_connection *conn, struct msgb *msg)
+int gsm48_rx_mm_serv_req(struct gsm_subscriber_connection *conn, struct msgb *msg)
 {
+	static const enum subscr_conn_from conn_from_cm_service_req =
+		SUBSCR_CONN_FROM_CM_SERVICE_REQ;
+	struct gsm_network *net = conn->network;
 	uint8_t mi_type;
 	char mi_string[GSM48_MI_SIZE];
 
-	struct gsm_network *network = conn->network;
-	struct gsm_subscriber *subscr;
 	struct gsm48_hdr *gh = msgb_l3(msg);
 	struct gsm48_service_request *req =
 			(struct gsm48_service_request *)gh->data;
@@ -1012,6 +683,12 @@
 	uint8_t *classmark2 = gh->data+2;
 	uint8_t mi_len = *(classmark2 + classmark2_len);
 	uint8_t *mi = (classmark2 + classmark2_len + 1);
+	struct osmo_location_area_id lai;
+	int rc;
+
+	lai.plmn.mcc = conn->network->country_code;
+	lai.plmn.mnc = conn->network->network_code;
+	lai.lac = conn->bts->location_area_code;
 
 	DEBUGP(DMM, "<- CM SERVICE REQUEST ");
 	if (msg->data_len < sizeof(struct gsm48_service_request*)) {
@@ -1033,14 +710,10 @@
 		DEBUGPC(DMM, "serv_type=0x%02x MI(%s)=%s\n",
 			req->cm_service_type, gsm48_mi_type_name(mi_type),
 			mi_string);
-		subscr = subscr_get_by_imsi(network->subscr_group,
-					    mi_string);
 	} else if (mi_type == GSM_MI_TYPE_TMSI) {
 		DEBUGPC(DMM, "serv_type=0x%02x MI(%s)=%s\n",
 			req->cm_service_type, gsm48_mi_type_name(mi_type),
 			mi_string);
-		subscr = subscr_get_by_tmsi(network->subscr_group,
-				tmsi_from_string(mi_string));
 	} else {
 		DEBUGPC(DMM, "mi_type is not expected: %d\n", mi_type);
 		return gsm48_tx_mm_serv_rej(conn,
@@ -1048,34 +721,40 @@
 	}
 
 	osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_IDENTITY, (classmark2 + classmark2_len));
+	memcpy(conn->classmark.classmark2, classmark2, classmark2_len);
+	conn->classmark.classmark2_len = classmark2_len;
+
+	if (conn->conn_fsm) {
+		if (msc_subscr_conn_is_accepted(conn))
+			return cm_serv_reuse_conn(conn, mi-1);
+		LOGP(DMM, LOGL_ERROR, "%s: connection already in use\n",
+		     vlr_subscr_name(conn->vsub));
+		msc_vlr_tx_cm_serv_rej(conn, VLR_PR_ARQ_RES_UNKNOWN_ERROR);
+		return -EINVAL;
+	}
+
+	rc = msc_create_conn_fsm(conn, mi_string);
+	if (rc) {
+		msc_vlr_tx_cm_serv_rej(conn, VLR_PR_ARQ_RES_UNKNOWN_ERROR);
+		/* logging already happened in msc_create_conn_fsm() */
+		return rc;
+	}
 
 	if (is_siemens_bts(conn->bts))
 		send_siemens_mrpci(msg->lchan, classmark2-1);
 
+	vlr_proc_acc_req(conn->conn_fsm,
+			 SUBSCR_CONN_E_ACCEPTED,
+			 SUBSCR_CONN_E_CN_CLOSE,
+			 (void*)&conn_from_cm_service_req,
+			 net->vlr, conn,
+			 VLR_PR_ARQ_T_CM_SERV_REQ, mi-1, &lai,
+			 conn->network->authentication_required,
+			 conn->network->a5_encryption,
+			 classmark_is_r99(&conn->classmark),
+			 conn->via_ran == RAN_UTRAN_IU);
 
-	/* 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_VLR);
-
-	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);
-
-	/* we will send a MM message soon */
-	conn->expire_timer_stopped = 1;
-
-	return gsm48_secure_channel(conn, req->cipher_key_seq,
-			_gsm48_rx_mm_serv_req_sec_cb, NULL);
+	return 0;
 }
 
 static int gsm48_rx_mm_imsi_detach_ind(struct gsm_subscriber_connection *conn, struct msgb *msg)
@@ -1086,51 +765,51 @@
 				(struct gsm48_imsi_detach_ind *) gh->data;
 	uint8_t mi_type = idi->mi[0] & GSM_MI_TYPE_MASK;
 	char mi_string[GSM48_MI_SIZE];
-	struct gsm_subscriber *subscr = NULL;
+	struct vlr_subscr *vsub = NULL;
 
 	gsm48_mi_to_string(mi_string, sizeof(mi_string), idi->mi, idi->mi_len);
-	DEBUGP(DMM, "IMSI DETACH INDICATION: MI(%s)=%s",
-		gsm48_mi_type_name(mi_type), mi_string);
+	DEBUGP(DMM, "IMSI DETACH INDICATION: MI(%s)=%s\n",
+	       gsm48_mi_type_name(mi_type), mi_string);
 
 	rate_ctr_inc(&network->msc_ctrs->ctr[MSC_CTR_LOC_UPDATE_TYPE_DETACH]);
 
 	switch (mi_type) {
 	case GSM_MI_TYPE_TMSI:
-		DEBUGPC(DMM, "\n");
-		subscr = subscr_get_by_tmsi(network->subscr_group,
-					    tmsi_from_string(mi_string));
+		vsub = vlr_subscr_find_by_tmsi(network->vlr,
+					       tmsi_from_string(mi_string));
 		break;
 	case GSM_MI_TYPE_IMSI:
-		DEBUGPC(DMM, "\n");
-		subscr = subscr_get_by_imsi(network->subscr_group,
-					    mi_string);
+		vsub = vlr_subscr_find_by_imsi(network->vlr, 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");
+		LOGP(DMM, LOGL_ERROR, "MI(%s)=%s: unimplemented mobile identity type\n",
+		     gsm48_mi_type_name(mi_type), mi_string);
 		break;
 	default:
-		DEBUGPC(DMM, ": unknown mobile identity type\n");
+		LOGP(DMM, LOGL_ERROR, "MI(%s)=%s: unknown mobile identity type\n",
+		     gsm48_mi_type_name(mi_type), mi_string);
 		break;
 	}
 
-	if (subscr) {
-		subscr_update(subscr, conn->bts,
-			      GSM_SUBSCRIBER_UPDATE_DETACHED);
-		DEBUGP(DMM, "Subscriber: %s\n", subscr_name(subscr));
 
-		subscr->equipment.classmark1 = idi->classmark1;
-		db_sync_equipment(&subscr->equipment);
+	/* TODO? We used to remember the subscriber's classmark1 here and
+	 * stored it in the old sqlite db, but now we store it in a conn that
+	 * will be discarded anyway: */
+	conn->classmark.classmark1 = idi->classmark1;
 
-		subscr_put(subscr);
-	} else
-		DEBUGP(DMM, "Unknown Subscriber ?!?\n");
+	if (!vsub) {
+		LOGP(DMM, LOGL_ERROR, "IMSI DETACH for unknown subscriber MI(%s)=%s\n",
+		     gsm48_mi_type_name(mi_type), mi_string);
+	} else {
+		LOGP(DMM, LOGL_INFO, "IMSI DETACH for %s\n", vlr_subscr_name(vsub));
+		vlr_subscr_rx_imsi_detach(vsub);
+		osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_DETACHED, vsub);
+		vlr_subscr_put(vsub);
+	}
 
-	/* FIXME: iterate over all transactions and release them,
-	 * imagine an IMSI DETACH happening during an active call! */
-
-	release_anchor(conn);
+	msc_subscr_conn_close(conn, 0);
 	return 0;
 }
 
@@ -1154,7 +833,7 @@
 		LOGP(DMM, LOGL_ERROR,
 		     "%s: MM AUTHENTICATION RESPONSE:"
 		     " l3 length invalid: %u\n",
-		     subscr_name(conn->subscr), msgb_l3len(msg));
+		     vlr_subscr_name(conn->vsub), msgb_l3len(msg));
 		return -EINVAL;
 	}
 
@@ -1187,7 +866,7 @@
 		LOGP(DMM, LOGL_ERROR,
 		     "%s: MM AUTHENTICATION RESPONSE:"
 		     " l3 length invalid: %u\n",
-		     subscr_name(conn->subscr), msgb_l3len(msg));
+		     vlr_subscr_name(conn->vsub), msgb_l3len(msg));
 		return -EINVAL;
 	}
 
@@ -1197,7 +876,7 @@
 		LOGP(DMM, LOGL_ERROR,
 		     "%s: MM R99 AUTHENTICATION RESPONSE:"
 		     " expected IEI 0x%02x, got 0x%02x\n",
-		     subscr_name(conn->subscr),
+		     vlr_subscr_name(conn->vsub),
 		     GSM48_IE_AUTH_RES_EXT, iei);
 		return -EINVAL;
 	}
@@ -1206,7 +885,7 @@
 		LOGP(DMM, LOGL_ERROR,
 		     "%s: MM R99 AUTHENTICATION RESPONSE:"
 		     " extended Auth Resp IE 0x%02x is too large: %u bytes\n",
-		     subscr_name(conn->subscr), GSM48_IE_AUTH_RES_EXT, ie_len);
+		     vlr_subscr_name(conn->vsub), GSM48_IE_AUTH_RES_EXT, ie_len);
 		return -EINVAL;
 	}
 
@@ -1218,17 +897,15 @@
 /* Chapter 9.2.3: Authentication Response */
 static int gsm48_rx_mm_auth_resp(struct gsm_subscriber_connection *conn, struct msgb *msg)
 {
-	struct gsm_network *net = conn->network;
 	uint8_t res[16];
 	uint8_t res_len;
 	int rc;
 	bool is_r99;
 
-	if (!conn->subscr) {
+	if (!conn->vsub) {
 		LOGP(DMM, LOGL_ERROR,
 		     "MM AUTHENTICATION RESPONSE: invalid: no subscriber\n");
-		gsm48_tx_mm_auth_rej(conn);
-		release_security_operation(conn);
+		msc_subscr_conn_close(conn, GSM_CAUSE_AUTH_FAILED);
 		return -EINVAL;
 	}
 
@@ -1242,56 +919,18 @@
 	}
 
 	if (rc) {
-		gsm48_tx_mm_auth_rej(conn);
-		release_security_operation(conn);
+		msc_subscr_conn_close(conn, GSM_CAUSE_AUTH_FAILED);
 		return -EINVAL;
 	}
 
 	DEBUGP(DMM, "%s: MM %s AUTHENTICATION RESPONSE (%s = %s)\n",
-	       subscr_name(conn->subscr),
+	       vlr_subscr_name(conn->vsub),
 	       is_r99 ? "R99" : "GSM", is_r99 ? "res" : "sres",
 	       osmo_hexdump_nospc(res, res_len));
 
-	/* Future: vlr_sub_rx_auth_resp(conn->vsub, is_r99,
-	 *				conn->via_ran == RAN_UTRAN_IU,
-	 *				res, res_len);
-	 */
-
-	if (res_len != 4) {
-		LOGP(DMM, LOGL_ERROR,
-		     "%s: MM AUTHENTICATION RESPONSE:"
-		     " UMTS authentication not supported\n",
-		     subscr_name(conn->subscr));
-	}
-
-	/* 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.vec.sres, res, 4)) {
-		int rc;
-		gsm_cbfn *cb = conn->sec_operation->cb;
-
-		DEBUGPC(DMM, "Invalid (expected %s)\n",
-			osmo_hexdump(conn->sec_operation->atuple.vec.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.vec.kc, 8, 0);
+	return vlr_subscr_rx_auth_resp(conn->vsub, is_r99,
+				       conn->via_ran == RAN_UTRAN_IU,
+				       res, res_len);
 }
 
 static int gsm48_rx_mm_auth_fail(struct gsm_subscriber_connection *conn, struct msgb *msg)
@@ -1301,20 +940,11 @@
 	uint8_t auts_tag;
 	uint8_t auts_len;
 	uint8_t *auts;
-	int rc;
 
-	if (!conn->sec_operation) {
-		DEBUGP(DMM, "%s: MM R99 AUTHENTICATION FAILURE:"
-		       " No authentication/cipher operation in progress\n",
-		       subscr_name(conn->subscr));
-		return -EINVAL;
-	}
-
-	if (!conn->subscr) {
+	if (!conn->vsub) {
 		LOGP(DMM, LOGL_ERROR,
 		     "MM R99 AUTHENTICATION FAILURE: invalid: no subscriber\n");
-		gsm48_tx_mm_auth_rej(conn);
-		release_security_operation(conn);
+		msc_subscr_conn_close(conn, GSM_CAUSE_AUTH_FAILED);
 		return -EINVAL;
 	}
 
@@ -1322,9 +952,8 @@
 		LOGP(DMM, LOGL_ERROR,
 		     "%s: MM R99 AUTHENTICATION FAILURE:"
 		     " l3 length invalid: %u\n",
-		     subscr_name(conn->subscr), msgb_l3len(msg));
-		gsm48_tx_mm_auth_rej(conn);
-		release_security_operation(conn);
+		     vlr_subscr_name(conn->vsub), msgb_l3len(msg));
+		msc_subscr_conn_close(conn, GSM_CAUSE_AUTH_FAILED);
 		return -EINVAL;
 	}
 
@@ -1333,10 +962,9 @@
 	if (cause != GSM48_REJECT_SYNCH_FAILURE) {
 		LOGP(DMM, LOGL_INFO,
 		     "%s: MM R99 AUTHENTICATION FAILURE: cause 0x%0x\n",
-		     subscr_name(conn->subscr), cause);
-		rc = gsm48_tx_mm_auth_rej(conn);
-		release_security_operation(conn);
-		return rc;
+		     vlr_subscr_name(conn->vsub), cause);
+		vlr_subscr_rx_auth_fail(conn->vsub, NULL);
+		return 0;
 	}
 
 	/* This is a Synch Failure procedure, which should pass an AUTS to
@@ -1347,9 +975,8 @@
 		LOGP(DMM, LOGL_INFO,
 		     "%s: MM R99 AUTHENTICATION FAILURE:"
 		     " invalid Synch Failure: missing AUTS IE\n",
-		     subscr_name(conn->subscr));
-		gsm48_tx_mm_auth_rej(conn);
-		release_security_operation(conn);
+		     vlr_subscr_name(conn->vsub));
+		msc_subscr_conn_close(conn, GSM_CAUSE_AUTH_FAILED);
 		return -EINVAL;
 	}
 
@@ -1364,10 +991,9 @@
 		     " invalid Synch Failure:"
 		     " expected AUTS IE 0x%02x of 14 bytes,"
 		     " got IE 0x%02x of %u bytes\n",
-		     subscr_name(conn->subscr),
+		     vlr_subscr_name(conn->vsub),
 		     GSM48_IE_AUTS, auts_tag, auts_len);
-		gsm48_tx_mm_auth_rej(conn);
-		release_security_operation(conn);
+		msc_subscr_conn_close(conn, GSM_CAUSE_AUTH_FAILED);
 		return -EINVAL;
 	}
 
@@ -1375,9 +1001,8 @@
 		LOGP(DMM, LOGL_INFO,
 		     "%s: MM R99 AUTHENTICATION FAILURE:"
 		     " invalid Synch Failure msg: message truncated (%u)\n",
-		     subscr_name(conn->subscr), msgb_l3len(msg));
-		gsm48_tx_mm_auth_rej(conn);
-		release_security_operation(conn);
+		     vlr_subscr_name(conn->vsub), msgb_l3len(msg));
+		msc_subscr_conn_close(conn, GSM_CAUSE_AUTH_FAILED);
 		return -EINVAL;
 	}
 
@@ -1385,15 +1010,21 @@
 	 * large enough. */
 
 	DEBUGP(DMM, "%s: MM R99 AUTHENTICATION SYNCH (AUTS = %s)\n",
-	       subscr_name(conn->subscr), osmo_hexdump_nospc(auts, 14));
+	       vlr_subscr_name(conn->vsub), osmo_hexdump_nospc(auts, 14));
 
-	/* Future: vlr_sub_rx_auth_fail(conn->vsub, auts); */
+	return vlr_subscr_rx_auth_fail(conn->vsub, auts);
+}
 
-	LOGP(DMM, LOGL_ERROR, "%s: MM R99 AUTHENTICATION not supported\n",
-	     subscr_name(conn->subscr));
-	rc = gsm48_tx_mm_auth_rej(conn);
-	release_security_operation(conn);
-	return rc;
+static int gsm48_rx_mm_tmsi_reall_compl(struct gsm_subscriber_connection *conn)
+{
+	DEBUGP(DMM, "TMSI Reallocation Completed. Subscriber: %s\n",
+	       vlr_subscr_name(conn->vsub));
+	if (!conn->vsub) {
+		LOGP(DMM, LOGL_ERROR,
+		     "Rx MM TMSI Reallocation Complete: invalid: no subscriber\n");
+		return -EINVAL;
+	}
+	return vlr_subscr_rx_tmsi_reall_compl(conn->vsub);
 }
 
 /* Receive a GSM 04.08 Mobility Management (MM) message */
@@ -1404,7 +1035,6 @@
 
 	switch (gsm48_hdr_msg_type(gh)) {
 	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:
@@ -1417,9 +1047,7 @@
 		rc = gsm48_rx_mm_status(msg);
 		break;
 	case GSM48_MT_MM_TMSI_REALL_COMPL:
-		DEBUGP(DMM, "TMSI Reallocation Completed. Subscriber: %s\n",
-		       subscr_name(conn->subscr));
-		loc_updating_success(conn, 1);
+		rc = gsm48_rx_mm_tmsi_reall_compl(conn);
 		break;
 	case GSM48_MT_MM_IMSI_DETACH_IND:
 		rc = gsm48_rx_mm_imsi_detach_ind(conn, msg);
@@ -1442,18 +1070,37 @@
 	return rc;
 }
 
+static uint8_t *gsm48_cm2_get_mi(uint8_t *classmark2_lv, unsigned int tot_len)
+{
+	/* Check the size for the classmark */
+	if (tot_len < 1 + *classmark2_lv)
+		return NULL;
+
+	uint8_t *mi_lv = classmark2_lv + *classmark2_lv + 1;
+	if (tot_len < 2 + *classmark2_lv + mi_lv[0])
+		return NULL;
+
+	return mi_lv;
+}
+
 /* Receive a PAGING RESPONSE message from the MS */
 static int gsm48_rx_rr_pag_resp(struct gsm_subscriber_connection *conn, struct msgb *msg)
 {
+	static const enum subscr_conn_from conn_from_paging_resp =
+		SUBSCR_CONN_FROM_PAGING_RESP;
+	struct gsm_network *net = conn->network;
 	struct gsm48_hdr *gh = msgb_l3(msg);
 	struct gsm48_pag_resp *resp;
 	uint8_t *classmark2_lv = gh->data + 1;
+	uint8_t *mi_lv;
 	uint8_t mi_type;
 	char mi_string[GSM48_MI_SIZE];
-	struct gsm_subscriber *subscr = NULL;
-	struct bsc_subscr *bsub;
-	uint32_t tmsi;
 	int rc = 0;
+	struct osmo_location_area_id lai;
+
+	lai.plmn.mcc = conn->network->country_code;
+	lai.plmn.mnc = conn->network->network_code;
+	lai.lac = conn->bts->location_area_code; /* (will be replaced by conn->lac soon) */
 
 	resp = (struct gsm48_pag_resp *) &gh->data[0];
 	gsm48_paging_extract_mi(resp, msgb_l3len(msg) - sizeof(*gh),
@@ -1461,55 +1108,31 @@
 	DEBUGP(DRR, "PAGING RESPONSE: MI(%s)=%s\n",
 		gsm48_mi_type_name(mi_type), mi_string);
 
-	switch (mi_type) {
-	case GSM_MI_TYPE_TMSI:
-		tmsi = tmsi_from_string(mi_string);
-		subscr = subscr_get_by_tmsi(conn->network->subscr_group, tmsi);
-		break;
-	case GSM_MI_TYPE_IMSI:
-		subscr = subscr_get_by_imsi(conn->network->subscr_group,
-					    mi_string);
-		break;
+	mi_lv = gsm48_cm2_get_mi(classmark2_lv, msgb_l3len(msg) - sizeof(*gh));
+	if (!mi_lv) {
+		/* FIXME */
+		return -1;
 	}
 
-	if (!subscr) {
-		DEBUGP(DRR, "<- Can't find any subscriber for this ID\n");
-		/* FIXME: request id? close channel? */
-		return -EINVAL;
-	}
+	rc = msc_create_conn_fsm(conn, mi_string);
+	if (rc)
+		/* logging already happened in msc_create_conn_fsm() */
+		return rc;
 
-	if (!conn->subscr) {
-		conn->subscr = subscr;
-	} else if (conn->subscr != subscr) {
-		LOGP(DRR, LOGL_ERROR, "<- Channel already owned by someone else?\n");
-		subscr_put(subscr);
-		return -EINVAL;
-	} else {
-		DEBUGP(DRR, "<- Channel already owned by us\n");
-		subscr_put(subscr);
-		subscr = conn->subscr;
-	}
+	memcpy(conn->classmark.classmark2, classmark2_lv+1, *classmark2_lv);
+	conn->classmark.classmark2_len = *classmark2_lv;
 
-	log_set_context(LOG_CTX_VLR_SUBSCR, subscr);
-	DEBUGP(DRR, "<- Channel was requested by %s\n",
-		subscr->name && strlen(subscr->name) ? subscr->name : subscr->imsi);
+	vlr_proc_acc_req(conn->conn_fsm,
+			 SUBSCR_CONN_E_ACCEPTED,
+			 SUBSCR_CONN_E_CN_CLOSE,
+			 (void*)&conn_from_paging_resp,
+			 net->vlr, conn,
+			 VLR_PR_ARQ_T_PAGING_RESP, mi_lv, &lai,
+			 conn->network->authentication_required,
+			 conn->network->a5_encryption,
+			 classmark_is_r99(&conn->classmark),
+			 conn->via_ran == RAN_UTRAN_IU);
 
-	subscr->equipment.classmark2_len = *classmark2_lv;
-	memcpy(subscr->equipment.classmark2, classmark2_lv+1, *classmark2_lv);
-	db_sync_equipment(&subscr->equipment);
-
-	/* TODO MSC split -- creating a BSC subscriber directly from MSC data
-	 * structures in RAM. At some point the MSC will send a message to the
-	 * BSC instead. */
-	bsub = bsc_subscr_find_or_create_by_imsi(conn->network->bsc_subscribers,
-						 subscr->imsi);
-	bsub->tmsi = subscr->tmsi;
-	bsub->lac = subscr->lac;
-
-	/* We received a paging */
-	conn->expire_timer_stopped = 1;
-
-	rc = gsm48_handle_paging_resp(conn, msg, bsub);
 	return rc;
 }
 
@@ -1527,7 +1150,12 @@
 	DEBUGP(DRR, "RX APPLICATION INFO id/flags=0x%02x apdu_len=%u apdu=%s\n",
 		apdu_id_flags, apdu_len, osmo_hexdump(apdu_data, apdu_len));
 
+	/* we're not using the app info blob anywhere, so ignore. */
+#if 0
 	return db_apdu_blob_store(conn->subscr, apdu_id_flags, apdu_len, apdu_data);
+#else
+	return 0;
+#endif
 }
 
 /* Receive a GSM 04.08 Radio Resource (RR) message */
@@ -1681,12 +1309,12 @@
 				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):"-",
+				vlr_subscr_msisdn_or_name(trans->vsub),
 				get_mncc_name(msg_type));
 		else
 			DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
 				"Sending '%s' to MNCC.\n",
-				(trans->subscr)?(trans->subscr->extension):"-",
+				vlr_subscr_msisdn_or_name(trans->vsub),
 				get_mncc_name(msg_type));
 	else
 		DEBUGP(DCC, "(bts - trx - ts - ti -- sub -) "
@@ -1752,7 +1380,8 @@
 	/* check all tranactions (without lchan) for subscriber */
 	switch (event) {
 	case GSM_PAGING_SUCCEEDED:
-		DEBUGP(DCC, "Paging subscr %s succeeded!\n", transt->subscr->extension);
+		DEBUGP(DCC, "Paging subscr %s succeeded!\n",
+		       vlr_subscr_msisdn_or_name(transt->vsub));
 		OSMO_ASSERT(conn);
 		/* Assign lchan */
 		transt->conn = conn;
@@ -1762,7 +1391,7 @@
 	case GSM_PAGING_EXPIRED:
 	case GSM_PAGING_BUSY:
 		DEBUGP(DCC, "Paging subscr %s expired!\n",
-			transt->subscr->extension);
+		       vlr_subscr_msisdn_or_name(transt->vsub));
 		/* Temporarily out of order */
 		mncc_release_ind(transt->net, transt,
 				 transt->callref,
@@ -2023,7 +1652,7 @@
 		return -EIO;
 
 	/* Which subscriber do we want to track trans1 or trans2? */
-	log_set_context(LOG_CTX_VLR_SUBSCR, trans1->subscr);
+	log_set_context(LOG_CTX_VLR_SUBSCR, trans1->vsub);
 
 	/* through-connect channel */
 	return tch_map(trans1->conn->lchan, trans2->conn->lchan);
@@ -2044,7 +1673,7 @@
 	if (!trans->conn)
 		return 0;
 
-	log_set_context(LOG_CTX_VLR_SUBSCR, trans->subscr);
+	log_set_context(LOG_CTX_VLR_SUBSCR, trans->vsub);
 	lchan = trans->conn->lchan;
 	bts = lchan->ts->trx->bts;
 
@@ -2247,9 +1876,8 @@
 
 	/* use subscriber as calling party number */
 	setup.fields |= MNCC_F_CALLING;
-	osmo_strlcpy(setup.calling.number, trans->subscr->extension,
-		     sizeof(setup.calling.number));
-	osmo_strlcpy(setup.imsi, trans->subscr->imsi, sizeof(setup.imsi));
+	osmo_strlcpy(setup.calling.number, trans->vsub->msisdn, sizeof(setup.calling.number));
+	osmo_strlcpy(setup.imsi, trans->vsub->imsi, sizeof(setup.imsi));
 
 	/* bearer capability */
 	if (TLVP_PRESENT(&tp, GSM48_IE_BEARER_CAP)) {
@@ -2298,7 +1926,7 @@
 	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,
+	     vlr_subscr_name(trans->vsub), trans->vsub->msisdn,
 	     setup.called.number);
 
 	rate_ctr_inc(&trans->net->msc_ctrs->ctr[MSC_CTR_CALL_MO_SETUP]);
@@ -2335,7 +1963,7 @@
 	}
 
 	/* Get free transaction_id */
-	trans_id = trans_assign_trans_id(trans->net, trans->subscr,
+	trans_id = trans_assign_trans_id(trans->net, trans->vsub,
 					 GSM48_PDISC_CC, 0);
 	if (trans_id < 0) {
 		/* no free transaction ID */
@@ -2426,8 +2054,7 @@
 	}
 
 	/* IMSI of called subscriber */
-	osmo_strlcpy(call_conf.imsi, trans->subscr->imsi,
-		     sizeof(call_conf.imsi));
+	osmo_strlcpy(call_conf.imsi, trans->vsub->imsi, sizeof(call_conf.imsi));
 
 	new_cc_state(trans, GSM_CSTATE_MO_TERM_CALL_CONF);
 
@@ -2580,9 +2207,8 @@
 	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
 	/* use subscriber as connected party number */
 	connect.fields |= MNCC_F_CONNECTED;
-	osmo_strlcpy(connect.connected.number, trans->subscr->extension,
-		     sizeof(connect.connected.number));
-	osmo_strlcpy(connect.imsi, trans->subscr->imsi, sizeof(connect.imsi));
+	osmo_strlcpy(connect.connected.number, trans->vsub->msisdn, sizeof(connect.connected.number));
+	osmo_strlcpy(connect.imsi, trans->vsub->imsi, sizeof(connect.imsi));
 
 	/* facility */
 	if (TLVP_PRESENT(&tp, GSM48_IE_FACILITY)) {
@@ -3379,7 +3005,7 @@
 		mncc_recv_rtp_err(net, callref, MNCC_RTP_CREATE);
 		return -EIO;
 	}
-	log_set_context(LOG_CTX_VLR_SUBSCR, trans->subscr);
+	log_set_context(LOG_CTX_VLR_SUBSCR, trans->vsub);
 	if (!trans->conn) {
 		LOGP(DMNCC, LOGL_NOTICE, "RTP create for trans without conn\n");
 		mncc_recv_rtp_err(net, callref, MNCC_RTP_CREATE);
@@ -3435,7 +3061,7 @@
 		mncc_recv_rtp_err(net, rtp->callref, MNCC_RTP_CONNECT);
 		return -EIO;
 	}
-	log_set_context(LOG_CTX_VLR_SUBSCR, trans->subscr);
+	log_set_context(LOG_CTX_VLR_SUBSCR, trans->vsub);
 	if (!trans->conn) {
 		LOGP(DMNCC, LOGL_ERROR, "RTP connect for trans without conn\n");
 		mncc_recv_rtp_err(net, rtp->callref, MNCC_RTP_CONNECT);
@@ -3612,7 +3238,7 @@
 			LOGP(DMNCC, LOGL_ERROR, "TCH frame for non-existing trans\n");
 			return -EIO;
 		}
-		log_set_context(LOG_CTX_VLR_SUBSCR, trans->subscr);
+		log_set_context(LOG_CTX_VLR_SUBSCR, trans->vsub);
 		if (!trans->conn) {
 			LOGP(DMNCC, LOGL_NOTICE, "TCH frame for trans without conn\n");
 			return 0;
@@ -3656,7 +3282,7 @@
 
 	/* Callref unknown */
 	if (!trans) {
-		struct gsm_subscriber *subscr;
+		struct vlr_subscr *vsub;
 
 		if (msg_type != MNCC_SETUP_REQ) {
 			DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
@@ -3679,17 +3305,16 @@
 		}
 		/* New transaction due to setup, find subscriber */
 		if (data->called.number[0])
-			subscr = subscr_get_by_extension(net->subscr_group,
-							data->called.number);
+			vsub = vlr_subscr_find_by_msisdn(net->vlr,
+							 data->called.number);
 		else
-			subscr = subscr_get_by_imsi(net->subscr_group,
-						    data->imsi);
+			vsub = vlr_subscr_find_by_imsi(net->vlr, data->imsi);
 
 		/* update the subscriber we deal with */
-		log_set_context(LOG_CTX_VLR_SUBSCR, subscr);
+		log_set_context(LOG_CTX_VLR_SUBSCR, vsub);
 
 		/* If subscriber is not found */
-		if (!subscr) {
+		if (!vsub) {
 			DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
 				"Received '%s' from MNCC with "
 				"unknown subscriber %s\n", data->called.number,
@@ -3700,22 +3325,22 @@
 						GSM48_CC_CAUSE_UNASSIGNED_NR);
 		}
 		/* If subscriber is not "attached" */
-		if (!subscr->lac) {
+		if (!vsub->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);
+			vlr_subscr_put(vsub);
 			/* 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(net, subscr, GSM48_PDISC_CC, 0xff, data->callref);
+		trans = trans_alloc(net, vsub, GSM48_PDISC_CC, 0xff, data->callref);
 		if (!trans) {
 			DEBUGP(DCC, "No memory for trans.\n");
-			subscr_put(subscr);
+			vlr_subscr_put(vsub);
 			/* Ressource unavailable */
 			mncc_release_ind(net, NULL, data->callref,
 					 GSM48_CAUSE_LOC_PRN_S_LU,
@@ -3723,7 +3348,7 @@
 			return -ENOMEM;
 		}
 		/* Find lchan */
-		conn = connection_for_subscr(subscr);
+		conn = connection_for_subscr(vsub);
 
 		/* If subscriber has no lchan */
 		if (!conn) {
@@ -3731,15 +3356,15 @@
 			llist_for_each_entry(transt, &net->trans_list, entry) {
 				/* Transaction of our lchan? */
 				if (transt == trans ||
-				    transt->subscr != subscr)
+				    transt->vsub != vsub)
 					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);
+					get_mncc_name(msg_type), vsub->lac);
+				vlr_subscr_put(vsub);
 				trans_free(trans);
 				return 0;
 			}
@@ -3747,24 +3372,26 @@
 			memcpy(&trans->cc.msg, data, sizeof(struct gsm_mncc));
 
 			/* Request a channel */
-			trans->paging_request = subscr_request_channel(subscr,
-							RSL_CHANNEED_TCH_F, setup_trig_pag_evt,
+			trans->paging_request = subscr_request_channel(
+							vsub,
+							RSL_CHANNEED_TCH_F,
+							setup_trig_pag_evt,
 							trans);
 			if (!trans->paging_request) {
 				LOGP(DCC, LOGL_ERROR, "Failed to allocate paging token.\n");
-				subscr_put(subscr);
+				vlr_subscr_put(vsub);
 				trans_free(trans);
 				return 0;
 			}
-			subscr_put(subscr);
+			vlr_subscr_put(vsub);
 			return 0;
 		}
 		/* Assign lchan */
-		trans->conn = conn;
-		subscr_put(subscr);
+		trans->conn = msc_subscr_conn_get(conn);
+		vlr_subscr_put(vsub);
 	} else {
 		/* update the subscriber we deal with */
-		log_set_context(LOG_CTX_VLR_SUBSCR, trans->subscr);
+		log_set_context(LOG_CTX_VLR_SUBSCR, trans->vsub);
 	}
 
 	if (trans->conn)
@@ -3774,7 +3401,7 @@
 	if (!conn) {
 		DEBUGP(DCC, "(bts - trx - ts - ti -- sub %s) "
 			"Received '%s' from MNCC in paging state\n",
-			(trans->subscr)?(trans->subscr->extension):"-",
+			vlr_subscr_msisdn_or_name(trans->vsub),
 			get_mncc_name(msg_type));
 		mncc_set_cause(&rel, GSM48_CAUSE_LOC_PRN_S_LU,
 				GSM48_CC_CAUSE_NORM_CALL_CLEAR);
@@ -3791,7 +3418,7 @@
 		"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):"-",
+		vlr_subscr_msisdn_or_name(trans->conn->vsub),
 		get_mncc_name(msg_type), trans->cc.state,
 		gsm48_cc_state_name(trans->cc.state));
 
@@ -3878,8 +3505,8 @@
 		return -EINVAL;
 	}
 
-	if (!conn->subscr) {
-		LOGP(DCC, LOGL_ERROR, "Invalid conn, no subscriber\n");
+	if (!conn->vsub) {
+		LOGP(DCC, LOGL_ERROR, "Invalid conn: no subscriber\n");
 		return -EINVAL;
 	}
 
@@ -3889,7 +3516,7 @@
 	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):"-",
+		transaction_id, vlr_subscr_msisdn_or_name(conn->vsub),
 		gsm48_cc_msg_name(msg_type), trans?(trans->cc.state):0,
 		gsm48_cc_state_name(trans?(trans->cc.state):0));
 
@@ -3898,7 +3525,7 @@
 		DEBUGP(DCC, "Unknown transaction ID %x, "
 			"creating new trans.\n", transaction_id);
 		/* Create transaction */
-		trans = trans_alloc(conn->network, conn->subscr,
+		trans = trans_alloc(conn->network, conn->vsub,
 				    GSM48_PDISC_CC,
 				    transaction_id, new_callref++);
 		if (!trans) {
@@ -3909,7 +3536,8 @@
 			return -ENOMEM;
 		}
 		/* Assign transaction */
-		trans->conn = conn;
+		trans->conn = msc_subscr_conn_get(conn);
+		cm_service_request_concludes(conn, msg);
 	}
 
 	/* find function for current state and message */
@@ -3922,70 +3550,74 @@
 		return 0;
 	}
 
-	assert(trans->subscr);
+	assert(trans->vsub);
 
 	rc = datastatelist[i].rout(trans, msg);
 
+	msc_subscr_conn_communicating(conn);
 	return rc;
 }
 
-/* Create a dummy to wait five seconds */
-static void release_anchor(struct gsm_subscriber_connection *conn)
+static bool msg_is_initially_permitted(const struct gsm48_hdr *hdr)
 {
-	if (!conn->anch_operation)
-		return;
+	uint8_t pdisc = gsm48_hdr_pdisc(hdr);
+	uint8_t msg_type = gsm48_hdr_msg_type(hdr);
 
-	osmo_timer_del(&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;
-
-	osmo_timer_setup(&conn->anch_operation->timeout, anchor_timeout, conn);
-	osmo_timer_schedule(&conn->anch_operation->timeout, 5, 0);
-	return 0;
-}
-
-struct gsm_subscriber_connection *msc_subscr_con_allocate(struct gsm_network *network)
-{
-	struct gsm_subscriber_connection *conn;
-
-	conn = talloc_zero(network, struct gsm_subscriber_connection);
-	if (!conn)
-		return NULL;
-
-	conn->network = network;
-	llist_add_tail(&conn->entry, &network->subscr_conns);
-	return conn;
-}
-
-void msc_subscr_con_free(struct gsm_subscriber_connection *conn)
-{
-	if (!conn)
-		return;
-
-	if (conn->subscr) {
-		subscr_put(conn->subscr);
-		conn->subscr = NULL;
+	switch (pdisc) {
+	case GSM48_PDISC_MM:
+		switch (msg_type) {
+		case GSM48_MT_MM_LOC_UPD_REQUEST:
+		case GSM48_MT_MM_CM_SERV_REQ:
+		case GSM48_MT_MM_AUTH_RESP:
+		case GSM48_MT_MM_AUTH_FAIL:
+		case GSM48_MT_MM_ID_RESP:
+		case GSM48_MT_MM_TMSI_REALL_COMPL:
+		case GSM48_MT_MM_IMSI_DETACH_IND:
+			return true;
+		default:
+			break;
+		}
+		break;
+	case GSM48_PDISC_RR:
+		switch (msg_type) {
+		case GSM48_MT_RR_CIPH_M_COMPL:
+		case GSM48_MT_RR_PAG_RESP:
+			return true;
+		default:
+			break;
+		}
+		break;
+	default:
+		break;
 	}
 
-	llist_del(&conn->entry);
-	talloc_free(conn);
+	return false;
 }
 
+void cm_service_request_concludes(struct gsm_subscriber_connection *conn,
+				  struct msgb *msg)
+{
+
+	/* If a CM Service Request was received before, this is the request the
+	 * conn was opened for. No need to wait for further messages. */
+
+	if (!conn->received_cm_service_request)
+		return;
+
+	if (log_check_level(DMM, LOGL_DEBUG)) {
+		struct gsm48_hdr *gh = msgb_l3(msg);
+		uint8_t pdisc = gsm48_hdr_pdisc(gh);
+		uint8_t msg_type = gsm48_hdr_msg_type(gh);
+
+		DEBUGP(DMM, "%s pdisc=%d msg_type=0x%02x:"
+		       " received_cm_service_request changes to false\n",
+		       vlr_subscr_name(conn->vsub),
+		       pdisc, msg_type);
+	}
+	conn->received_cm_service_request = false;
+}
+
+
 /* Main entry point for GSM 04.08/44.008 Layer 3 data (e.g. from the BSC). */
 int gsm0408_dispatch(struct gsm_subscriber_connection *conn, struct msgb *msg)
 {
@@ -3997,6 +3629,16 @@
 	OSMO_ASSERT(msg);
 
 	LOGP(DRLL, LOGL_DEBUG, "Dispatching 04.08 message, pdisc=%d\n", pdisc);
+
+	if (!msc_subscr_conn_is_accepted(conn)
+	    && !msg_is_initially_permitted(gh)) {
+		LOGP(DRLL, LOGL_ERROR,
+		     "subscr %s: Message not permitted for initial conn:"
+		     " pdisc=0x%02x msg_type=0x%02x\n",
+		     vlr_subscr_name(conn->vsub), gh->proto_discr, gh->msg_type);
+		return -EACCES;
+	}
+
 #if 0
 	if (silent_call_reroute(conn, msg))
 		return silent_call_rx(conn, msg);
@@ -4004,7 +3646,6 @@
 
 	switch (pdisc) {
 	case GSM48_PDISC_CC:
-		release_anchor(conn);
 		rc = gsm0408_rcv_cc(conn, msg);
 		break;
 	case GSM48_PDISC_MM:
@@ -4014,7 +3655,6 @@
 		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:
@@ -4024,7 +3664,6 @@
 		rc = -ENOTSUP;
 		break;
 	case GSM48_PDISC_NC_SS:
-		release_anchor(conn);
 		rc = handle_rcv_ussd(conn, msg);
 		break;
 	default:
@@ -4037,6 +3676,166 @@
 	return rc;
 }
 
+/***********************************************************************
+ * VLR integration
+ ***********************************************************************/
+
+/* VLR asks us to send an authentication request */
+static int msc_vlr_tx_auth_req(void *msc_conn_ref, struct gsm_auth_tuple *at,
+			       bool send_autn)
+{
+	struct gsm_subscriber_connection *conn = msc_conn_ref;
+	return gsm48_tx_mm_auth_req(conn, at->vec.rand,
+				    send_autn? at->vec.autn : NULL,
+				    at->key_seq);
+}
+
+/* VLR asks us to send an authentication reject */
+static int msc_vlr_tx_auth_rej(void *msc_conn_ref)
+{
+	struct gsm_subscriber_connection *conn = msc_conn_ref;
+	return gsm48_tx_mm_auth_rej(conn);
+}
+
+/* VLR asks us to transmit an Identity Request of given type */
+static int msc_vlr_tx_id_req(void *msc_conn_ref, uint8_t mi_type)
+{
+	struct gsm_subscriber_connection *conn = msc_conn_ref;
+	return mm_tx_identity_req(conn, mi_type);
+}
+
+/* VLR asks us to transmit a Location Update Accept */
+static int msc_vlr_tx_lu_acc(void *msc_conn_ref, uint32_t send_tmsi)
+{
+	struct gsm_subscriber_connection *conn = msc_conn_ref;
+	return gsm0408_loc_upd_acc(conn, send_tmsi);
+}
+
+/* VLR asks us to transmit a Location Update Reject */
+static int msc_vlr_tx_lu_rej(void *msc_conn_ref, uint8_t cause)
+{
+	struct gsm_subscriber_connection *conn = msc_conn_ref;
+	return gsm0408_loc_upd_rej(conn, cause);
+}
+
+/* VLR asks us to transmit a CM Service Accept */
+static int msc_vlr_tx_cm_serv_acc(void *msc_conn_ref)
+{
+	struct gsm_subscriber_connection *conn = msc_conn_ref;
+	return gsm48_tx_mm_serv_ack(conn);
+}
+
+/* VLR asks us to transmit a CM Service Reject */
+static int msc_vlr_tx_cm_serv_rej(void *msc_conn_ref, enum vlr_proc_arq_result result)
+{
+	uint8_t cause;
+	struct gsm_subscriber_connection *conn = msc_conn_ref;
+	conn->received_cm_service_request = false;
+
+	switch (result) {
+	default:
+	case VLR_PR_ARQ_RES_NONE:
+	case VLR_PR_ARQ_RES_SYSTEM_FAILURE:
+	case VLR_PR_ARQ_RES_UNKNOWN_ERROR:
+		cause = GSM48_REJECT_NETWORK_FAILURE;
+		break;
+	case VLR_PR_ARQ_RES_ILLEGAL_SUBSCR:
+		cause = GSM48_REJECT_LOC_NOT_ALLOWED;
+		break;
+	case VLR_PR_ARQ_RES_UNIDENT_SUBSCR:
+		cause = GSM48_REJECT_INVALID_MANDANTORY_INF;
+		break;
+	case VLR_PR_ARQ_RES_ROAMING_NOTALLOWED:
+		cause = GSM48_REJECT_ROAMING_NOT_ALLOWED;
+		break;
+	case VLR_PR_ARQ_RES_ILLEGAL_EQUIP:
+		cause = GSM48_REJECT_ILLEGAL_MS;
+		break;
+	case VLR_PR_ARQ_RES_TIMEOUT:
+		cause = GSM48_REJECT_CONGESTION;
+		break;
+	};
+
+	return gsm48_tx_mm_serv_rej(conn, cause);
+}
+
+/* VLR asks us to start using ciphering */
+static int msc_vlr_set_ciph_mode(void *msc_conn_ref,
+				 enum vlr_ciph ciph,
+				 bool retrieve_imeisv)
+{
+	struct gsm_subscriber_connection *conn = msc_conn_ref;
+	struct vlr_subscr *vsub;
+	struct gsm_auth_tuple *tuple;
+
+	if (!conn || !conn->vsub) {
+		LOGP(DMM, LOGL_ERROR, "Cannot send Ciphering Mode Command to"
+		     " NULL conn/subscriber");
+		return -EINVAL;
+	}
+
+	vsub = conn->vsub;
+	tuple = vsub->last_tuple;
+
+	if (!tuple) {
+		LOGP(DMM, LOGL_ERROR, "subscr %s: Cannot send Ciphering Mode"
+		     " Command: no auth tuple available\n",
+		     vlr_subscr_name(vsub));
+		return -EINVAL;
+	}
+
+	/* TODO: MSCSPLIT: don't directly push BSC buttons */
+	return gsm0808_cipher_mode(conn, ciph, tuple->vec.kc, 8,
+				   retrieve_imeisv);
+}
+
+/* VLR informs us that the subscriber data has somehow been modified */
+static void msc_vlr_subscr_update(struct vlr_subscr *subscr)
+{
+	/* FIXME */
+}
+
+/* VLR informs us that the subscriber has been associated with a conn */
+static void msc_vlr_subscr_assoc(void *msc_conn_ref,
+				 struct vlr_subscr *vsub)
+{
+	struct gsm_subscriber_connection *conn = msc_conn_ref;
+	OSMO_ASSERT(!conn->vsub);
+	conn->vsub = vlr_subscr_get(vsub);
+}
+
+/* operations that we need to implement for libvlr */
+static const struct vlr_ops msc_vlr_ops = {
+	.tx_auth_req = msc_vlr_tx_auth_req,
+	.tx_auth_rej = msc_vlr_tx_auth_rej,
+	.tx_id_req = msc_vlr_tx_id_req,
+	.tx_lu_acc = msc_vlr_tx_lu_acc,
+	.tx_lu_rej = msc_vlr_tx_lu_rej,
+	.tx_cm_serv_acc = msc_vlr_tx_cm_serv_acc,
+	.tx_cm_serv_rej = msc_vlr_tx_cm_serv_rej,
+	.set_ciph_mode = msc_vlr_set_ciph_mode,
+	.subscr_update = msc_vlr_subscr_update,
+	.subscr_assoc = msc_vlr_subscr_assoc,
+};
+
+/* Allocate net->vlr so that the VTY may configure the VLR's data structures */
+int msc_vlr_alloc(struct gsm_network *net)
+{
+	net->vlr = vlr_alloc(net, &msc_vlr_ops);
+	if (!net->vlr)
+		return -ENOMEM;
+	net->vlr->user_ctx = net;
+	return 0;
+}
+
+/* Launch the VLR, i.e. its GSUP connection */
+int msc_vlr_start(struct gsm_network *net)
+{
+	OSMO_ASSERT(net->vlr);
+	return vlr_start("MSC", net->vlr, net->gsup_server_addr_str,
+			 net->gsup_server_port);
+}
+
 /*
  * This will be run by the linker when loading the DSO. We use it to
  * do system initialization, e.g. registration of signal handlers.
diff --git a/src/libmsc/gsm_04_11.c b/src/libmsc/gsm_04_11.c
index aa2030f..6ad944b 100644
--- a/src/libmsc/gsm_04_11.c
+++ b/src/libmsc/gsm_04_11.c
@@ -56,6 +56,8 @@
 #include <openbsc/bsc_rll.h>
 #include <openbsc/chan_alloc.h>
 #include <openbsc/bsc_api.h>
+#include <openbsc/osmo_msc.h>
+#include <openbsc/vlr.h>
 
 #ifdef BUILD_SMPP
 #include "smpp_smsc.h"
@@ -74,7 +76,7 @@
 {
 	/* drop references to subscriber structure */
 	if (sms->receiver)
-		subscr_put(sms->receiver);
+		vlr_subscr_put(sms->receiver);
 #ifdef BUILD_SMPP
 	if (sms->smpp.esme)
 		smpp_esme_put(sms->smpp.esme);
@@ -83,8 +85,8 @@
 	talloc_free(sms);
 }
 
-struct gsm_sms *sms_from_text(struct gsm_subscriber *receiver,
-                              struct gsm_subscriber *sender,
+struct gsm_sms *sms_from_text(struct vlr_subscr *receiver,
+			      struct vlr_subscr *sender,
                               int dcs, const char *text)
 {
 	struct gsm_sms *sms = sms_alloc();
@@ -92,16 +94,16 @@
 	if (!sms)
 		return NULL;
 
-	sms->receiver = subscr_get(receiver);
+	sms->receiver = vlr_subscr_get(receiver);
 	osmo_strlcpy(sms->text, text, sizeof(sms->text));
 
-	osmo_strlcpy(sms->src.addr, sender->extension, sizeof(sms->src.addr));
+	osmo_strlcpy(sms->src.addr, sender->msisdn, sizeof(sms->src.addr));
 	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;
-	osmo_strlcpy(sms->dst.addr, receiver->extension, sizeof(sms->dst.addr));
+	osmo_strlcpy(sms->dst.addr, receiver->msisdn, sizeof(sms->dst.addr));
 	/* Generate user_data */
 	sms->user_data_len = gsm_7bit_encode_n(sms->user_data, sizeof(sms->user_data),
 						sms->text, NULL);
@@ -297,7 +299,7 @@
 			goto try_local;
 		if (rc < 0) {
 	 		LOGP(DLSMS, LOGL_ERROR, "%s: SMS delivery error: %d.",
-			     subscr_name(conn->subscr), rc);
+			     vlr_subscr_name(conn->vsub), rc);
 	 		rc = GSM411_RP_CAUSE_MO_TEMP_FAIL;
 			/* rc will be logged by gsm411_send_rp_error() */
 	 		rate_ctr_inc(&conn->bts->network->msc_ctrs->ctr[
@@ -310,8 +312,8 @@
 #endif
 
 	/* determine gsms->receiver based on dialled number */
-	gsms->receiver = subscr_get_by_extension(conn->network->subscr_group,
-						 gsms->dst.addr);
+	gsms->receiver = vlr_subscr_find_by_msisdn(conn->network->vlr,
+						   gsms->dst.addr);
 	if (!gsms->receiver) {
 #ifdef BUILD_SMPP
 		/* Avoid a second look-up */
@@ -325,7 +327,7 @@
 			rate_ctr_inc(&conn->network->msc_ctrs->ctr[MSC_CTR_SMS_NO_RECEIVER]);
 		} else if (rc < 0) {
 	 		LOGP(DLSMS, LOGL_ERROR, "%s: SMS delivery error: %d.",
-			     subscr_name(conn->subscr), rc);
+			     vlr_subscr_name(conn->vsub), rc);
 	 		rc = GSM411_RP_CAUSE_MO_TEMP_FAIL;
 			/* rc will be logged by gsm411_send_rp_error() */
 	 		rate_ctr_inc(&conn->bts->network->msc_ctrs->ctr[
@@ -469,13 +471,12 @@
 		}
 	}
 
-	osmo_strlcpy(gsms->src.addr, conn->subscr->extension,
-		     sizeof(gsms->src.addr));
+	osmo_strlcpy(gsms->src.addr, conn->vsub->msisdn, sizeof(gsms->src.addr));
 
 	LOGP(DLSMS, 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(conn->subscr), sms_mti, sms_vpf, gsms->msg_ref,
+	     vlr_subscr_name(conn->vsub), sms_mti, sms_vpf, gsms->msg_ref,
 	     gsms->protocol_id, gsms->data_coding_scheme, gsms->dst.addr,
 	     gsms->user_data_len,
 			sms_alphabet == DCS_7BIT_DEFAULT ? gsms->text :
@@ -634,7 +635,7 @@
 	 * the cause and take action depending on it */
 
 	LOGP(DLSMS, LOGL_NOTICE, "%s: RX SMS RP-ERROR, cause %d:%d (%s)\n",
-	     subscr_name(trans->conn->subscr), cause_len, cause,
+	     vlr_subscr_name(trans->conn->vsub), cause_len, cause,
 	     get_value_string(gsm411_rp_cause_strs, cause));
 
 	if (!sms) {
@@ -804,7 +805,7 @@
 	int new_trans = 0;
 	int rc = 0;
 
-	if (!conn->subscr)
+	if (!conn->vsub)
 		return -EIO;
 		/* FIXME: send some error message */
 
@@ -824,7 +825,7 @@
 
 	if (!trans) {
 		DEBUGP(DLSMS, " -> (new transaction)\n");
-		trans = trans_alloc(conn->network, conn->subscr,
+		trans = trans_alloc(conn->network, conn->vsub,
 				    GSM48_PDISC_SMS,
 				    transaction_id, new_callref++);
 		if (!trans) {
@@ -837,9 +838,10 @@
 		gsm411_smr_init(&trans->sms.smr_inst, 0, 1,
 			gsm411_rl_recv, gsm411_mn_send);
 
-		trans->conn = conn;
+		trans->conn = msc_subscr_conn_get(conn);
 
 		new_trans = 1;
+		cm_service_request_concludes(conn, msg);
 	}
 
 	/* 5.4: For MO, if a CP-DATA is received for a new
@@ -866,6 +868,8 @@
 		}
 	}
 
+	msc_subscr_conn_communicating(conn);
+
 	gsm411_smc_recv(&trans->sms.smc_inst,
 		(new_trans) ? GSM411_MMSMS_EST_IND : GSM411_MMSMS_DATA_IND,
 		msg, msg_type);
@@ -886,7 +890,7 @@
 	int rc;
 
 	transaction_id =
-		trans_assign_trans_id(conn->network, conn->subscr,
+		trans_assign_trans_id(conn->network, conn->vsub,
 				      GSM48_PDISC_SMS, 0);
 	if (transaction_id == -1) {
 		LOGP(DLSMS, LOGL_ERROR, "No available transaction ids\n");
@@ -899,7 +903,7 @@
 	DEBUGP(DLSMS, "%s()\n", __func__);
 
 	/* FIXME: allocate transaction with message reference */
-	trans = trans_alloc(conn->network, conn->subscr,
+	trans = trans_alloc(conn->network, conn->vsub,
 			    GSM48_PDISC_SMS,
 			    transaction_id, new_callref++);
 	if (!trans) {
@@ -916,7 +920,7 @@
 		gsm411_rl_recv, gsm411_mn_send);
 	trans->sms.sms = sms;
 
-	trans->conn = conn;
+	trans->conn = msc_subscr_conn_get(conn);
 
 	/* Hardcode SMSC Originating Address for now */
 	data = (uint8_t *)msgb_put(msg, 8);
@@ -994,7 +998,7 @@
 /* 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,
+int gsm411_send_sms_subscr(struct vlr_subscr *vsub,
 			   struct gsm_sms *sms)
 {
 	struct gsm_subscriber_connection *conn;
@@ -1002,18 +1006,18 @@
 
 	/* check if we already have an open lchan to the subscriber.
 	 * if yes, send the SMS this way */
-	conn = connection_for_subscr(subscr);
+	conn = connection_for_subscr(vsub);
 	if (conn) {
 		LOGP(DLSMS, LOGL_DEBUG, "Sending SMS via already open connection %p to %s\n",
-		     conn, subscr_name(subscr));
+		     conn, vlr_subscr_name(vsub));
 		return gsm411_send_sms(conn, sms);
 	}
 
 	/* if not, we have to start paging */
 	LOGP(DLSMS, LOGL_DEBUG, "Sending SMS: no connection open, start paging %s\n",
-	     subscr_name(subscr));
-	res = subscr_request_channel(subscr, RSL_CHANNEED_SDCCH,
-					paging_cb_send_sms, sms);
+	     vlr_subscr_name(vsub));
+	res = subscr_request_channel(vsub, RSL_CHANNEED_SDCCH,
+				     paging_cb_send_sms, sms);
 	if (!res) {
 		send_signal(S_SMS_UNKNOWN_ERROR, NULL, sms, GSM_PAGING_BUSY);
 		sms_free(sms);
@@ -1040,6 +1044,7 @@
 	}
 }
 
+/* Process incoming SAPI N-REJECT from BSC */
 void gsm411_sapi_n_reject(struct gsm_subscriber_connection *conn)
 {
 	struct gsm_network *net;
diff --git a/src/libmsc/gsm_subscriber.c b/src/libmsc/gsm_subscriber.c
index 1a03cf7..e9b2e0e 100644
--- a/src/libmsc/gsm_subscriber.c
+++ b/src/libmsc/gsm_subscriber.c
@@ -39,11 +39,10 @@
 #include <openbsc/signal.h>
 #include <openbsc/db.h>
 #include <openbsc/chan_alloc.h>
+#include <openbsc/vlr.h>
 
 void *tall_sub_req_ctx;
 
-extern struct llist_head *subscr_bsc_active_subscribers(void);
-
 int gsm48_secure_channel(struct gsm_subscriber_connection *conn, int key_seq,
                          gsm_cbfn *cb, void *cb_data);
 
@@ -62,31 +61,35 @@
 	void *param;
 };
 
-static struct gsm_subscriber *get_subscriber(struct gsm_subscriber_group *sgrp,
-						int type, const char *ident)
+static struct bsc_subscr *vlr_subscr_to_bsc_sub(struct llist_head *bsc_subscribers,
+						struct vlr_subscr *vsub)
 {
-	struct gsm_subscriber *subscr = db_get_subscriber(type, ident);
-	if (subscr)
-		subscr->group = sgrp;
-	return subscr;
+	struct bsc_subscr *sub;
+	/* TODO MSC split -- creating a BSC subscriber directly from MSC data
+	 * structures in RAM. At some point the MSC will send a message to the
+	 * BSC instead. */
+	sub = bsc_subscr_find_or_create_by_imsi(bsc_subscribers, vsub->imsi);
+	sub->tmsi = vsub->tmsi;
+	sub->lac = vsub->lac;
+	return sub;
 }
 
 /*
  * 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)
+int subscr_paging_dispatch(unsigned int hooknum, unsigned int event,
+			   struct msgb *msg, void *data, void *param)
 {
 	struct subscr_request *request, *tmp;
 	struct gsm_subscriber_connection *conn = data;
-	struct gsm_subscriber *subscr = param;
+	struct vlr_subscr *vsub = param;
 	struct paging_signal_data sig_data;
 	struct bsc_subscr *bsub;
 	struct gsm_network *net;
 
-	OSMO_ASSERT(subscr && subscr->is_paging);
-	net = subscr->group->net;
+	OSMO_ASSERT(vsub && vsub->cs.is_paging);
+	net = vsub->vlr->user_ctx;
 
 	/*
 	 * Stop paging on all other BTS. E.g. if this is
@@ -95,18 +98,12 @@
 	 * and forget we wanted to page.
 	 */
 
-	/* TODO MSC split -- creating a BSC subscriber directly from MSC data
-	 * structures in RAM. At some point the MSC will send a message to the
-	 * BSC instead. */
-	bsub = bsc_subscr_find_or_create_by_imsi(net->bsc_subscribers,
-						 subscr->imsi);
-	bsub->tmsi = subscr->tmsi;
-	bsub->lac = subscr->lac;
+	bsub = vlr_subscr_to_bsc_sub(conn->network->bsc_subscribers, vsub);
 	paging_request_stop(&net->bts_list, NULL, bsub, NULL, NULL);
 	bsc_subscr_put(bsub);
 
 	/* Inform parts of the system we don't know */
-	sig_data.subscr = subscr;
+	sig_data.vsub	= vsub;
 	sig_data.bts	= conn ? conn->bts : NULL;
 	sig_data.conn	= conn;
 	sig_data.paging_result = event;
@@ -117,105 +114,52 @@
 		&sig_data
 	);
 
-	llist_for_each_entry_safe(request, tmp, &subscr->requests, entry) {
+	llist_for_each_entry_safe(request, tmp, &vsub->cs.requests, entry) {
 		llist_del(&request->entry);
 		request->cbfn(hooknum, event, msg, data, request->param);
 		talloc_free(request);
 	}
 
 	/* balanced with the moment we start paging */
-	subscr->is_paging = 0;
-	subscr_put(subscr);
+	vsub->cs.is_paging = false;
+	vlr_subscr_put(vsub);
 	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);
-}
-
-struct subscr_request *subscr_request_channel(struct gsm_subscriber *subscr,
-			int channel_type, gsm_cbfn *cbfn, void *param)
+struct subscr_request *subscr_request_channel(struct vlr_subscr *vsub,
+					      int channel_type,
+					      gsm_cbfn *cbfn, void *param)
 {
 	int rc;
 	struct subscr_request *request;
 	struct bsc_subscr *bsub;
-	struct gsm_network *net = subscr->group->net;
+	struct gsm_network *net = vsub->vlr->user_ctx;
 
 	/* Start paging.. we know it is async so we can do it before */
-	if (!subscr->is_paging) {
+	if (!vsub->cs.is_paging) {
 		LOGP(DMM, LOGL_DEBUG, "Subscriber %s not paged yet.\n",
-			subscr_name(subscr));
-		/* TODO MSC split -- creating a BSC subscriber directly from
-		 * MSC data structures in RAM. At some point the MSC will send
-		 * a message to the BSC instead. */
-		bsub = bsc_subscr_find_or_create_by_imsi(net->bsc_subscribers,
-							 subscr->imsi);
-		bsub->tmsi = subscr->tmsi;
-		bsub->lac = subscr->lac;
-		rc = paging_request(net, bsub, channel_type, subscr_paging_cb,
-				    subscr);
+		     vlr_subscr_name(vsub));
+		bsub = vlr_subscr_to_bsc_sub(net->bsc_subscribers, vsub);
+		rc = paging_request(net, bsub, channel_type, NULL, NULL);
 		bsc_subscr_put(bsub);
 		if (rc <= 0) {
 			LOGP(DMM, LOGL_ERROR, "Subscriber %s paging failed: %d\n",
-				subscr_name(subscr), rc);
+			     vlr_subscr_name(vsub), rc);
 			return NULL;
 		}
 		/* reduced on the first paging callback */
-		subscr_get(subscr);
-		subscr->is_paging = 1;
+		vlr_subscr_get(vsub);
+		vsub->cs.is_paging = true;
 	}
 
 	/* TODO: Stop paging in case of memory allocation failure */
-	request = talloc_zero(subscr, struct subscr_request);
+	request = talloc_zero(vsub, struct subscr_request);
 	if (!request)
 		return NULL;
 
 	request->cbfn = cbfn;
 	request->param = param;
-	llist_add_tail(&request->entry, &subscr->requests);
+	llist_add_tail(&request->entry, &vsub->cs.requests);
 	return request;
 }
 
@@ -225,196 +169,13 @@
 	talloc_free(request);
 }
 
-struct gsm_subscriber *subscr_create_subscriber(struct gsm_subscriber_group *sgrp,
-						const char *imsi)
+struct gsm_subscriber_connection *connection_for_subscr(struct vlr_subscr *vsub)
 {
-	struct gsm_subscriber *subscr = db_create_subscriber(imsi,
-							     sgrp->net->ext_min,
-							     sgrp->net->ext_max,
-							     sgrp->net->auto_assign_exten);
-	if (subscr)
-		subscr->group = sgrp;
-	return subscr;
-}
-
-struct gsm_subscriber *subscr_get_by_tmsi(struct gsm_subscriber_group *sgrp,
-					  uint32_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_subscribers(), entry) {
-		if (tmsi == subscr->tmsi)
-			return subscr_get(subscr);
-	}
-
-	sprintf(tmsi_string, "%u", tmsi);
-	return get_subscriber(sgrp, GSM_SUBSCRIBER_TMSI, tmsi_string);
-}
-
-struct gsm_subscriber *subscr_get_by_imsi(struct gsm_subscriber_group *sgrp,
-					  const char *imsi)
-{
-	struct gsm_subscriber *subscr;
-
-	llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) {
-		if (strcmp(subscr->imsi, imsi) == 0)
-			return subscr_get(subscr);
-	}
-
-	return get_subscriber(sgrp, GSM_SUBSCRIBER_IMSI, imsi);
-}
-
-struct gsm_subscriber *subscr_get_by_extension(struct gsm_subscriber_group *sgrp,
-					       const char *ext)
-{
-	struct gsm_subscriber *subscr;
-
-	llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) {
-		if (strcmp(subscr->extension, ext) == 0)
-			return subscr_get(subscr);
-	}
-
-	return get_subscriber(sgrp, GSM_SUBSCRIBER_EXTENSION, ext);
-}
-
-struct gsm_subscriber *subscr_get_by_id(struct gsm_subscriber_group *sgrp,
-					unsigned long long id)
-{
-	struct gsm_subscriber *subscr;
-	char buf[32];
-	sprintf(buf, "%llu", id);
-
-	llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) {
-		if (subscr->id == id)
-			return subscr_get(subscr);
-	}
-
-	return get_subscriber(sgrp, GSM_SUBSCRIBER_ID, buf);
-}
-
-int subscr_update_expire_lu(struct gsm_subscriber *s, struct gsm_bts *bts)
-{
-	int rc;
-
-	if (!s) {
-		LOGP(DMM, LOGL_ERROR, "LU Expiration but NULL subscriber\n");
-		return -1;
-	}
-	if (!bts) {
-		LOGP(DMM, LOGL_ERROR, "%s: LU Expiration but NULL bts\n",
-		     subscr_name(s));
-		return -1;
-	}
-
-	/* Table 10.5.33: The T3212 timeout value field is coded as the
-	 * binary representation of the timeout value for
-	 * periodic updating in decihours. Mark the subscriber as
-	 * inactive if it missed two consecutive location updates.
-	 * Timeout is twice the t3212 value plus one minute */
-
-	/* Is expiration handling enabled? */
-	if (bts->si_common.chan_desc.t3212 == 0)
-		s->expire_lu = GSM_SUBSCRIBER_NO_EXPIRATION;
-	else
-		s->expire_lu = time(NULL) +
-			(bts->si_common.chan_desc.t3212 * 60 * 6 * 2) + 60;
-
-	rc = db_sync_subscriber(s);
-	db_subscriber_update(s);
-	return rc;
-}
-
-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->group = bts->network->subscr_group;
-		/* 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);
-
-		/*
-		 * The below will set a new expire_lu but as a side-effect
-		 * the new lac will be saved in the database.
-		 */
-		rc = subscr_update_expire_lu(s, bts);
-		osmo_signal_dispatch(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);
-		osmo_signal_dispatch(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);
-}
-
-static void subscr_expire_callback(void *data, long long unsigned int id)
-{
-	struct gsm_network *net = data;
-	struct gsm_subscriber *s = subscr_get_by_id(net->subscr_group, id);
-	struct gsm_subscriber_connection *conn = connection_for_subscr(s);
-
-	/*
-	 * The subscriber is active and the phone stopped the timer. As
-	 * we don't want to periodically update the database for active
-	 * subscribers we will just do it when the subscriber was selected
-	 * for expiration. This way on the next around another subscriber
-	 * will be selected.
-	 */
-	if (conn && conn->expire_timer_stopped) {
-		LOGP(DMM, LOGL_DEBUG, "Not expiring subscriber %s (ID %llu)\n",
-			subscr_name(s), id);
-		subscr_update_expire_lu(s, conn->bts);
-		subscr_put(s);
-		return;
-	}
-
-
-	LOGP(DMM, LOGL_NOTICE, "Expiring inactive subscriber %s (ID %llu)\n",
-			subscr_name(s), id);
-	s->lac = GSM_LAC_RESERVED_DETACHED;
-	db_sync_subscriber(s);
-
-	subscr_put(s);
-}
-
-void subscr_expire(struct gsm_subscriber_group *sgrp)
-{
-	db_subscriber_expire(sgrp->net, subscr_expire_callback);
-}
-
-struct gsm_subscriber_connection *connection_for_subscr(struct gsm_subscriber *subscr)
-{
-	/* FIXME: replace this with a backpointer in gsm_subscriber? */
-	struct gsm_network *net = subscr->group->net;
+	struct gsm_network *net = vsub->vlr->user_ctx;
 	struct gsm_subscriber_connection *conn;
 
 	llist_for_each_entry(conn, &net->subscr_conns, entry) {
-		if (conn->subscr == subscr)
+		if (conn->vsub == vsub)
 			return conn;
 	}
 
diff --git a/src/libmsc/meas_feed.c b/src/libmsc/meas_feed.c
index 3ddcdc3..1e7b4cd 100644
--- a/src/libmsc/meas_feed.c
+++ b/src/libmsc/meas_feed.c
@@ -18,6 +18,7 @@
 #include <openbsc/gsm_subscriber.h>
 #include <openbsc/meas_feed.h>
 #include <openbsc/vty.h>
+#include <openbsc/vlr.h>
 
 #include "meas_feed.h"
 
@@ -35,13 +36,13 @@
 {
 	struct msgb *msg;
 	struct meas_feed_meas *mfm;
-	struct gsm_subscriber *subscr;
+	struct vlr_subscr *vsub;
 
 	/* ignore measurements as long as we don't know who it is */
-	if (!mr->lchan || !mr->lchan->conn || !mr->lchan->conn->subscr)
+	if (!mr->lchan || !mr->lchan->conn || !mr->lchan->conn->vsub)
 		return 0;
 
-	subscr = mr->lchan->conn->subscr;
+	vsub = mr->lchan->conn->vsub;
 
 	msg = msgb_alloc(sizeof(struct meas_feed_meas), "Meas. Feed");
 	if (!msg)
@@ -53,8 +54,8 @@
 	mfm->hdr.version = MEAS_FEED_VERSION;
 
 	/* fill in MEAS_FEED_MEAS specific header */
-	osmo_strlcpy(mfm->imsi, subscr->imsi, sizeof(mfm->imsi));
-	osmo_strlcpy(mfm->name, subscr->name, sizeof(mfm->name));
+	osmo_strlcpy(mfm->imsi, vsub->imsi, sizeof(mfm->imsi));
+	osmo_strlcpy(mfm->name, vsub->name, sizeof(mfm->name));
 	osmo_strlcpy(mfm->scenario, g_mfs.scenario, sizeof(mfm->scenario));
 
 	/* copy the entire measurement report */
diff --git a/src/libmsc/osmo_msc.c b/src/libmsc/osmo_msc.c
index 2389980..95e5818 100644
--- a/src/libmsc/osmo_msc.c
+++ b/src/libmsc/osmo_msc.c
@@ -25,9 +25,12 @@
 #include <openbsc/debug.h>
 #include <openbsc/transaction.h>
 #include <openbsc/db.h>
+#include <openbsc/vlr.h>
+#include <openbsc/osmo_msc.h>
 
 #include <openbsc/gsm_04_11.h>
 
+/* Receive a SAPI-N-REJECT from BSC */
 static void msc_sapi_n_reject(struct gsm_subscriber_connection *conn, int dlci)
 {
 	int sapi = dlci & 0x7;
@@ -36,18 +39,66 @@
 		gsm411_sapi_n_reject(conn);
 }
 
-static int msc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause)
+static bool keep_conn(struct gsm_subscriber_connection *conn)
 {
-	gsm0408_clear_request(conn, cause);
-	return 1;
+	/* TODO: what about a silent call? */
+
+	if (!conn->conn_fsm) {
+		DEBUGP(DMM, "No conn_fsm, release conn\n");
+		return false;
+	}
+
+	switch (conn->conn_fsm->state) {
+	case SUBSCR_CONN_S_NEW:
+	case SUBSCR_CONN_S_ACCEPTED:
+		return true;
+	default:
+		return false;
+	}
 }
 
+static void subscr_conn_bump(struct gsm_subscriber_connection *conn)
+{
+	if (!conn)
+		return;
+	if (!conn->conn_fsm)
+		return;
+	if (!(conn->conn_fsm->state == SUBSCR_CONN_S_ACCEPTED
+	      || conn->conn_fsm->state == SUBSCR_CONN_S_COMMUNICATING))
+		return;
+	osmo_fsm_inst_dispatch(conn->conn_fsm, SUBSCR_CONN_E_BUMP, NULL);
+}
+
+/* Receive a COMPLETE LAYER3 INFO from BSC */
 static int msc_compl_l3(struct gsm_subscriber_connection *conn, struct msgb *msg,
 			uint16_t chosen_channel)
 {
-	gsm0408_new_conn(conn);
+	/* Ownership of the gsm_subscriber_connection is still a bit mucky
+	 * between libbsc and libmsc. In libmsc, we use ref counting, but not
+	 * in libbsc. This will become simpler with the MSCSPLIT. */
+
+	/* reserve for the duration of this function */
+	msc_subscr_conn_get(conn);
+
 	gsm0408_dispatch(conn, msg);
 
+	if (!keep_conn(conn)) {
+		DEBUGP(DMM, "compl_l3: Discarding conn\n");
+		/* keep the use_count reserved, libbsc will discard. If we
+		 * released the ref count and discarded here, libbsc would
+		 * double-free. And we will not change bsc_api semantics. */
+		return BSC_API_CONN_POL_REJECT;
+	}
+	DEBUGP(DMM, "compl_l3: Keeping conn\n");
+
+	/* Bump whether the conn wants to be closed */
+	subscr_conn_bump(conn);
+
+	/* If this should be kept, the conn->conn_fsm has placed a use_count */
+	msc_subscr_conn_put(conn);
+	return BSC_API_CONN_POL_ACCEPT;
+
+#if 0
 	/*
 	 * If this is a silent call we want the channel to remain open as long as
 	 * possible and this is why we accept this connection regardless of any
@@ -55,20 +106,28 @@
 	 */
 	if (conn->silent_call)
 		return BSC_API_CONN_POL_ACCEPT;
-	if (conn->loc_operation || conn->sec_operation || conn->anch_operation)
+	if (conn->sec_operation || conn->anch_operation)
 		return BSC_API_CONN_POL_ACCEPT;
 	if (trans_has_conn(conn))
 		return BSC_API_CONN_POL_ACCEPT;
 
 	LOGP(DRR, LOGL_INFO, "MSC Complete L3: Rejecting connection.\n");
 	return BSC_API_CONN_POL_REJECT;
+#endif
 }
 
+/* Receive a DTAP message from BSC */
 static void msc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg)
 {
+	msc_subscr_conn_get(conn);
 	gsm0408_dispatch(conn, msg);
+
+	/* Bump whether the conn wants to be closed */
+	subscr_conn_bump(conn);
+	msc_subscr_conn_put(conn);
 }
 
+/* Receive an ASSIGNMENT COMPLETE from BSC */
 static void msc_assign_compl(struct gsm_subscriber_connection *conn,
 			     uint8_t rr_cause, uint8_t chosen_channel,
 			     uint8_t encr_alg_id, uint8_t speec)
@@ -76,58 +135,150 @@
 	LOGP(DRR, LOGL_DEBUG, "MSC assign complete (do nothing).\n");
 }
 
+/* Receive an ASSIGNMENT FAILURE from BSC */
 static void msc_assign_fail(struct gsm_subscriber_connection *conn,
 			    uint8_t cause, uint8_t *rr_cause)
 {
 	LOGP(DRR, LOGL_DEBUG, "MSC assign failure (do nothing).\n");
 }
 
+/* Receive a CLASSMARK CHANGE from BSC */
 static void msc_classmark_chg(struct gsm_subscriber_connection *conn,
 			      const uint8_t *cm2, uint8_t cm2_len,
 			      const uint8_t *cm3, uint8_t cm3_len)
 {
-	struct gsm_subscriber *subscr = conn->subscr;
-
-	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);
+	if (cm2 && cm2_len) {
+		if (cm2_len > sizeof(conn->classmark.classmark2)) {
+			LOGP(DRR, LOGL_NOTICE, "%s: classmark2 is %u bytes, truncating at %zu bytes\n",
+			     vlr_subscr_name(conn->vsub), cm2_len, sizeof(conn->classmark.classmark2));
+			cm2_len = sizeof(conn->classmark.classmark2);
 		}
-		db_sync_equipment(&subscr->equipment);
+		conn->classmark.classmark2_len = cm2_len;
+		memcpy(conn->classmark.classmark2, cm2, cm2_len);
+	}
+	if (cm3 && cm3_len) {
+		if (cm3_len > sizeof(conn->classmark.classmark3)) {
+			LOGP(DRR, LOGL_NOTICE, "%s: classmark3 is %u bytes, truncating at %zu bytes\n",
+			     vlr_subscr_name(conn->vsub), cm3_len, sizeof(conn->classmark.classmark3));
+			cm3_len = sizeof(conn->classmark.classmark3);
+		}
+		conn->classmark.classmark3_len = cm3_len;
+		memcpy(conn->classmark.classmark3, cm3, cm3_len);
 	}
 }
 
+/* Receive a CIPHERING MODE COMPLETE from BSC */
 static void msc_ciph_m_compl(struct gsm_subscriber_connection *conn,
 			     struct msgb *msg, uint8_t alg_id)
 {
-	gsm_cbfn *cb;
+	struct gsm48_hdr *gh = msgb_l3(msg);
+	unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
+	struct tlv_parsed tp;
+	uint8_t mi_type;
+	char imeisv[GSM48_MI_SIZE] = "";
+	struct vlr_ciph_result ciph_res = { .cause = VLR_CIPH_REJECT };
 
-	DEBUGP(DRR, "CIPHERING MODE COMPLETE\n");
-
-	/* Safety check */
-	if (!conn->sec_operation) {
-		DEBUGP(DRR, "No authentication/cipher operation in progress !!!\n");
+	if (!gh) {
+		LOGP(DRR, LOGL_ERROR, "invalid: msgb without l3 header\n");
 		return;
 	}
 
-	/* FIXME: check for MI (if any) */
-
-	/* Call back whatever was in progress (if anything) ... */
-	cb = conn->sec_operation->cb;
-	if (cb) {
-		cb(GSM_HOOK_RR_SECURITY, GSM_SECURITY_SUCCEEDED,
-			NULL, conn, conn->sec_operation->cb_data);
-
+	if (!conn) {
+		LOGP(DRR, LOGL_ERROR,
+		     "invalid: rx Ciphering Mode Complete on NULL conn\n");
+		return;
+	}
+	if (!conn->vsub) {
+		LOGP(DRR, LOGL_ERROR,
+		     "invalid: rx Ciphering Mode Complete for NULL subscr\n");
+		return;
 	}
 
-	/* Complete the operation */
-	release_security_operation(conn);
+	DEBUGP(DRR, "%s: CIPHERING MODE COMPLETE\n",
+	       vlr_subscr_name(conn->vsub));
+
+	tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
+
+	/* bearer capability */
+	if (TLVP_PRESENT(&tp, GSM48_IE_MOBILE_ID)) {
+		mi_type = TLVP_VAL(&tp, GSM48_IE_MOBILE_ID)[0] & GSM_MI_TYPE_MASK;
+		if (mi_type == GSM_MI_TYPE_IMEISV
+		    && TLVP_LEN(&tp, GSM48_IE_MOBILE_ID) > 0) {
+			gsm48_mi_to_string(imeisv, sizeof(imeisv),
+					   TLVP_VAL(&tp, GSM48_IE_MOBILE_ID),
+					   TLVP_LEN(&tp, GSM48_IE_MOBILE_ID));
+			ciph_res.imeisv = imeisv;
+		}
+	}
+
+	ciph_res.cause = VLR_CIPH_COMPL;
+	vlr_subscr_rx_ciph_res(conn->vsub, &ciph_res);
 }
 
+struct gsm_subscriber_connection *msc_subscr_con_allocate(struct gsm_network *network)
+{
+	struct gsm_subscriber_connection *conn;
 
+	conn = talloc_zero(network, struct gsm_subscriber_connection);
+	if (!conn)
+		return NULL;
 
+	conn->network = network;
+	llist_add_tail(&conn->entry, &network->subscr_conns);
+	return conn;
+}
+
+void msc_subscr_cleanup(struct vlr_subscr *vsub)
+{
+	if (!vsub)
+		return;
+	vsub->lu_fsm = NULL;
+}
+
+void msc_subscr_con_cleanup(struct gsm_subscriber_connection *conn)
+{
+	if (!conn)
+		return;
+
+	if (conn->vsub) {
+		DEBUGP(DRLL, "subscr %s: Freeing subscriber connection\n",
+		       vlr_subscr_name(conn->vsub));
+		msc_subscr_cleanup(conn->vsub);
+		vlr_subscr_put(conn->vsub);
+		conn->vsub = NULL;
+	} else
+		DEBUGP(DRLL, "Freeing subscriber connection"
+		       " with NULL subscriber\n");
+
+	if (!conn->conn_fsm)
+		return;
+
+	osmo_fsm_inst_term(conn->conn_fsm,
+			   (conn->conn_fsm->state == SUBSCR_CONN_S_RELEASED)
+				? OSMO_FSM_TERM_REGULAR
+				: OSMO_FSM_TERM_ERROR,
+			   NULL);
+}
+
+void msc_subscr_con_free(struct gsm_subscriber_connection *conn)
+{
+	if (!conn)
+		return;
+
+	msc_subscr_con_cleanup(conn);
+
+	llist_del(&conn->entry);
+	talloc_free(conn);
+}
+
+/* Receive a CLEAR REQUEST from BSC */
+static int msc_clear_request(struct gsm_subscriber_connection *conn, uint32_t cause)
+{
+	msc_subscr_conn_close(conn, cause);
+	return 1;
+}
+
+/* MSC-level operations to be called by libbsc in NITB */
 static struct bsc_api msc_handler = {
 	.sapi_n_reject = msc_sapi_n_reject,
 	.compl_l3 = msc_compl_l3,
@@ -137,41 +288,108 @@
 	.assign_fail = msc_assign_fail,
 	.classmark_chg = msc_classmark_chg,
 	.cipher_mode_compl = msc_ciph_m_compl,
+	.conn_cleanup = msc_subscr_con_cleanup,
 };
 
 struct bsc_api *msc_bsc_api() {
 	return &msc_handler;
 }
 
-/* lchan release handling */
-void msc_release_connection(struct gsm_subscriber_connection *conn)
+static void msc_subscr_conn_release_all(struct gsm_subscriber_connection *conn, uint32_t cause)
 {
-	/* skip when we are in release, e.g. due an error */
 	if (conn->in_release)
 		return;
+	conn->in_release = true;
 
-	/* skip releasing of silent calls as they have no transaction */
-	if (conn->silent_call)
+	/* If we're closing in a middle of a trans, we need to clean up */
+	trans_conn_closed(conn);
+
+	switch (conn->via_ran) {
+	case RAN_UTRAN_IU:
+		/* future: iu_tx_release(conn->iu.ue_ctx, NULL); */
+		break;
+	case RAN_GERAN_A:
+		/* future: a_iface_tx_clear_cmd(conn); */
+		break;
+	default:
+		LOGP(DMM, LOGL_ERROR, "%s: Unknown RAN type, cannot tx release/clear\n",
+		     vlr_subscr_name(conn->vsub));
+		break;
+	}
+}
+
+/* If the conn->conn_fsm is still present, dispatch SUBSCR_CONN_E_CN_CLOSE
+ * event to gracefully terminate the connection. If the conn_fsm is already
+ * cleared, call msc_subscr_conn_release_all() to take release actions.
+ * \param cause  a GSM_CAUSE_* constant, e.g. GSM_CAUSE_AUTH_FAILED.
+ */
+void msc_subscr_conn_close(struct gsm_subscriber_connection *conn,
+			   uint32_t cause)
+{
+	if (!conn)
 		return;
-
-	/* check if there is a pending operation */
-	if (conn->loc_operation || conn->sec_operation || conn->anch_operation)
+	if (conn->in_release) {
+		DEBUGP(DMM, "msc_subscr_conn_close(vsub=%s, cause=%u):"
+		       " already dispatching release, ignore.\n",
+		       vlr_subscr_name(conn->vsub), cause);
 		return;
-
-	if (trans_has_conn(conn))
+	}
+	if (!conn->conn_fsm) {
+		DEBUGP(DMM, "msc_subscr_conn_close(vsub=%s, cause=%u): no conn fsm,"
+		       " releasing directly without release event.\n",
+		       vlr_subscr_name(conn->vsub), cause);
+		/* In case of an IMSI Detach, we don't have conn_fsm. Release
+		 * anyway to ensure a timely Iu Release / BSSMAP Clear. */
+		msc_subscr_conn_release_all(conn, cause);
 		return;
+	}
+	if (conn->conn_fsm->state == SUBSCR_CONN_S_RELEASED) {
+		DEBUGP(DMM, "msc_subscr_conn_close(vsub=%s, cause=%u):"
+		       " conn fsm already releasing, ignore.\n",
+		       vlr_subscr_name(conn->vsub), cause);
+		return;
+	}
+	osmo_fsm_inst_dispatch(conn->conn_fsm, SUBSCR_CONN_E_CN_CLOSE, &cause);
+}
 
-	/* no more connections, asking to release the channel */
+/* increment the ref-count. Needs to be called by every user */
+struct gsm_subscriber_connection *
+_msc_subscr_conn_get(struct gsm_subscriber_connection *conn,
+		     const char *file, int line)
+{
+	OSMO_ASSERT(conn);
 
-	/*
-	 * We had stopped the LU expire timer T3212. Now we are about
-	 * to send the MS back to the idle state and this should lead
-	 * to restarting the timer. Set the new expiration time.
-	 */
-	if (conn->expire_timer_stopped)
-		subscr_update_expire_lu(conn->subscr, conn->bts);
+	if (conn->in_release)
+		return NULL;
 
-	conn->in_release = 1;
-	gsm0808_clear(conn);
-	msc_subscr_con_free(conn);
+	conn->use_count++;
+	LOGPSRC(DREF, LOGL_DEBUG, file, line,
+		"%s: MSC conn use + 1 == %u\n",
+		vlr_subscr_name(conn->vsub), conn->use_count);
+
+	return conn;
+}
+
+/* decrement the ref-count. Once it reaches zero, we release */
+void _msc_subscr_conn_put(struct gsm_subscriber_connection *conn,
+			  const char *file, int line)
+{
+	OSMO_ASSERT(conn);
+
+	if (conn->use_count == 0) {
+		LOGPSRC(DREF, LOGL_ERROR, file, line,
+			"%s: MSC conn use - 1 failed: is already 0\n",
+			vlr_subscr_name(conn->vsub));
+		return;
+	}
+
+	conn->use_count--;
+	LOGPSRC(DREF, LOGL_DEBUG, file, line,
+		"%s: MSC conn use - 1 == %u\n",
+		vlr_subscr_name(conn->vsub), conn->use_count);
+
+	if (conn->use_count == 0) {
+		gsm0808_clear(conn);
+		bsc_subscr_con_free(conn);
+	}
 }
diff --git a/src/libmsc/rrlp.c b/src/libmsc/rrlp.c
index e695daa..cd3da06 100644
--- a/src/libmsc/rrlp.c
+++ b/src/libmsc/rrlp.c
@@ -65,14 +65,14 @@
 static int subscr_sig_cb(unsigned int subsys, unsigned int signal,
 			 void *handler_data, void *signal_data)
 {
-	struct gsm_subscriber *subscr;
+	struct vlr_subscr *vsub;
 	struct gsm_subscriber_connection *conn;
 
 	switch (signal) {
 	case S_SUBSCR_ATTACHED:
 		/* A subscriber has attached. */
-		subscr = signal_data;
-		conn = connection_for_subscr(subscr);
+		vsub = signal_data;
+		conn = connection_for_subscr(vsub);
 		if (!conn)
 			break;
 		send_rrlp_req(conn);
diff --git a/src/libmsc/silent_call.c b/src/libmsc/silent_call.c
index 590d01b..6f3fbf2 100644
--- a/src/libmsc/silent_call.c
+++ b/src/libmsc/silent_call.c
@@ -55,6 +55,7 @@
 		DEBUGPC(DLSMS, "success, using Timeslot %u on ARFCN %u\n",
 			conn->lchan->ts->nr, conn->lchan->ts->trx->arfcn);
 		conn->silent_call = 1;
+		msc_subscr_conn_get(conn);
 		/* increment lchan reference count */
 		osmo_signal_dispatch(SS_SCALL, S_SCALL_SUCCESS, &sigdata);
 		break;
@@ -121,20 +122,20 @@
 
 
 /* initiate a silent call with a given subscriber */
-int gsm_silent_call_start(struct gsm_subscriber *subscr, void *data, int type)
+int gsm_silent_call_start(struct vlr_subscr *vsub, void *data, int type)
 {
 	struct subscr_request *req;
 
-	req = subscr_request_channel(subscr, type, paging_cb_silent, data);
+	req = subscr_request_channel(vsub, type, paging_cb_silent, data);
 	return req != NULL;
 }
 
 /* end a silent call with a given subscriber */
-int gsm_silent_call_stop(struct gsm_subscriber *subscr)
+int gsm_silent_call_stop(struct vlr_subscr *vsub)
 {
 	struct gsm_subscriber_connection *conn;
 
-	conn = connection_for_subscr(subscr);
+	conn = connection_for_subscr(vsub);
 	if (!conn)
 		return -EINVAL;
 
@@ -146,7 +147,7 @@
 		conn->lchan->ts->nr, conn->lchan->ts->trx->arfcn);
 
 	conn->silent_call = 0;
-	msc_release_connection(conn);
+	msc_subscr_conn_put(conn);
 
 	return 0;
 }
diff --git a/src/libmsc/smpp_openbsc.c b/src/libmsc/smpp_openbsc.c
index f94968a..f06eb7d 100644
--- a/src/libmsc/smpp_openbsc.c
+++ b/src/libmsc/smpp_openbsc.c
@@ -45,31 +45,32 @@
 #include <openbsc/transaction.h>
 #include <openbsc/gsm_subscriber.h>
 #include <openbsc/chan_alloc.h>
+#include <openbsc/vlr.h>
 
 #include "smpp_smsc.h"
 
-/*! \brief find gsm_subscriber for a given SMPP NPI/TON/Address */
-static struct gsm_subscriber *subscr_by_dst(struct gsm_network *net,
-					    uint8_t npi, uint8_t ton, const char *addr)
+/*! \brief find vlr_subscr for a given SMPP NPI/TON/Address */
+static struct vlr_subscr *subscr_by_dst(struct gsm_network *net,
+					    uint8_t npi, uint8_t ton,
+					    const char *addr)
 {
-	struct gsm_subscriber *subscr = NULL;
+	struct vlr_subscr *vsub = NULL;
 
 	switch (npi) {
 	case NPI_Land_Mobile_E212:
-		subscr = subscr_get_by_imsi(net->subscr_group, addr);
+		vsub = vlr_subscr_find_by_imsi(net->vlr, addr);
 		break;
 	case NPI_ISDN_E163_E164:
 	case NPI_Private:
-		subscr = subscr_get_by_extension(net->subscr_group, addr);
+		vsub = vlr_subscr_find_by_msisdn(net->vlr, addr);
 		break;
 	default:
 		LOGP(DSMPP, LOGL_NOTICE, "Unsupported NPI: %u\n", npi);
 		break;
 	}
 
-	/* tag the context in case we know it */
-	log_set_context(LOG_CTX_VLR_SUBSCR, subscr);
-	return subscr;
+	log_set_context(LOG_CTX_VLR_SUBSCR, vsub);
+	return vsub;
 }
 
 /*! \brief find a TLV with given tag in list of libsmpp34 TLVs */
@@ -88,7 +89,7 @@
 static int submit_to_sms(struct gsm_sms **psms, struct gsm_network *net,
 			 const struct submit_sm_t *submit)
 {
-	struct gsm_subscriber *dest;
+	struct vlr_subscr *dest;
 	struct gsm_sms *sms;
 	struct tlv_t *t;
 	const uint8_t *sms_msg;
@@ -111,7 +112,7 @@
 			/* ERROR: we cannot have both! */
 			LOGP(DLSMS, LOGL_ERROR, "SMPP Cannot have payload in "
 				"TLV _and_ in the header\n");
-			subscr_put(dest);
+			vlr_subscr_put(dest);
 			return ESME_ROPTPARNOTALLWD;
 		}
 		sms_msg = t->value.octet;
@@ -122,7 +123,7 @@
 	} else {
 		LOGP(DLSMS, LOGL_ERROR,
 			"SMPP neither message payload nor valid sm_length.\n");
-		subscr_put(dest);
+		vlr_subscr_put(dest);
 		return ESME_RINVPARLEN;
 	}
 
@@ -134,7 +135,7 @@
 	sms->receiver = dest;
 	sms->dst.ton = submit->dest_addr_ton;
 	sms->dst.npi = submit->dest_addr_npi;
-	osmo_strlcpy(sms->dst.addr, dest->extension, sizeof(sms->dst.addr));
+	osmo_strlcpy(sms->dst.addr, dest->msisdn, sizeof(sms->dst.addr));
 
 	/* fill in the source address */
 	sms->src.ton = submit->source_addr_ton;
@@ -252,7 +253,7 @@
 	return rc;
 }
 
-static void alert_all_esme(struct smsc *smsc, struct gsm_subscriber *subscr,
+static void alert_all_esme(struct smsc *smsc, struct vlr_subscr *vsub,
 			   uint8_t smpp_avail_status)
 {
 	struct osmo_esme *esme;
@@ -265,11 +266,11 @@
 		if (esme->acl && esme->acl->deliver_src_imsi) {
 			smpp_tx_alert(esme, TON_Subscriber_Number,
 				      NPI_Land_Mobile_E212,
-				      subscr->imsi, smpp_avail_status);
+				      vsub->imsi, smpp_avail_status);
 		} else {
 			smpp_tx_alert(esme, TON_Network_Specific,
 				      NPI_ISDN_E163_E164,
-				      subscr->extension, smpp_avail_status);
+				      vsub->msisdn, smpp_avail_status);
 		}
 	}
 }
@@ -316,7 +317,7 @@
 		}
 		break;
 	case S_SMS_SMMA:
-		if (!sig_sms->trans || !sig_sms->trans->subscr) {
+		if (!sig_sms->trans || !sig_sms->trans->vsub) {
 			/* SMMA without a subscriber? strange... */
 			LOGP(DLSMS, LOGL_NOTICE, "SMMA without subscriber?\n");
 			break;
@@ -325,7 +326,7 @@
 		/* There's no real 1:1 match for SMMA in SMPP.  However,
 		 * an ALERT NOTIFICATION seems to be the most logical
 		 * choice */
-		alert_all_esme(smsc, sig_sms->trans->subscr, 0);
+		alert_all_esme(smsc, sig_sms->trans->vsub, 0);
 		break;
 	}
 
@@ -336,7 +337,7 @@
 static int smpp_subscr_cb(unsigned int subsys, unsigned int signal,
 			  void *handler_data, void *signal_data)
 {
-	struct gsm_subscriber *subscr = signal_data;
+	struct vlr_subscr *vsub = signal_data;
 	struct smsc *smsc = handler_data;
 	uint8_t smpp_avail_status;
 
@@ -352,7 +353,7 @@
 		return 0;
 	}
 
-	alert_all_esme(smsc, subscr, smpp_avail_status);
+	alert_all_esme(smsc, vsub, smpp_avail_status);
 
 	return 0;
 }
@@ -452,12 +453,12 @@
 			      dl_meas->full.rx_qual);
 	}
 
-	if (lchan->conn && lchan->conn->subscr) {
-		struct gsm_subscriber *subscr = lchan->conn->subscr;
-		size_t imei_len = strlen(subscr->equipment.imei);
+	if (lchan->conn && lchan->conn->vsub) {
+		struct vlr_subscr *vsub = lchan->conn->vsub;
+		size_t imei_len = strlen(vsub->imei);
 		if (imei_len)
 			append_tlv(req_tlv, TLVID_osmo_imei,
-				   (uint8_t *)subscr->equipment.imei, imei_len+1);
+				   (uint8_t *)vsub->imei, imei_len+1);
 	}
 }
 
@@ -496,7 +497,7 @@
 {
 	osmo_timer_del(&cmd->response_timer);
 	llist_del(&cmd->list);
-	subscr_put(cmd->subscr);
+	vlr_subscr_put(cmd->vsub);
 	sms_free(cmd->sms);
 	talloc_free(cmd);
 }
@@ -514,7 +515,7 @@
 	struct gsm_subscriber_connection *conn;
 	struct gsm_trans *trans;
 
-	conn = connection_for_subscr(cmd->subscr);
+	conn = connection_for_subscr(cmd->vsub);
 	if (!conn) {
 		LOGP(DSMPP, LOGL_ERROR, "No connection to subscriber anymore\n");
 		return;
@@ -538,7 +539,7 @@
 	struct gsm_trans *trans;
 	int gsm411_cause;
 
-	conn = connection_for_subscr(cmd->subscr);
+	conn = connection_for_subscr(cmd->vsub);
 	if (!conn) {
 		LOGP(DSMPP, LOGL_ERROR, "No connection to subscriber anymore\n");
 		return;
@@ -566,7 +567,7 @@
 }
 
 static int smpp_cmd_enqueue(struct osmo_esme *esme,
-			    struct gsm_subscriber *subscr, struct gsm_sms *sms,
+			    struct vlr_subscr *vsub, struct gsm_sms *sms,
 			    uint32_t sequence_number, bool *deferred)
 {
 	struct osmo_smpp_cmd *cmd;
@@ -577,7 +578,7 @@
 
 	cmd->sequence_nr	= sequence_number;
 	cmd->sms		= sms;
-	cmd->subscr		= subscr_get(subscr);
+	cmd->vsub		= vlr_subscr_get(vsub);
 
 	/* FIXME: No predefined value for this response_timer as specified by
 	 * SMPP 3.4 specs, section 7.2. Make this configurable? Don't forget
@@ -623,13 +624,13 @@
 		deliver.source_addr_npi = NPI_Land_Mobile_E212;
 		snprintf((char *)deliver.source_addr,
 			sizeof(deliver.source_addr), "%s",
-			conn->subscr->imsi);
+			conn->vsub->imsi);
 	} else {
 		deliver.source_addr_ton = TON_Network_Specific;
 		deliver.source_addr_npi = NPI_ISDN_E163_E164;
 		snprintf((char *)deliver.source_addr,
 			 sizeof(deliver.source_addr), "%s",
-			 conn->subscr->extension);
+			 conn->vsub->msisdn);
 	}
 
 	deliver.dest_addr_ton	= sms->dst.ton;
@@ -686,7 +687,7 @@
 	if (ret < 0)
 		return ret;
 
-	return smpp_cmd_enqueue(esme, conn->subscr, sms,
+	return smpp_cmd_enqueue(esme, conn->vsub, sms,
 				deliver.sequence_number, deferred);
 }
 
diff --git a/src/libmsc/smpp_smsc.h b/src/libmsc/smpp_smsc.h
index d8e82e4..4bee59b 100644
--- a/src/libmsc/smpp_smsc.h
+++ b/src/libmsc/smpp_smsc.h
@@ -88,7 +88,7 @@
 
 struct osmo_smpp_cmd {
 	struct llist_head	list;
-	struct gsm_subscriber	*subscr;
+	struct vlr_subscr	*vsub;
 	struct gsm_sms		*sms;
 	uint32_t		sequence_nr;
 	struct osmo_timer_list	response_timer;
diff --git a/src/libmsc/sms_queue.c b/src/libmsc/sms_queue.c
index dc7f6e8..fe7a608 100644
--- a/src/libmsc/sms_queue.c
+++ b/src/libmsc/sms_queue.c
@@ -28,6 +28,8 @@
  * things up by collecting data from other parts of the system.
  */
 
+#include <limits.h>
+
 #include <openbsc/sms_queue.h>
 #include <openbsc/chan_alloc.h>
 #include <openbsc/db.h>
@@ -36,6 +38,7 @@
 #include <openbsc/gsm_04_11.h>
 #include <openbsc/gsm_subscriber.h>
 #include <openbsc/signal.h>
+#include <openbsc/vlr.h>
 
 #include <osmocom/core/talloc.h>
 
@@ -47,7 +50,7 @@
 struct gsm_sms_pending {
 	struct llist_head entry;
 
-	struct gsm_subscriber *subscr;
+	struct vlr_subscr *vsub;
 	unsigned long long sms_id;
 	int failed_attempts;
 	int resend;
@@ -62,7 +65,8 @@
 	int pending;
 
 	struct llist_head pending_sms;
-	unsigned long long last_subscr_id;
+
+	char last_msisdn[GSM_EXTENSION_LENGTH+1];
 };
 
 static int sms_subscr_cb(unsigned int, unsigned int, void *, void *);
@@ -88,12 +92,12 @@
 
 static struct gsm_sms_pending *sms_subscriber_find_pending(
 					struct gsm_sms_queue *smsq,
-					struct gsm_subscriber *subscr)
+					struct vlr_subscr *vsub)
 {
 	struct gsm_sms_pending *pending;
 
 	llist_for_each_entry(pending, &smsq->pending_sms, entry) {
-		if (pending->subscr == subscr)
+		if (pending->vsub == vsub)
 			return pending;
 	}
 
@@ -101,9 +105,9 @@
 }
 
 static int sms_subscriber_is_pending(struct gsm_sms_queue *smsq,
-				     struct gsm_subscriber *subscr)
+				     struct vlr_subscr *vsub)
 {
-	return sms_subscriber_find_pending(smsq, subscr) != NULL;
+	return sms_subscriber_find_pending(smsq, vsub) != NULL;
 }
 
 static struct gsm_sms_pending *sms_pending_from(struct gsm_sms_queue *smsq,
@@ -115,27 +119,28 @@
 	if (!pending)
 		return NULL;
 
-	pending->subscr = subscr_get(sms->receiver);
+	pending->vsub = vlr_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);
+	vlr_subscr_put(pending->vsub);
 	llist_del(&pending->entry);
 	talloc_free(pending);
 }
 
 static void sms_pending_resend(struct gsm_sms_pending *pending)
 {
+	struct gsm_network *net = pending->vsub->vlr->user_ctx;
 	struct gsm_sms_queue *smsq;
 	LOGP(DLSMS, LOGL_DEBUG,
 	     "Scheduling resend of SMS %llu.\n", pending->sms_id);
 
 	pending->resend = 1;
 
-	smsq = pending->subscr->group->net->sms_queue;
+	smsq = net->sms_queue;
 	if (osmo_timer_pending(&smsq->resend_pending))
 		return;
 
@@ -144,12 +149,13 @@
 
 static void sms_pending_failed(struct gsm_sms_pending *pending, int paging_error)
 {
+	struct gsm_network *net = pending->vsub->vlr->user_ctx;
 	struct gsm_sms_queue *smsq;
 
 	LOGP(DLSMS, LOGL_NOTICE, "Sending SMS %llu failed %d times.\n",
 	     pending->sms_id, pending->failed_attempts);
 
-	smsq = pending->subscr->group->net->sms_queue;
+	smsq = net->sms_queue;
 	if (++pending->failed_attempts < smsq->max_fail)
 		return sms_pending_resend(pending);
 
@@ -186,23 +192,49 @@
 	}
 }
 
-static struct gsm_sms *take_next_sms(struct gsm_sms_queue *smsq)
+/* Find the next pending SMS by cycling through the recipients. We could also
+ * cycle through the pending SMS, but that might cause us to keep trying to
+ * send SMS to the same few subscribers repeatedly while not servicing other
+ * subscribers for a long time. By walking the list of recipient MSISDNs, we
+ * ensure that all subscribers get their fair time to receive SMS. */
+struct gsm_sms *smsq_take_next_sms(struct gsm_network *net,
+				   char *last_msisdn,
+				   size_t last_msisdn_buflen)
 {
 	struct gsm_sms *sms;
+	int wrapped = 0;
+	int sanity = 100;
+	char started_with_msisdn[last_msisdn_buflen];
 
-	sms = db_sms_get_unsent_by_subscr(smsq->network, smsq->last_subscr_id, 10);
-	if (sms) {
-		smsq->last_subscr_id = sms->receiver->id + 1;
+	osmo_strlcpy(started_with_msisdn, last_msisdn,
+		     sizeof(started_with_msisdn));
+
+	while (wrapped < 2 && (--sanity)) {
+		/* If we wrapped around and passed the first msisdn, we're
+		 * through the entire SMS DB; end it. */
+		if (wrapped && strcmp(last_msisdn, started_with_msisdn) >= 0)
+			break;
+
+		sms = db_sms_get_next_unsent_rr_msisdn(net, last_msisdn, 9);
+		if (!sms) {
+			last_msisdn[0] = '\0';
+			wrapped ++;
+			continue;
+		}
+
+		/* Whatever happens, next time around service another recipient
+		 */
+		osmo_strlcpy(last_msisdn, sms->dst.addr, last_msisdn_buflen);
+
+		/* Is the subscriber attached? If not, go to next SMS */
+		if (!sms->receiver || !sms->receiver->lu_complete)
+			continue;
+
 		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;
+	DEBUGP(DLSMS, "SMS queue: no SMS to be sent\n");
+	return NULL;
 }
 
 /**
@@ -224,7 +256,8 @@
 		struct gsm_sms *sms;
 
 
-		sms = take_next_sms(smsq);
+		sms = smsq_take_next_sms(smsq->network, smsq->last_msisdn,
+					 sizeof(smsq->last_msisdn));
 		if (!sms) {
 			LOGP(DLSMS, LOGL_DEBUG, "Sending SMS done (%d attempted)\n",
 			     attempted);
@@ -289,21 +322,22 @@
 /**
  * Send the next SMS or trigger the queue
  */
-static void sms_send_next(struct gsm_subscriber *subscr)
+static void sms_send_next(struct vlr_subscr *vsub)
 {
-	struct gsm_sms_queue *smsq = subscr->group->net->sms_queue;
+	struct gsm_network *net = vsub->vlr->user_ctx;
+	struct gsm_sms_queue *smsq = net->sms_queue;
 	struct gsm_sms_pending *pending;
 	struct gsm_sms *sms;
 
 	/* the subscriber should not be in the queue */
-	OSMO_ASSERT(!sms_subscriber_is_pending(smsq, subscr));
+	OSMO_ASSERT(!sms_subscriber_is_pending(smsq, vsub));
 
 	/* check for more messages for this subscriber */
-	sms = db_sms_get_unsent_for_subscr(subscr);
+	sms = db_sms_get_unsent_for_subscr(vsub, UINT_MAX);
 	if (!sms)
 		goto no_pending_sms;
 
-	/* No sms should be scheduled right now */
+	/* The sms should not be scheduled right now */
 	OSMO_ASSERT(!sms_is_in_pending(smsq, sms));
 
 	/* Remember that we deliver this SMS and send it */
@@ -322,7 +356,7 @@
 
 no_pending_sms:
 	/* Try to send the SMS to avoid the queue being stuck */
-	sms_submit_pending(subscr->group->net->sms_queue);
+	sms_submit_pending(net->sms_queue);
 }
 
 /*
@@ -362,7 +396,7 @@
 	return 0;
 }
 
-static int sub_ready_for_sm(struct gsm_network *net, struct gsm_subscriber *subscr)
+static int sub_ready_for_sm(struct gsm_network *net, struct vlr_subscr *vsub)
 {
 	struct gsm_sms *sms;
 	struct gsm_sms_pending *pending;
@@ -383,20 +417,20 @@
 	 */
 
 	/* check if we have pending requests */
-	pending = sms_subscriber_find_pending(net->sms_queue, subscr);
+	pending = sms_subscriber_find_pending(net->sms_queue, vsub);
 	if (pending) {
 		LOGP(DMSC, LOGL_NOTICE,
 		     "Pending paging while subscriber %llu attached.\n",
-		      subscr->id);
+		      vsub->id);
 		return 0;
 	}
 
-	conn = connection_for_subscr(subscr);
+	conn = connection_for_subscr(vsub);
 	if (!conn)
 		return -1;
 
 	/* Now try to deliver any pending SMS to this sub */
-	sms = db_sms_get_unsent_for_subscr(subscr);
+	sms = db_sms_get_unsent_for_subscr(vsub, UINT_MAX);
 	if (!sms)
 		return -1;
 	gsm411_send_sms(conn, sms);
@@ -406,13 +440,13 @@
 static int sms_subscr_cb(unsigned int subsys, unsigned int signal,
 			 void *handler_data, void *signal_data)
 {
-	struct gsm_subscriber *subscr = signal_data;
+	struct vlr_subscr *vsub = signal_data;
 
 	if (signal != S_SUBSCR_ATTACHED)
 		return 0;
 
 	/* this is readyForSM */
-	return sub_ready_for_sm(handler_data, subscr);
+	return sub_ready_for_sm(handler_data, vsub);
 }
 
 static int sms_sms_cb(unsigned int subsys, unsigned int signal,
@@ -421,7 +455,7 @@
 	struct gsm_network *network = handler_data;
 	struct sms_signal_data *sig_sms = signal_data;
 	struct gsm_sms_pending *pending;
-	struct gsm_subscriber *subscr;
+	struct vlr_subscr *vsub;
 
 	/* We got a new SMS and maybe should launch the queue again. */
 	if (signal == S_SMS_SUBMITTED || signal == S_SMS_SMMA) {
@@ -447,11 +481,11 @@
 	case S_SMS_DELIVERED:
 		/* Remember the subscriber and clear the pending entry */
 		network->sms_queue->pending -= 1;
-		subscr = subscr_get(pending->subscr);
+		vsub = vlr_subscr_get(pending->vsub);
 		sms_pending_free(pending);
 		/* Attempt to send another SMS to this subscriber */
-		sms_send_next(subscr);
-		subscr_put(subscr);
+		sms_send_next(vsub);
+		vlr_subscr_put(vsub);
 		break;
 	case S_SMS_MEM_EXCEEDED:
 		network->sms_queue->pending -= 1;
@@ -508,7 +542,7 @@
 
 	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->vsub->id, pending->sms_id,
 			pending->failed_attempts, VTY_NEWLINE);
 	return 0;
 }
@@ -535,7 +569,7 @@
 
 	llist_for_each_entry_safe(pending, tmp, &smsq->pending_sms, entry) {
 		LOGP(DLSMS, LOGL_NOTICE,
-		     "SMSqueue clearing for sub %llu\n", pending->subscr->id);
+		     "SMSqueue clearing for sub %llu\n", pending->vsub->id);
 		sms_pending_free(pending);
 	}
 
diff --git a/src/libmsc/subscr_conn.c b/src/libmsc/subscr_conn.c
index 91ffe40..b28a511 100644
--- a/src/libmsc/subscr_conn.c
+++ b/src/libmsc/subscr_conn.c
@@ -23,19 +23,24 @@
 
 #include <osmocom/core/logging.h>
 #include <osmocom/core/fsm.h>
+#include <osmocom/core/signal.h>
 
 #include <openbsc/osmo_msc.h>
 #include <openbsc/vlr.h>
 #include <openbsc/debug.h>
 #include <openbsc/transaction.h>
+#include <openbsc/signal.h>
+
+#define SUBSCR_CONN_TIMEOUT 5 /* seconds */
 
 static const struct value_string subscr_conn_fsm_event_names[] = {
 	OSMO_VALUE_STRING(SUBSCR_CONN_E_INVALID),
+	OSMO_VALUE_STRING(SUBSCR_CONN_E_START),
 	OSMO_VALUE_STRING(SUBSCR_CONN_E_ACCEPTED),
+	OSMO_VALUE_STRING(SUBSCR_CONN_E_COMMUNICATING),
 	OSMO_VALUE_STRING(SUBSCR_CONN_E_BUMP),
 	OSMO_VALUE_STRING(SUBSCR_CONN_E_MO_CLOSE),
 	OSMO_VALUE_STRING(SUBSCR_CONN_E_CN_CLOSE),
-	OSMO_VALUE_STRING(SUBSCR_CONN_E_CLOSE_CONF),
 	{ 0, NULL }
 };
 
@@ -50,14 +55,21 @@
 static void paging_resp(struct gsm_subscriber_connection *conn,
 			       enum gsm_paging_event pe)
 {
-	subscr_paging_dispatch(GSM_HOOK_RR_PAGING, pe, NULL, conn, conn->subscr);
+	subscr_paging_dispatch(GSM_HOOK_RR_PAGING, pe, NULL, conn, conn->vsub);
+}
+
+void subscr_conn_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	OSMO_ASSERT(event == SUBSCR_CONN_E_START);
+	osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_NEW,
+				SUBSCR_CONN_TIMEOUT, 0);
 }
 
 void subscr_conn_fsm_new(struct osmo_fsm_inst *fi, uint32_t event, void *data)
 {
 	struct gsm_subscriber_connection *conn = fi->priv;
 	enum subscr_conn_from from = SUBSCR_CONN_FROM_INVALID;
-	enum gsm_paging_event pe;
+	bool success;
 
 	if (data) {
 		from = *(enum subscr_conn_from*)data;
@@ -67,12 +79,12 @@
 	/* If accepted, transition the state, all other cases mean failure. */
 	switch (event) {
 	case SUBSCR_CONN_E_ACCEPTED:
-		osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_ACCEPTED, 0, 0);
+		osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_ACCEPTED,
+					SUBSCR_CONN_TIMEOUT, 0);
 		break;
 
 	case SUBSCR_CONN_E_MO_CLOSE:
 	case SUBSCR_CONN_E_CN_CLOSE:
-	case SUBSCR_CONN_E_CLOSE_CONF:
 		break;
 
 	default:
@@ -81,23 +93,27 @@
 		break;
 	}
 
-	/* if appropriate, signal paging success or failure */
-	if (from == SUBSCR_CONN_FROM_PAGING_RESP) {
-		pe = (fi->state == SUBSCR_CONN_S_ACCEPTED)?
-			GSM_PAGING_SUCCEEDED : GSM_PAGING_EXPIRED;
-		paging_resp(conn, pe);
-	}
+	success = (fi->state == SUBSCR_CONN_S_ACCEPTED);
+
+	if (from == SUBSCR_CONN_FROM_LU)
+		rate_ctr_inc(&conn->network->msc_ctrs->ctr[
+		             	success ? MSC_CTR_LOC_UPDATE_COMPLETED
+					: MSC_CTR_LOC_UPDATE_FAILED]);
+
+	/* signal paging success or failure in case this was a paging */
+	if (from == SUBSCR_CONN_FROM_PAGING_RESP)
+		paging_resp(conn,
+			    success ? GSM_PAGING_SUCCEEDED
+			    	    : GSM_PAGING_EXPIRED);
 
 	/* On failure, discard the conn */
-	if (fi->state != SUBSCR_CONN_S_ACCEPTED) {
+	if (!success) {
 		/* TODO: on MO_CLOSE or CN_CLOSE, first go to RELEASING and
 		 * await BSC confirmation? */
 		osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASED, 0, 0);
 		return;
 	}
 
-	/* On success, handle pending requests and/or close conn */
-
 	if (from == SUBSCR_CONN_FROM_CM_SERVICE_REQ) {
 		conn->received_cm_service_request = true;
 		LOGPFSM(fi, "received_cm_service_request = true\n");
@@ -106,33 +122,6 @@
 	osmo_fsm_inst_dispatch(fi, SUBSCR_CONN_E_BUMP, data);
 }
 
-#if 0
-	case SUBSCR_CONN_E_PARQ_SUCCESS:
-		osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_ACCEPTED, 0, 0);
-		accept_conn = true;
-		/* fall through */
-	case SUBSCR_CONN_E_PARQ_FAILURE:
-		parq_type = data ? *(enum vlr_parq_type*)data : VLR_PR_ARQ_T_INVALID;
-		switch (parq_type) {
-
-		case VLR_PR_ARQ_T_CM_SERV_REQ:
-			accept_conn = handle_cm_serv_result(fi, accept_conn);
-			break;
-
-		case VLR_PR_ARQ_T_PAGING_RESP:
-			accept_conn = handle_paging_result(fi, accept_conn);
-			break;
-
-		default:
-			LOGPFSML(fi, LOGL_ERROR,
-				 "Invalid VLR Process Access Request type"
-				 " %d\n", parq_type);
-			accept_conn = false;
-			break;
-		}
-		break;
-#endif
-
 static void subscr_conn_fsm_bump(struct osmo_fsm_inst *fi, uint32_t event, void *data)
 {
 	struct gsm_subscriber_connection *conn = fi->priv;
@@ -143,8 +132,7 @@
 	if (conn->received_cm_service_request)
 		return;
 
-	/* is this needed? */
-	if (conn->subscr && !llist_empty(&conn->subscr->requests))
+	if (conn->vsub && !llist_empty(&conn->vsub->cs.requests))
 		return;
 
 	if (trans_has_conn(conn))
@@ -153,9 +141,19 @@
 	osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASED, 0, 0);
 }
 
+static void subscr_conn_fsm_accepted_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	struct gsm_subscriber_connection *conn = fi->priv;
+	osmo_signal_dispatch(SS_SUBSCR, S_SUBSCR_ATTACHED, conn->vsub);
+}
+
 static void subscr_conn_fsm_accepted(struct osmo_fsm_inst *fi, uint32_t event, void *data)
 {
 	switch (event) {
+	case SUBSCR_CONN_E_COMMUNICATING:
+		osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_COMMUNICATING, 0, 0);
+		return;
+
 	case SUBSCR_CONN_E_BUMP:
 		subscr_conn_fsm_bump(fi, event, data);
 		return;
@@ -169,34 +167,68 @@
 	osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASED, 0, 0);
 }
 
-static void subscr_conn_fsm_release(struct osmo_fsm_inst *fi, uint32_t prev_state)
+static void subscr_conn_fsm_communicating(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	switch (event) {
+	case SUBSCR_CONN_E_COMMUNICATING:
+		/* no-op */
+		return;
+
+	case SUBSCR_CONN_E_BUMP:
+		subscr_conn_fsm_bump(fi, event, data);
+		return;
+
+	default:
+		break;
+	}
+	/* Whatever unexpected happens in the accepted state, it means release.
+	 * Even if an unexpected event is passed, the safest thing to do is
+	 * discard the conn. We don't expect another SUBSCR_CONN_E_ACCEPTED. */
+	osmo_fsm_inst_state_chg(fi, SUBSCR_CONN_S_RELEASED, 0, 0);
+}
+
+static void subscr_conn_fsm_cleanup(struct osmo_fsm_inst *fi,
+				    enum osmo_fsm_term_cause cause)
 {
 	struct gsm_subscriber_connection *conn = fi->priv;
+	fi->priv = NULL;
+
 	if (!conn)
 		return;
 
-	/* temporary hack, see owned_by_msc */
-	if (!conn->owned_by_msc) {
-		DEBUGP(DMM, "%s leaving bsc_subscr_con_free() to bsc_api.c, owned_by_msc = false\n",
-		       subscr_name(conn->subscr));
-		return;
-	}
+	conn->conn_fsm = NULL;
+ 	msc_subscr_conn_close(conn, cause);
+	msc_subscr_conn_put(conn);
+}
 
-	DEBUGP(DMM, "%s calling bsc_subscr_con_free(), owned_by_msc = true\n",
-	       subscr_name(conn->subscr));
-	gsm0808_clear(conn);
-	bsc_subscr_con_free(conn);
+int subscr_conn_fsm_timeout(struct osmo_fsm_inst *fi)
+{
+	struct gsm_subscriber_connection *conn = fi->priv;
+	if (conn)
+		vlr_subscr_conn_timeout(conn->vsub);
+	osmo_fsm_inst_dispatch(fi, SUBSCR_CONN_E_CN_CLOSE, NULL);
+	return 0;
+}
+
+static void subscr_conn_fsm_release(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
 }
 
 #define S(x)	(1 << (x))
 
 static const struct osmo_fsm_state subscr_conn_fsm_states[] = {
+	[SUBSCR_CONN_S_INIT] = {
+		.name = OSMO_STRINGIFY(SUBSCR_CONN_S_INIT),
+		.in_event_mask = S(SUBSCR_CONN_E_START),
+		.out_state_mask = S(SUBSCR_CONN_S_NEW),
+		.action = subscr_conn_fsm_init,
+	},
 	[SUBSCR_CONN_S_NEW] = {
 		.name = OSMO_STRINGIFY(SUBSCR_CONN_S_NEW),
 		.in_event_mask = S(SUBSCR_CONN_E_ACCEPTED) |
 				 S(SUBSCR_CONN_E_MO_CLOSE) |
-				 S(SUBSCR_CONN_E_CN_CLOSE) |
-				 S(SUBSCR_CONN_E_CLOSE_CONF),
+				 S(SUBSCR_CONN_E_CN_CLOSE),
 		.out_state_mask = S(SUBSCR_CONN_S_ACCEPTED) |
 				  S(SUBSCR_CONN_S_RELEASED),
 		.action = subscr_conn_fsm_new,
@@ -204,14 +236,27 @@
 	[SUBSCR_CONN_S_ACCEPTED] = {
 		.name = OSMO_STRINGIFY(SUBSCR_CONN_S_ACCEPTED),
 		/* allow everything to release for any odd behavior */
-		.in_event_mask = S(SUBSCR_CONN_E_ACCEPTED) |
-				 S(SUBSCR_CONN_E_BUMP) |
+		.in_event_mask = S(SUBSCR_CONN_E_COMMUNICATING) |
+		                 S(SUBSCR_CONN_E_BUMP) |
+				 S(SUBSCR_CONN_E_ACCEPTED) |
 				 S(SUBSCR_CONN_E_MO_CLOSE) |
-				 S(SUBSCR_CONN_E_CN_CLOSE) |
-				 S(SUBSCR_CONN_E_CLOSE_CONF),
-		.out_state_mask = S(SUBSCR_CONN_S_RELEASED),
+				 S(SUBSCR_CONN_E_CN_CLOSE),
+		.out_state_mask = S(SUBSCR_CONN_S_RELEASED) |
+				  S(SUBSCR_CONN_S_COMMUNICATING),
+		.onenter = subscr_conn_fsm_accepted_enter,
 		.action = subscr_conn_fsm_accepted,
 	},
+	[SUBSCR_CONN_S_COMMUNICATING] = {
+		.name = OSMO_STRINGIFY(SUBSCR_CONN_S_COMMUNICATING),
+		/* allow everything to release for any odd behavior */
+		.in_event_mask = S(SUBSCR_CONN_E_BUMP) |
+				 S(SUBSCR_CONN_E_ACCEPTED) |
+				 S(SUBSCR_CONN_E_COMMUNICATING) |
+				 S(SUBSCR_CONN_E_MO_CLOSE) |
+				 S(SUBSCR_CONN_E_CN_CLOSE),
+		.out_state_mask = S(SUBSCR_CONN_S_RELEASED),
+		.action = subscr_conn_fsm_communicating,
+	},
 	[SUBSCR_CONN_S_RELEASED] = {
 		.name = OSMO_STRINGIFY(SUBSCR_CONN_S_RELEASED),
 		.onenter = subscr_conn_fsm_release,
@@ -226,6 +271,8 @@
 	.allstate_action = NULL,
 	.log_subsys = DVLR,
 	.event_names = subscr_conn_fsm_event_names,
+	.cleanup = subscr_conn_fsm_cleanup,
+	.timer_cb = subscr_conn_fsm_timeout,
 };
 
 int msc_create_conn_fsm(struct gsm_subscriber_connection *conn, const char *id)
@@ -239,7 +286,14 @@
 		return -EINVAL;
 	}
 
-	fi = osmo_fsm_inst_alloc(&subscr_conn_fsm, conn, conn, LOGL_DEBUG, id);
+	/* Allocate the FSM not with the subscr_conn. Semantically it would
+	 * make sense, but in subscr_conn_fsm_cleanup(), we want to discard the
+	 * subscriber connection. If the FSM is freed along with the subscriber
+	 * connection, then in _osmo_fsm_inst_term() the osmo_fsm_inst_free()
+	 * that follows the cleanup() call would run into a double free. */
+	fi = osmo_fsm_inst_alloc(&subscr_conn_fsm, conn->network,
+				 msc_subscr_conn_get(conn),
+				 LOGL_DEBUG, id);
 
 	if (!fi) {
 		LOGP(DMM, LOGL_ERROR,
@@ -247,6 +301,7 @@
 		return -ENOMEM;
 	}
 	conn->conn_fsm = fi;
+	osmo_fsm_inst_dispatch(conn->conn_fsm, SUBSCR_CONN_E_START, NULL);
 	return 0;
 }
 
@@ -254,15 +309,23 @@
 {
 	if (!conn)
 		return false;
-	if (!conn->subscr)
+	if (!conn->vsub)
 		return false;
 	if (!conn->conn_fsm)
 		return false;
-	if (conn->conn_fsm->state != SUBSCR_CONN_S_ACCEPTED)
+	if (!(conn->conn_fsm->state == SUBSCR_CONN_S_ACCEPTED
+	      || conn->conn_fsm->state == SUBSCR_CONN_S_COMMUNICATING))
 		return false;
 	return true;
 }
 
+void msc_subscr_conn_communicating(struct gsm_subscriber_connection *conn)
+{
+	OSMO_ASSERT(conn);
+	osmo_fsm_inst_dispatch(conn->conn_fsm, SUBSCR_CONN_E_COMMUNICATING,
+			       NULL);
+}
+
 void msc_subscr_conn_init(void)
 {
 	osmo_fsm_register(&subscr_conn_fsm);
diff --git a/src/libmsc/token_auth.c b/src/libmsc/token_auth.c
deleted file mode 100644
index 5af1e98..0000000
--- a/src/libmsc/token_auth.c
+++ /dev/null
@@ -1,160 +0,0 @@
-/* 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 <osmocom/core/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, uint32_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->group->net->auth_policy != GSM_AUTH_POLICY_TOKEN)
-		return 0;
-
-	if (subscr->flags & GSM_SUBSCRIBER_FIRST_CONTACT) {
-		struct gsm_subscriber *sender;
-		uint32_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;
-		}
-
-
-		/* FIXME: don't use ID 1 static */
-		sender = subscr_get_by_id(subscr->group, 1);
-
-		sms = sms_from_text(subscr, sender, 0, sms_str);
-
-		subscr_put(sender);
-		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) {
-				uint8_t auth_rand[16];
-				/* kick the subscriber off the network */
-				gsm48_tx_mm_auth_req(conn, auth_rand, NULL, 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;
-	uint8_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->group->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, NULL, 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)
-{
-	osmo_signal_register_handler(SS_SUBSCR, token_subscr_cb, NULL);
-	osmo_signal_register_handler(SS_SMS, token_sms_cb, NULL);
-}
diff --git a/src/libmsc/transaction.c b/src/libmsc/transaction.c
index dba4bed..d157f54 100644
--- a/src/libmsc/transaction.c
+++ b/src/libmsc/transaction.c
@@ -23,25 +23,31 @@
 #include <openbsc/mncc.h>
 #include <openbsc/debug.h>
 #include <osmocom/core/talloc.h>
-#include <openbsc/gsm_subscriber.h>
 #include <openbsc/gsm_04_08.h>
 #include <openbsc/mncc.h>
 #include <openbsc/paging.h>
 #include <openbsc/osmo_msc.h>
+#include <openbsc/vlr.h>
 
 void *tall_trans_ctx;
 
 void _gsm48_cc_trans_free(struct gsm_trans *trans);
 
+/*! Find a transaction in connection for given protocol + transaction ID
+ * \param[in] conn Connection in whihc we want to find transaction
+ * \param[in] proto Protocol of transaction
+ * \param[in] trans_id Transaction ID of transaction
+ * \returns Matching transaction, if any
+ */
 struct gsm_trans *trans_find_by_id(struct gsm_subscriber_connection *conn,
 				   uint8_t proto, uint8_t trans_id)
 {
 	struct gsm_trans *trans;
 	struct gsm_network *net = conn->network;
-	struct gsm_subscriber *subscr = conn->subscr;
+	struct vlr_subscr *vsub = conn->vsub;
 
 	llist_for_each_entry(trans, &net->trans_list, entry) {
-		if (trans->subscr == subscr &&
+		if (trans->vsub == vsub &&
 		    trans->protocol == proto &&
 		    trans->transaction_id == trans_id)
 			return trans;
@@ -49,6 +55,11 @@
 	return NULL;
 }
 
+/*! Find a transaction by call reference
+ * \param[in] net Network in which we should search
+ * \param[in] callref Call Reference of transaction
+ * \returns Matching transaction, if any
+ */
 struct gsm_trans *trans_find_by_callref(struct gsm_network *net,
 					uint32_t callref)
 {
@@ -61,21 +72,27 @@
 	return NULL;
 }
 
+/*! Allocate a new transaction and add it to network list
+ *  \param[in] net Netwokr in which we allocate transaction
+ *  \param[in] subscr Subscriber for which we allocate transaction
+ *  \param[in] protocol Protocol (CC/SMS/...)
+ *  \param[in] callref Call Reference
+ *  \returns Transaction
+ */
 struct gsm_trans *trans_alloc(struct gsm_network *net,
-			      struct gsm_subscriber *subscr,
+			      struct vlr_subscr *vsub,
 			      uint8_t protocol, uint8_t trans_id,
 			      uint32_t callref)
 {
 	struct gsm_trans *trans;
 
-	DEBUGP(DCC, "subscr=%p, net=%p\n", subscr, net);
+	DEBUGP(DCC, "subscr=%p, net=%p\n", vsub, net);
 
 	trans = talloc_zero(tall_trans_ctx, struct gsm_trans);
 	if (!trans)
 		return NULL;
 
-	trans->subscr = subscr;
-	subscr_get(trans->subscr);
+	trans->vsub = vlr_subscr_get(vsub);
 
 	trans->protocol = protocol;
 	trans->transaction_id = trans_id;
@@ -87,6 +104,9 @@
 	return trans;
 }
 
+/*! Release a transaction
+ * \param[in] trans Transaction to be released
+ */
 void trans_free(struct gsm_trans *trans)
 {
 	switch (trans->protocol) {
@@ -103,23 +123,28 @@
 		trans->paging_request = NULL;
 	}
 
-	if (trans->subscr) {
-		subscr_put(trans->subscr);
-		trans->subscr = NULL;
+	if (trans->vsub) {
+		vlr_subscr_put(trans->vsub);
+		trans->vsub = NULL;
 	}
 
 	llist_del(&trans->entry);
 
 	if (trans->conn)
-		msc_release_connection(trans->conn);
+		msc_subscr_conn_put(trans->conn);
 
 	trans->conn = NULL;
 	talloc_free(trans);
 }
 
-/* allocate an unused transaction ID for the given subscriber
- * in the given protocol using the ti_flag specified */
-int trans_assign_trans_id(struct gsm_network *net, struct gsm_subscriber *subscr,
+/*! allocate an unused transaction ID for the given subscriber
+ * in the given protocol using the ti_flag specified
+ * \param[in] net GSM network
+ * \param[in] subscr Subscriber for which to find ID
+ * \param[in] protocol Protocol for whihc to find ID
+ * \param[in] ti_flag FIXME
+ */
+int trans_assign_trans_id(struct gsm_network *net, struct vlr_subscr *vsub,
 			  uint8_t protocol, uint8_t ti_flag)
 {
 	struct gsm_trans *trans;
@@ -131,7 +156,7 @@
 
 	/* generate bitmask of already-used TIDs for this (subscr,proto) */
 	llist_for_each_entry(trans, &net->trans_list, entry) {
-		if (trans->subscr != subscr ||
+		if (trans->vsub != vsub ||
 		    trans->protocol != protocol ||
 		    trans->transaction_id == 0xff)
 			continue;
@@ -151,6 +176,10 @@
 	return -1;
 }
 
+/*! Check if we have any transaction for given connection
+ * \param[in] conn Connection to check
+ * \returns 1 in case there is a transaction, 0 otherwise
+ */
 int trans_has_conn(const struct gsm_subscriber_connection *conn)
 {
 	struct gsm_trans *trans;
@@ -161,3 +190,25 @@
 
 	return 0;
 }
+
+/*! Free all transactions associated with a connection, presumably when the
+ * conn is being closed. The transaction code will inform the CC or SMS
+ * facilities, which will then send the necessary release indications.
+ * \param[in] conn Connection that is going to be closed.
+ */
+void trans_conn_closed(struct gsm_subscriber_connection *conn)
+{
+	struct gsm_trans *trans;
+
+	/* As part of the CC REL_IND the remote leg might be released and this
+	 * will trigger another call to trans_free. This is something the llist
+	 * macro can not handle and we need to re-iterate the list every time.
+	 */
+restart:
+	llist_for_each_entry(trans, &conn->network->trans_list, entry) {
+		if (trans->conn == conn) {
+			trans_free(trans);
+			goto restart;
+		}
+	}
+}
diff --git a/src/libmsc/ussd.c b/src/libmsc/ussd.c
index f12c1f2..81a3566 100644
--- a/src/libmsc/ussd.c
+++ b/src/libmsc/ussd.c
@@ -33,6 +33,7 @@
 #include <openbsc/gsm_subscriber.h>
 #include <openbsc/debug.h>
 #include <openbsc/osmo_msc.h>
+#include <openbsc/vlr.h>
 
 /* Declarations of USSD strings to be recognised */
 const char USSD_TEXT_OWN_NUMBER[] = "*#100#";
@@ -48,13 +49,19 @@
 	struct ss_request req;
 	struct gsm48_hdr *gh;
 
+	/* TODO: Use subscriber_connection ref-counting if we ever want
+	 * to keep the connection alive due ot ongoing USSD exchange.
+	 * As we answer everytying synchronously so far, there's no need
+	 * yet */
+
+	cm_service_request_concludes(conn, msg);
+
 	memset(&req, 0, sizeof(req));
 	gh = msgb_l3(msg);
 	rc = gsm0480_decode_ss_request(gh, msgb_l3len(msg), &req);
 	if (!rc) {
 		DEBUGP(DMM, "Unhandled SS\n");
 		rc = gsm0480_send_ussd_reject(conn, msg, &req);
-		msc_release_connection(conn);
 		return rc;
 	}
 
@@ -63,13 +70,13 @@
 		if (req.ss_code > 0) {
 			/* Assume interrogateSS or modification of it and reject */
 			rc = gsm0480_send_ussd_reject(conn, msg, &req);
-			msc_release_connection(conn);
 			return rc;
 		}
 		/* Still assuming a Release-Complete and returning */
 		return 0;
 	}
 
+	msc_subscr_conn_communicating(conn);
 	if (!strcmp(USSD_TEXT_OWN_NUMBER, (const char *)req.ussd_text)) {
 		DEBUGP(DMM, "USSD: Own number requested\n");
 		rc = send_own_number(conn, msg, &req);
@@ -78,17 +85,18 @@
 		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 ss_request *req)
 {
-	char *own_number = conn->subscr->extension;
+	char *own_number = conn->vsub->msisdn;
 	char response_string[GSM_EXTENSION_LENGTH + 20];
 
+	DEBUGP(DMM, "%s: MSISDN = %s\n", vlr_subscr_name(conn->vsub),
+	       own_number);
+
 	/* 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/src/libmsc/vty_interface_layer3.c b/src/libmsc/vty_interface_layer3.c
index 99d7fb9..c393a8f 100644
--- a/src/libmsc/vty_interface_layer3.c
+++ b/src/libmsc/vty_interface_layer3.c
@@ -51,74 +51,78 @@
 #include <openbsc/sms_queue.h>
 #include <openbsc/mncc_int.h>
 #include <openbsc/handover.h>
+#include <openbsc/vlr.h>
 
 #include <osmocom/vty/logging.h>
 
+#include <openbsc/osmo_msc.h>
+
 #include "meas_feed.h"
 
 extern struct gsm_network *gsmnet_from_vty(struct vty *v);
 
-static void subscr_dump_full_vty(struct vty *vty, struct gsm_subscriber *subscr)
+static void subscr_dump_full_vty(struct vty *vty, struct vlr_subscr *vsub)
 {
-	int rc;
 	int reqs;
-	struct gsm_auth_info ainfo;
-	struct gsm_auth_tuple atuple;
 	struct llist_head *entry;
 	char expire_time[200];
 
-	vty_out(vty, "    ID: %llu, Authorized: %d%s", subscr->id,
-		subscr->authorized, VTY_NEWLINE);
-	if (strlen(subscr->name))
-		vty_out(vty, "    Name: '%s'%s", subscr->name, VTY_NEWLINE);
-	if (strlen(subscr->extension))
-		vty_out(vty, "    Extension: %s%s", subscr->extension,
+	if (strlen(vsub->name))
+		vty_out(vty, "    Name: '%s'%s", vsub->name, VTY_NEWLINE);
+	if (strlen(vsub->msisdn))
+		vty_out(vty, "    Extension: %s%s", vsub->msisdn,
 			VTY_NEWLINE);
 	vty_out(vty, "    LAC: %d/0x%x%s",
-		subscr->lac, subscr->lac, VTY_NEWLINE);
-	vty_out(vty, "    IMSI: %s%s", subscr->imsi, VTY_NEWLINE);
-	if (subscr->tmsi != GSM_RESERVED_TMSI)
-		vty_out(vty, "    TMSI: %08X%s", subscr->tmsi,
+		vsub->lac, vsub->lac, VTY_NEWLINE);
+	vty_out(vty, "    IMSI: %s%s", vsub->imsi, VTY_NEWLINE);
+	if (vsub->tmsi != GSM_RESERVED_TMSI)
+		vty_out(vty, "    TMSI: %08X%s", vsub->tmsi,
+			VTY_NEWLINE);
+	if (vsub->tmsi_new != GSM_RESERVED_TMSI)
+		vty_out(vty, "    new TMSI: %08X%s", vsub->tmsi_new,
 			VTY_NEWLINE);
 
-	rc = db_get_authinfo_for_subscr(&ainfo, subscr);
-	if (!rc) {
+#if 0
+	/* TODO: add this to vlr_subscr? */
+	if (vsub->auth_info.auth_algo != AUTH_ALGO_NONE) {
+		struct gsm_auth_info *i = &vsub->auth_info;
 		vty_out(vty, "    A3A8 algorithm id: %d%s",
-			ainfo.auth_algo, VTY_NEWLINE);
+			i->auth_algo, VTY_NEWLINE);
 		vty_out(vty, "    A3A8 Ki: %s%s",
-			osmo_hexdump(ainfo.a3a8_ki, ainfo.a3a8_ki_len),
+			osmo_hexdump(i->a3a8_ki, i->a3a8_ki_len),
 			VTY_NEWLINE);
 	}
+#endif
 
-	rc = db_get_lastauthtuple_for_subscr(&atuple, subscr);
-	if (!rc) {
+	if (vsub->last_tuple) {
+		struct gsm_auth_tuple *t = vsub->last_tuple;
 		vty_out(vty, "    A3A8 last tuple (used %d times):%s",
-			atuple.use_count, VTY_NEWLINE);
+			t->use_count, VTY_NEWLINE);
 		vty_out(vty, "     seq # : %d%s",
-			atuple.key_seq, VTY_NEWLINE);
+			t->key_seq, VTY_NEWLINE);
 		vty_out(vty, "     RAND  : %s%s",
-			osmo_hexdump(atuple.vec.rand, sizeof(atuple.vec.rand)),
+			osmo_hexdump(t->vec.rand, sizeof(t->vec.rand)),
 			VTY_NEWLINE);
 		vty_out(vty, "     SRES  : %s%s",
-			osmo_hexdump(atuple.vec.sres, sizeof(atuple.vec.sres)),
+			osmo_hexdump(t->vec.sres, sizeof(t->vec.sres)),
 			VTY_NEWLINE);
 		vty_out(vty, "     Kc    : %s%s",
-			osmo_hexdump(atuple.vec.kc, sizeof(atuple.vec.kc)),
+			osmo_hexdump(t->vec.kc, sizeof(t->vec.kc)),
 			VTY_NEWLINE);
 	}
 
 	/* print the expiration time of a subscriber */
 	strftime(expire_time, sizeof(expire_time),
-			"%a, %d %b %Y %T %z", localtime(&subscr->expire_lu));
+			"%a, %d %b %Y %T %z", localtime(&vsub->expire_lu));
 	expire_time[sizeof(expire_time) - 1] = '\0';
 	vty_out(vty, "    Expiration Time: %s%s", expire_time, VTY_NEWLINE);
 
 	reqs = 0;
-	llist_for_each(entry, &subscr->requests)
+	llist_for_each(entry, &vsub->cs.requests)
 		reqs += 1;
-	vty_out(vty, "    Paging: %s paging Requests: %d%s",
-		subscr->is_paging ? "is" : "not", reqs, VTY_NEWLINE);
-	vty_out(vty, "    Use count: %u%s", subscr->use_count, VTY_NEWLINE);
+	vty_out(vty, "    Paging: %s paging for %d requests%s",
+		vsub->cs.is_paging ? "is" : "not", reqs, VTY_NEWLINE);
+	vty_out(vty, "    Use count: %u%s", vsub->use_count, VTY_NEWLINE);
 }
 
 
@@ -129,11 +133,18 @@
 	SHOW_STR "Show information about subscribers\n"
 	"Display contents of subscriber cache\n")
 {
-	struct gsm_subscriber *subscr;
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	struct vlr_subscr *vsub;
+	int count = 0;
 
-	llist_for_each_entry(subscr, &active_subscribers, entry) {
+	llist_for_each_entry(vsub, &gsmnet->vlr->subscribers, list) {
+		if (++count > 100) {
+			vty_out(vty, "%% More than %d subscribers in cache,"
+				" stopping here.%s", count-1, VTY_NEWLINE);
+			break;
+		}
 		vty_out(vty, "  Subscriber:%s", VTY_NEWLINE);
-		subscr_dump_full_vty(vty, subscr);
+		subscr_dump_full_vty(vty, vsub);
 	}
 
 	return CMD_SUCCESS;
@@ -147,25 +158,27 @@
 {
 	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
 	struct gsm_sms *sms;
-	int id = 0;
+	unsigned long long sms_id = 0;
 
 	while (1) {
-		sms = db_sms_get_unsent_by_subscr(gsmnet, id, UINT_MAX);
+		sms = db_sms_get_next_unsent(gsmnet, sms_id, UINT_MAX);
 		if (!sms)
 			break;
 
-		gsm411_send_sms_subscr(sms->receiver, sms);
+		if (sms->receiver)
+			gsm411_send_sms_subscr(sms->receiver, sms);
 
-		id = sms->receiver->id + 1;
+		sms_id = sms->id + 1;
 	}
 
 	return CMD_SUCCESS;
 }
 
-static int _send_sms_str(struct gsm_subscriber *receiver,
-                         struct gsm_subscriber *sender,
-                         char *str, uint8_t tp_pid)
+static int _send_sms_str(struct vlr_subscr *receiver,
+			 struct vlr_subscr *sender,
+			 char *str, uint8_t tp_pid)
 {
+	struct gsm_network *net = receiver->vlr->user_ctx;
 	struct gsm_sms *sms;
 
 	sms = sms_from_text(receiver, sender, 0, str);
@@ -180,22 +193,20 @@
 	LOGP(DLSMS, LOGL_DEBUG, "SMS stored in DB\n");
 
 	sms_free(sms);
-	sms_queue_trigger(receiver->group->net->sms_queue);
+	sms_queue_trigger(net->sms_queue);
 	return CMD_SUCCESS;
 }
 
-static struct gsm_subscriber *get_subscr_by_argv(struct gsm_network *gsmnet,
-						 const char *type,
-						 const char *id)
+static struct vlr_subscr *get_vsub_by_argv(struct gsm_network *gsmnet,
+					       const char *type,
+					       const char *id)
 {
-	if (!strcmp(type, "extension"))
-		return subscr_get_by_extension(gsmnet->subscr_group, id);
-	else if (!strcmp(type, "imsi"))
-		return subscr_get_by_imsi(gsmnet->subscr_group, id);
+	if (!strcmp(type, "extension") || !strcmp(type, "msisdn"))
+		return vlr_subscr_find_by_msisdn(gsmnet->vlr, id);
+	else if (!strcmp(type, "imsi") || !strcmp(type, "id"))
+		return vlr_subscr_find_by_imsi(gsmnet->vlr, id);
 	else if (!strcmp(type, "tmsi"))
-		return subscr_get_by_tmsi(gsmnet->subscr_group, atoi(id));
-	else if (!strcmp(type, "id"))
-		return subscr_get_by_id(gsmnet->subscr_group, atoi(id));
+		return vlr_subscr_find_by_tmsi(gsmnet->vlr, atoi(id));
 
 	return NULL;
 }
@@ -213,18 +224,18 @@
 	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]);
+	struct vlr_subscr *vsub = get_vsub_by_argv(gsmnet, argv[0],
+						       argv[1]);
 
-	if (!subscr) {
+	if (!vsub) {
 		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);
+	subscr_dump_full_vty(vty, vsub);
 
-	subscr_put(subscr);
+	vlr_subscr_put(vsub);
 
 	return CMD_SUCCESS;
 }
@@ -237,28 +248,9 @@
 	"Identify the subscriber by his IMSI\n" \
 	"Identifier for the subscriber\n")
 {
-	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
-	struct gsm_subscriber *subscr;
-
-	subscr = subscr_get_by_imsi(gsmnet->subscr_group, argv[0]);
-	if (subscr)
-		db_sync_subscriber(subscr);
-	else {
-		subscr = subscr_create_subscriber(gsmnet->subscr_group, argv[0]);
-
-		if (!subscr) {
-			vty_out(vty, "%% No subscriber created for IMSI %s%s",
-				argv[0], VTY_NEWLINE);
-			return CMD_WARNING;
-		}
-	}
-
-	/* Show info about the created subscriber. */
-	subscr_dump_full_vty(vty, subscr);
-
-	subscr_put(subscr);
-
-	return CMD_SUCCESS;
+	vty_out(vty, "%% 'subscriber create' now needs to be done at osmo-hlr%s",
+		VTY_NEWLINE);
+	return CMD_WARNING;
 }
 
 DEFUN(subscriber_send_pending_sms,
@@ -267,21 +259,21 @@
 	SUBSCR_HELP "SMS Operations\n" "Send pending SMS\n")
 {
 	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
-	struct gsm_subscriber *subscr;
+	struct vlr_subscr *vsub;
 	struct gsm_sms *sms;
 
-	subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
-	if (!subscr) {
+	vsub = get_vsub_by_argv(gsmnet, argv[0], argv[1]);
+	if (!vsub) {
 		vty_out(vty, "%% No subscriber found for %s %s%s",
 			argv[0], argv[1], VTY_NEWLINE);
 		return CMD_WARNING;
 	}
 
-	sms = db_sms_get_unsent_by_subscr(gsmnet, subscr->id, UINT_MAX);
+	sms = db_sms_get_unsent_for_subscr(vsub, UINT_MAX);
 	if (sms)
 		gsm411_send_sms_subscr(sms->receiver, sms);
 
-	subscr_put(subscr);
+	vlr_subscr_put(vsub);
 
 	return CMD_SUCCESS;
 }
@@ -292,12 +284,12 @@
 	SUBSCR_HELP "SMS Operations\n" SUBSCR_HELP "Send 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]);
-	struct gsm_subscriber *sender = get_subscr_by_argv(gsmnet, argv[2], argv[3]);
+	struct vlr_subscr *vsub = get_vsub_by_argv(gsmnet, argv[0], argv[1]);
+	struct vlr_subscr *sender = get_vsub_by_argv(gsmnet, argv[2], argv[3]);
 	char *str;
 	int rc;
 
-	if (!subscr) {
+	if (!vsub) {
 		vty_out(vty, "%% No subscriber found for %s %s%s",
 			argv[0], argv[1], VTY_NEWLINE);
 		rc = CMD_WARNING;
@@ -312,15 +304,15 @@
 	}
 
 	str = argv_concat(argv, argc, 4);
-	rc = _send_sms_str(subscr, sender, str, 0);
+	rc = _send_sms_str(vsub, sender, str, 0);
 	talloc_free(str);
 
 err:
 	if (sender)
-		subscr_put(sender);
+		vlr_subscr_put(sender);
 
-	if (subscr)
-		subscr_put(subscr);
+	if (vsub)
+		vlr_subscr_put(vsub);
 
 	return rc;
 }
@@ -332,12 +324,12 @@
 	SUBSCR_HELP "Silent SMS Operations\n" SUBSCR_HELP "Send 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]);
-	struct gsm_subscriber *sender = get_subscr_by_argv(gsmnet, argv[2], argv[3]);
+	struct vlr_subscr *vsub = get_vsub_by_argv(gsmnet, argv[0], argv[1]);
+	struct vlr_subscr *sender = get_vsub_by_argv(gsmnet, argv[2], argv[3]);
 	char *str;
 	int rc;
 
-	if (!subscr) {
+	if (!vsub) {
 		vty_out(vty, "%% No subscriber found for %s %s%s",
 			argv[0], argv[1], VTY_NEWLINE);
 		rc = CMD_WARNING;
@@ -352,15 +344,15 @@
 	}
 
 	str = argv_concat(argv, argc, 4);
-	rc = _send_sms_str(subscr, sender, str, 64);
+	rc = _send_sms_str(vsub, sender, str, 64);
 	talloc_free(str);
 
 err:
 	if (sender)
-		subscr_put(sender);
+		vlr_subscr_put(sender);
 
-	if (subscr)
-		subscr_put(subscr);
+	if (vsub)
+		vlr_subscr_put(vsub);
 
 	return rc;
 }
@@ -379,10 +371,10 @@
 	CHAN_TYPE_HELP)
 {
 	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
-	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	struct vlr_subscr *vsub = get_vsub_by_argv(gsmnet, argv[0], argv[1]);
 	int rc, type;
 
-	if (!subscr) {
+	if (!vsub) {
 		vty_out(vty, "%% No subscriber found for %s %s%s",
 			argv[0], argv[1], VTY_NEWLINE);
 		return CMD_WARNING;
@@ -397,15 +389,15 @@
 	else
 		type = RSL_CHANNEED_ANY;	/* Defaults to ANY */
 
-	rc = gsm_silent_call_start(subscr, vty, type);
+	rc = gsm_silent_call_start(vsub, vty, type);
 	if (rc <= 0) {
 		vty_out(vty, "%% Subscriber not attached%s",
 			VTY_NEWLINE);
-		subscr_put(subscr);
+		vlr_subscr_put(vsub);
 		return CMD_WARNING;
 	}
 
-	subscr_put(subscr);
+	vlr_subscr_put(vsub);
 
 	return CMD_SUCCESS;
 }
@@ -417,22 +409,22 @@
 	CHAN_TYPE_HELP)
 {
 	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
-	struct gsm_subscriber *subscr = get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	struct vlr_subscr *vsub = get_vsub_by_argv(gsmnet, argv[0], argv[1]);
 	int rc;
 
-	if (!subscr) {
+	if (!vsub) {
 		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);
+	rc = gsm_silent_call_stop(vsub);
 	if (rc < 0) {
-		subscr_put(subscr);
+		vlr_subscr_put(vsub);
 		return CMD_WARNING;
 	}
 
-	subscr_put(subscr);
+	vlr_subscr_put(vsub);
 
 	return CMD_SUCCESS;
 }
@@ -449,10 +441,10 @@
 	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]);
+	struct vlr_subscr *vsub = get_vsub_by_argv(gsmnet, argv[0], argv[1]);
 	int level;
 
-	if (!subscr) {
+	if (!vsub) {
 		vty_out(vty, "%% No subscriber found for %s %s%s",
 			argv[0], argv[1], VTY_NEWLINE);
 		return CMD_WARNING;
@@ -461,15 +453,15 @@
 	level = atoi(argv[2]);
 	text = argv_concat(argv, argc, 3);
 	if (!text) {
-		subscr_put(subscr);
+		vlr_subscr_put(vsub);
 		return CMD_WARNING;
 	}
 
-	conn = connection_for_subscr(subscr);
+	conn = connection_for_subscr(vsub);
 	if (!conn) {
 		vty_out(vty, "%% An active connection is required for %s %s%s",
 			argv[0], argv[1], VTY_NEWLINE);
-		subscr_put(subscr);
+		vlr_subscr_put(vsub);
 		talloc_free(text);
 		return CMD_WARNING;
 	}
@@ -477,7 +469,7 @@
 	msc_send_ussd_notify(conn, level, text);
 	msc_send_ussd_release_complete(conn);
 
-	subscr_put(subscr);
+	vlr_subscr_put(vsub);
 	talloc_free(text);
 	return CMD_SUCCESS;
 }
@@ -485,32 +477,12 @@
 DEFUN(ena_subscr_delete,
       ena_subscr_delete_cmd,
       "subscriber " SUBSCR_TYPES " ID delete",
-	SUBSCR_HELP "Delete subscriber in HLR\n")
+	SUBSCR_HELP "Delete subscriber in VLR\n")
 {
-	int rc;
-	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;
-	}
-
-	if (subscr->use_count != 1) {
-		vty_out(vty, "Removing active subscriber%s", VTY_NEWLINE);
-	}
-
-	rc = db_subscriber_delete(subscr);
-	subscr_put(subscr);
-
-	if (rc != 0) {
-		vty_out(vty, "Failed to remove subscriber%s", VTY_NEWLINE);
-		return CMD_WARNING;
-	}
-
-	return CMD_SUCCESS;
+	vty_out(vty, "%% 'subscriber delete' is no longer supported.%s"
+		"%% This is now up to osmo-hlr.%s",
+		VTY_NEWLINE, VTY_NEWLINE);
+	return CMD_WARNING;
 }
 
 DEFUN(ena_subscr_expire,
@@ -519,19 +491,28 @@
 	SUBSCR_HELP "Expire the subscriber Now\n")
 {
 	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
-	struct gsm_subscriber *subscr =
-			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	struct vlr_subscr *vsub = get_vsub_by_argv(gsmnet, argv[0],
+						       argv[1]);
 
-	if (!subscr) {
+	if (!vsub) {
 		vty_out(vty, "%% No subscriber found for %s %s%s",
 			argv[0], argv[1], VTY_NEWLINE);
 		return CMD_WARNING;
 	}
 
-	subscr->expire_lu = time(0);
-	db_sync_subscriber(subscr);
-	subscr_put(subscr);
+	if (vsub->lu_complete) {
+		vsub->lu_complete = false;
+		vlr_subscr_put(vsub);
+		vty_out(vty, "%% VLR released subscriber %s%s",
+			vlr_subscr_name(vsub), VTY_NEWLINE);
+	}
 
+	if (vsub->use_count > 1)
+		vty_out(vty, "%% Subscriber %s is still in use,"
+			" should be released soon%s",
+			vlr_subscr_name(vsub), VTY_NEWLINE);
+
+	vlr_subscr_put(vsub);
 	return CMD_SUCCESS;
 }
 
@@ -542,22 +523,10 @@
 	"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;
+	vty_out(vty, "%% 'subscriber authorized' is no longer supported.%s"
+		"%% Authorization is now up to osmo-hlr.%s",
+		VTY_NEWLINE, VTY_NEWLINE);
+	return CMD_WARNING;
 }
 
 DEFUN(ena_subscr_name,
@@ -566,38 +535,10 @@
 	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;
-	}
-
-	if (strlen(name) > sizeof(subscr->name)-1) {
-		vty_out(vty,
-			"%% NAME is too long, max. %zu characters are allowed%s",
-			sizeof(subscr->name)-1, VTY_NEWLINE);
-		subscr_put(subscr);
-		return CMD_WARNING;
-	}
-
-	osmo_strlcpy(subscr->name, name, sizeof(subscr->name));
-	talloc_free(name);
-	db_sync_subscriber(subscr);
-
-	subscr_put(subscr);
-
-	return CMD_SUCCESS;
+	vty_out(vty, "%% 'subscriber name' is no longer supported.%s"
+		"%% This is now up to osmo-hlr.%s",
+		VTY_NEWLINE, VTY_NEWLINE);
+	return CMD_WARNING;
 }
 
 DEFUN(ena_subscr_extension,
@@ -606,30 +547,10 @@
 	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 *ext = argv[2];
-
-	if (!subscr) {
-		vty_out(vty, "%% No subscriber found for %s %s%s",
-			argv[0], argv[1], VTY_NEWLINE);
-		return CMD_WARNING;
-	}
-
-	if (strlen(ext) > sizeof(subscr->extension)-1) {
-		vty_out(vty,
-			"%% EXTENSION is too long, max. %zu characters are allowed%s",
-			sizeof(subscr->extension)-1, VTY_NEWLINE);
-		return CMD_WARNING;
-	}
-
-	osmo_strlcpy(subscr->extension, ext, sizeof(subscr->extension));
-	db_sync_subscriber(subscr);
-
-	subscr_put(subscr);
-
-	return CMD_SUCCESS;
+	vty_out(vty, "%% 'subscriber extension' is no longer supported.%s"
+		"%% This is now up to osmo-hlr.%s",
+		VTY_NEWLINE, VTY_NEWLINE);
+	return CMD_WARNING;
 }
 
 DEFUN(ena_subscr_handover,
@@ -642,20 +563,20 @@
 	struct gsm_subscriber_connection *conn;
 	struct gsm_bts *bts;
 	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
-	struct gsm_subscriber *subscr =
-			get_subscr_by_argv(gsmnet, argv[0], argv[1]);
+	struct vlr_subscr *vsub =
+			get_vsub_by_argv(gsmnet, argv[0], argv[1]);
 
-	if (!subscr) {
+	if (!vsub) {
 		vty_out(vty, "%% No subscriber found for %s %s.%s",
 			argv[0], argv[1], VTY_NEWLINE);
 		return CMD_WARNING;
 	}
 
-	conn = connection_for_subscr(subscr);
+	conn = connection_for_subscr(vsub);
 	if (!conn) {
 		vty_out(vty, "%% No active connection for subscriber %s %s.%s",
 			argv[0], argv[1], VTY_NEWLINE);
-		subscr_put(subscr);
+		vlr_subscr_put(vsub);
 		return CMD_WARNING;
 	}
 
@@ -663,7 +584,7 @@
 	if (!bts) {
 		vty_out(vty, "%% BTS with number(%d) could not be found.%s",
 			atoi(argv[2]), VTY_NEWLINE);
-		subscr_put(subscr);
+		vlr_subscr_put(vsub);
 		return CMD_WARNING;
 	}
 
@@ -679,7 +600,7 @@
 			VTY_NEWLINE);
 	}
 
-	subscr_put(subscr);
+	vlr_subscr_put(vsub);
 	return CMD_SUCCESS;
 }
 
@@ -695,69 +616,10 @@
       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);
-		vty_out(vty, "%% Unknown auth method %s%s",
-				alg_str, VTY_NEWLINE);
-		return CMD_WARNING;
-	}
-
-	if (ki_str) {
-		rc = osmo_hexparse(ki_str, ainfo.a3a8_ki, sizeof(ainfo.a3a8_ki));
-		if ((rc > maxlen) || (rc < minlen)) {
-			subscr_put(subscr);
-			vty_out(vty, "%% Wrong Ki `%s'%s",
-				ki_str, VTY_NEWLINE);
-			return CMD_WARNING;
-		}
-		ainfo.a3a8_ki_len = rc;
-	} else {
-		ainfo.a3a8_ki_len = 0;
-		if (minlen) {
-			subscr_put(subscr);
-			vty_out(vty, "%% Missing Ki argument%s", VTY_NEWLINE);
-			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);
-
-	if (rc) {
-		vty_out(vty, "%% Operation has failed%s", VTY_NEWLINE);
-		return CMD_WARNING;
-	}
-	return CMD_SUCCESS;
+	vty_out(vty, "%% 'subscriber a3a8' is no longer supported.%s"
+		"%% This is now up to osmo-hlr.%s",
+		VTY_NEWLINE, VTY_NEWLINE);
+	return CMD_WARNING;
 }
 
 DEFUN(subscriber_purge,
@@ -765,12 +627,11 @@
       "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->subscr_group);
-	vty_out(vty, "%d subscriber(s) were purged.%s", purged, VTY_NEWLINE);
-	return CMD_SUCCESS;
+	/* TODO: does this still have a use with the VLR? */
+	vty_out(vty, "%% 'subscriber purge-inactive' is no longer supported.%s"
+		"%% This is now up to osmo-hlr.%s",
+		VTY_NEWLINE, VTY_NEWLINE);
+	return CMD_WARNING;
 }
 
 DEFUN(subscriber_update,
@@ -778,18 +639,9 @@
       "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;
+	vty_out(vty, "%% 'subscriber update' is no longer supported.%s",
+		VTY_NEWLINE);
+	return CMD_WARNING;
 }
 
 static int scall_cbfn(unsigned int subsys, unsigned int signal,
@@ -1035,7 +887,7 @@
 	LOGGING_STR FILTER_STR
       "Filter log messages by IMSI\n" "IMSI to be used as filter\n")
 {
-	struct gsm_subscriber *vlr_subscr;
+	struct vlr_subscr *vlr_subscr;
 	struct bsc_subscr *bsc_subscr;
 	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
 	struct log_target *tgt = osmo_log_vty2tgt(vty);
@@ -1044,7 +896,7 @@
 	if (!tgt)
 		return CMD_WARNING;
 
-	vlr_subscr = subscr_get_by_imsi(gsmnet->subscr_group, imsi);
+	vlr_subscr = vlr_subscr_find_by_imsi(gsmnet->vlr, imsi);
 	bsc_subscr = bsc_subscr_find_by_imsi(gsmnet->bsc_subscribers, imsi);
 
 	if (!vlr_subscr && !bsc_subscr) {
@@ -1053,10 +905,55 @@
 		return CMD_WARNING;
 	}
 
+	log_set_filter_vlr_subscr(tgt, vlr_subscr);
 	log_set_filter_bsc_subscr(tgt, bsc_subscr);
 	return CMD_SUCCESS;
 }
 
+static struct cmd_node hlr_node = {
+	HLR_NODE,
+	"%s(config-hlr)# ",
+	1,
+};
+
+DEFUN(cfg_hlr, cfg_hlr_cmd,
+      "hlr", "Configure connection to the HLR")
+{
+	vty->node = HLR_NODE;
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_hlr_remote_ip, cfg_hlr_remote_ip_cmd, "remote-ip A.B.C.D",
+      "Remote GSUP address of the HLR\n"
+      "Remote GSUP address (default: " MSC_HLR_REMOTE_IP_DEFAULT ")")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	talloc_free((void*)gsmnet->gsup_server_addr_str);
+	gsmnet->gsup_server_addr_str = talloc_strdup(gsmnet, argv[0]);
+	return CMD_SUCCESS;
+}
+
+DEFUN(cfg_hlr_remote_port, cfg_hlr_remote_port_cmd, "remote-port <1-65535>",
+      "Remote GSUP port of the HLR\n"
+      "Remote GSUP port (default: " OSMO_STRINGIFY(MSC_HLR_REMOTE_PORT_DEFAULT) ")")
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+	gsmnet->gsup_server_port = atoi(argv[0]);
+	return CMD_SUCCESS;
+}
+
+static int config_write_hlr(struct vty *vty)
+{
+	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
+
+	vty_out(vty, "hlr%s", VTY_NEWLINE);
+	vty_out(vty, " remote-ip %s%s",
+		gsmnet->gsup_server_addr_str, VTY_NEWLINE);
+	vty_out(vty, " remote-port %u%s",
+		gsmnet->gsup_server_port, VTY_NEWLINE);
+	return CMD_SUCCESS;
+}
+
 static struct cmd_node nitb_node = {
 	NITB_NODE,
 	"%s(config-nitb)# ",
@@ -1077,18 +974,10 @@
       "Set random parameters for a new record when a subscriber is first seen.\n"
       "Minimum for subscriber extension\n""Maximum for subscriber extension\n")
 {
-	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
-	uint64_t mi = atoll(argv[0]), ma = atoll(argv[1]);
-	gsmnet->auto_create_subscr = true;
-	gsmnet->auto_assign_exten = true;
-	if (mi >= ma) {
-		vty_out(vty, "Incorrect range: %s >= %s, expected MIN < MAX%s",
-			argv[0], argv[1], VTY_NEWLINE);
-		return CMD_WARNING;
-	}
-	gsmnet->ext_min = mi;
-	gsmnet->ext_max = ma;
-        return CMD_SUCCESS;
+	vty_out(vty, "%% 'subscriber-create-on-demand' is no longer supported.%s"
+		"%% This is now up to osmo-hlr.%s",
+		VTY_NEWLINE, VTY_NEWLINE);
+	return CMD_WARNING;
 }
 
 DEFUN(cfg_nitb_subscr_create, cfg_nitb_subscr_create_cmd,
@@ -1096,19 +985,20 @@
       "Make a new record when a subscriber is first seen.\n"
       "Do not automatically assign extension to created subscribers\n")
 {
-	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
-	gsmnet->auto_create_subscr = true;
-	gsmnet->auto_assign_exten = argc ? false : true;
-	return CMD_SUCCESS;
+	vty_out(vty, "%% 'subscriber-create-on-demand' is no longer supported.%s"
+		"%% This is now up to osmo-hlr.%s",
+		VTY_NEWLINE, VTY_NEWLINE);
+	return CMD_WARNING;
 }
 
 DEFUN(cfg_nitb_no_subscr_create, cfg_nitb_no_subscr_create_cmd,
       "no subscriber-create-on-demand",
       NO_STR "Make a new record when a subscriber is first seen.\n")
 {
-	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
-	gsmnet->auto_create_subscr = false;
-	return CMD_SUCCESS;
+	vty_out(vty, "%% 'subscriber-create-on-demand' is no longer supported.%s"
+		"%% This is now up to osmo-hlr.%s",
+		VTY_NEWLINE, VTY_NEWLINE);
+	return CMD_WARNING;
 }
 
 DEFUN(cfg_nitb_assign_tmsi, cfg_nitb_assign_tmsi_cmd,
@@ -1116,7 +1006,7 @@
       "Assign TMSI during Location Updating.\n")
 {
 	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
-	gsmnet->avoid_tmsi = 0;
+	gsmnet->vlr->cfg.assign_tmsi = true;
 	return CMD_SUCCESS;
 }
 
@@ -1125,7 +1015,7 @@
       NO_STR "Assign TMSI during Location Updating.\n")
 {
 	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
-	gsmnet->avoid_tmsi = 1;
+	gsmnet->vlr->cfg.assign_tmsi = false;
 	return CMD_SUCCESS;
 }
 
@@ -1134,19 +1024,8 @@
 	struct gsm_network *gsmnet = gsmnet_from_vty(vty);
 
 	vty_out(vty, "nitb%s", VTY_NEWLINE);
-	if (!gsmnet->auto_create_subscr)
-		vty_out(vty, " no subscriber-create-on-demand%s", VTY_NEWLINE);
-	else
-		vty_out(vty, " subscriber-create-on-demand%s%s",
-			gsmnet->auto_assign_exten ? "" : " no-extension",
-			VTY_NEWLINE);
-
-	if (gsmnet->ext_min != GSM_MIN_EXTEN || gsmnet->ext_max != GSM_MAX_EXTEN)
-		vty_out(vty, " subscriber-create-on-demand random %"PRIu64" %"
-			PRIu64"%s", gsmnet->ext_min, gsmnet->ext_max,
-			VTY_NEWLINE);
 	vty_out(vty, " %sassign-tmsi%s",
-		gsmnet->avoid_tmsi ? "no " : "", VTY_NEWLINE);
+		gsmnet->vlr->cfg.assign_tmsi? "" : "no ", VTY_NEWLINE);
 	return CMD_SUCCESS;
 }
 
@@ -1196,6 +1075,10 @@
 	install_element(CFG_LOG_NODE, &log_level_sms_cmd);
 	install_element(CFG_LOG_NODE, &logging_fltr_imsi_cmd);
 
+	install_element(CONFIG_NODE, &cfg_hlr_cmd);
+	install_node(&hlr_node, config_write_hlr);
+	install_element(HLR_NODE, &cfg_hlr_remote_ip_cmd);
+	install_element(HLR_NODE, &cfg_hlr_remote_port_cmd);
 
 	install_element(CONFIG_NODE, &cfg_nitb_cmd);
 	install_node(&nitb_node, config_write_nitb);
diff --git a/src/libvlr/vlr_access_req_fsm.c b/src/libvlr/vlr_access_req_fsm.c
index 4e63e29..279e3d4 100644
--- a/src/libvlr/vlr_access_req_fsm.c
+++ b/src/libvlr/vlr_access_req_fsm.c
@@ -343,6 +343,7 @@
 		vsub = vlr_subscr_find_by_tmsi(par->vlr, par->tmsi);
 	}
 	if (vsub) {
+		log_set_context(LOG_CTX_VLR_SUBSCR, vsub);
 		if (vsub->proc_arq_fsm && fi != vsub->proc_arq_fsm) {
 			LOGPFSML(fi, LOGL_ERROR,
 				 "Another proc_arq_fsm is already"
diff --git a/src/osmo-nitb/bsc_hack.c b/src/osmo-nitb/bsc_hack.c
index 17b23b2..89bbb5e 100644
--- a/src/osmo-nitb/bsc_hack.c
+++ b/src/osmo-nitb/bsc_hack.c
@@ -44,7 +44,6 @@
 #include <openbsc/vty.h>
 #include <openbsc/bss.h>
 #include <openbsc/mncc.h>
-#include <openbsc/token_auth.h>
 #include <openbsc/handover_decision.h>
 #include <openbsc/rrlp.h>
 #include <osmocom/ctrl/control_if.h>
@@ -240,7 +239,7 @@
 
 static void subscr_expire_cb(void *data)
 {
-	subscr_expire(bsc_gsmnet->subscr_group);
+	/* TODO expire vlr_subscrs? */
 	osmo_timer_schedule(&bsc_gsmnet->subscr_expire_timer, EXPIRE_INTERVAL);
 }
 
@@ -261,7 +260,6 @@
 
 	tall_bsc_ctx = talloc_named_const(NULL, 1, "openbsc");
 	talloc_ctx_init(tall_bsc_ctx);
-	on_dso_load_token();
 	on_dso_load_rrlp();
 	on_dso_load_ho_dec();
 
@@ -285,6 +283,10 @@
 	/* Initialize VTY */
 	bsc_vty_init(bsc_gsmnet);
 	ctrl_vty_init(tall_bsc_ctx);
+	if (msc_vlr_alloc(bsc_gsmnet)) {
+		fprintf(stderr, "Failed to allocate VLR\n");
+		exit(1);
+	}
 
 #ifdef BUILD_SMPP
 	if (smpp_openbsc_alloc_init(tall_bsc_ctx) < 0)
@@ -342,7 +344,7 @@
 		return -1;
 	}
 
-	if (msc_ctrl_cmds_install() != 0) {
+	if (msc_ctrl_cmds_install(bsc_gsmnet) != 0) {
 		printf("Failed to initialize the MSC control commands.\n");
 		return -1;
 	}
@@ -356,6 +358,14 @@
 		exit(1);
 	}
 
+	osmo_fsm_log_addr(true);
+	if (msc_vlr_start(bsc_gsmnet)) {
+		fprintf(stderr, "Failed to start VLR\n");
+		exit(1);
+	}
+
+	msc_subscr_conn_init();
+
 	if (db_init(database_name)) {
 		printf("DB: Failed to init database. Please check the option settings.\n");
 		return -1;