sgsn: Integrate subscriber handling into the SGSN

This commit adds a new authorization policy 'remote' and uses
the subscriber cache for authorization when this policy is being used.

Note that there is no remote backend implemented yet. After the
IMSI/IMEI have been acquired, a request would be sent to the remote
peer. The attach/auth-ciph procedure continues when authorization
info has been received from the peer. This means, that
gprs_subscr_update() must be called then to tell the GMM layer
that it can proceed. A later commit will add VTY commands to do this
manually.

Sponsored-by: On-Waves ehf
diff --git a/openbsc/include/openbsc/sgsn.h b/openbsc/include/openbsc/sgsn.h
index 33bc72f..6b83337 100644
--- a/openbsc/include/openbsc/sgsn.h
+++ b/openbsc/include/openbsc/sgsn.h
@@ -10,7 +10,8 @@
 enum sgsn_auth_policy {
 	SGSN_AUTH_POLICY_OPEN,
 	SGSN_AUTH_POLICY_CLOSED,
-	SGSN_AUTH_POLICY_ACL_ONLY
+	SGSN_AUTH_POLICY_ACL_ONLY,
+	SGSN_AUTH_POLICY_REMOTE
 };
 
 struct sgsn_config {
diff --git a/openbsc/src/gprs/gprs_sgsn.c b/openbsc/src/gprs/gprs_sgsn.c
index 71cd742..525bfab 100644
--- a/openbsc/src/gprs/gprs_sgsn.c
+++ b/openbsc/src/gprs/gprs_sgsn.c
@@ -187,6 +187,14 @@
 		osmo_timer_del(&mm->timer);
 	}
 
+	/* Detach from subscriber which is possibly freed then */
+	if (mm->subscr) {
+		struct gsm_subscriber *subscr =  mm->subscr;
+		mm->subscr = NULL;
+		subscr->mm = NULL;
+		gprs_subscr_delete(subscr);
+	}
+
 	/* Unlink from global list of MM contexts */
 	llist_del(&mm->list);
 
@@ -455,7 +463,24 @@
 void sgsn_update_subscriber_data(struct sgsn_mm_ctx *mmctx,
 				 struct gsm_subscriber *subscr)
 {
-	OSMO_ASSERT(mmctx);
+	if (!mmctx && subscr && strlen(subscr->imsi) > 0) {
+		mmctx = sgsn_mm_ctx_by_imsi(subscr->imsi);
+		OSMO_ASSERT(!mmctx || !mmctx->subscr || mmctx->subscr == subscr);
+	}
+
+	if (!mmctx) {
+		LOGP(DMM, LOGL_INFO,
+		     "Subscriber data update for unregistered MM context, IMSI %s\n",
+		     subscr->imsi);
+		return;
+	}
+
+	LOGMMCTXP(LOGL_INFO, mmctx, "Subscriber data update");
+
+	if (!subscr->mm && !mmctx->subscr) {
+		mmctx->subscr =	subscr_get(subscr);
+		mmctx->subscr->mm = mmctx;
+	}
 
 	sgsn_auth_update(mmctx);
 }
diff --git a/openbsc/src/gprs/sgsn_auth.c b/openbsc/src/gprs/sgsn_auth.c
index eb85d45..071bdab 100644
--- a/openbsc/src/gprs/sgsn_auth.c
+++ b/openbsc/src/gprs/sgsn_auth.c
@@ -22,6 +22,7 @@
 #include <openbsc/sgsn.h>
 #include <openbsc/gprs_sgsn.h>
 #include <openbsc/gprs_gmm.h>
+#include <openbsc/gsm_subscriber.h>
 
 #include <openbsc/debug.h>
 
@@ -101,6 +102,18 @@
 	case SGSN_AUTH_POLICY_ACL_ONLY:
 		check_acl = 1;
 		break;
+
+	case SGSN_AUTH_POLICY_REMOTE:
+		if (!mmctx->subscr)
+			return mmctx->auth_state;
+
+		if (mmctx->subscr->flags & GPRS_SUBSCRIBER_UPDATE_PENDING)
+			return mmctx->auth_state;
+
+		if (mmctx->subscr->authorized)
+			return SGSN_AUTH_ACCEPTED;
+
+		return SGSN_AUTH_REJECTED;
 	}
 
 	if (!strlen(mmctx->imsi)) {
@@ -126,7 +139,15 @@
 
 int sgsn_auth_request(struct sgsn_mm_ctx *mmctx)
 {
-	/* TODO: Add remote subscriber update requests here */
+	LOGMMCTXP(LOGL_DEBUG, mmctx, "Requesting authorization\n");
+
+	if (sgsn->cfg.auth_policy == SGSN_AUTH_POLICY_REMOTE && !mmctx->subscr) {
+		if (gprs_subscr_request_update(mmctx) >= 0) {
+			LOGMMCTXP(LOGL_INFO, mmctx,
+				  "Missing information, requesting subscriber data\n");
+			return 0;
+		}
+	}
 
 	sgsn_auth_update(mmctx);
 
@@ -136,12 +157,14 @@
 void sgsn_auth_update(struct sgsn_mm_ctx *mmctx)
 {
 	enum sgsn_auth_state auth_state;
+	struct gsm_subscriber *subscr = mmctx->subscr;
 
 	LOGMMCTXP(LOGL_DEBUG, mmctx, "Updating authorization\n");
 
 	auth_state = sgsn_auth_state(mmctx);
-	if (auth_state == SGSN_AUTH_UNKNOWN) {
-		/* Reject requests since remote updates are NYI */
+	if (auth_state == SGSN_AUTH_UNKNOWN && subscr &&
+	    !(subscr->flags & GPRS_SUBSCRIBER_UPDATE_PENDING)) {
+		/* Reject requests if gprs_subscr_request_update fails */
 		LOGMMCTXP(LOGL_ERROR, mmctx,
 			  "Missing information, authorization not possible\n");
 		auth_state = SGSN_AUTH_REJECTED;
diff --git a/openbsc/src/gprs/sgsn_vty.c b/openbsc/src/gprs/sgsn_vty.c
index 6381671..5fb230f 100644
--- a/openbsc/src/gprs/sgsn_vty.c
+++ b/openbsc/src/gprs/sgsn_vty.c
@@ -45,6 +45,7 @@
 	{ SGSN_AUTH_POLICY_OPEN,	"accept-all" },
 	{ SGSN_AUTH_POLICY_CLOSED,	"closed" },
 	{ SGSN_AUTH_POLICY_ACL_ONLY,    "acl-only" },
+	{ SGSN_AUTH_POLICY_REMOTE,      "remote" },
 	{ 0, NULL }
 };
 
@@ -358,14 +359,15 @@
 }
 
 DEFUN(cfg_auth_policy, cfg_auth_policy_cmd,
-	"auth-policy (accept-all|closed|acl-only)",
+	"auth-policy (accept-all|closed|acl-only|remote)",
 	"Autorization Policy of SGSN\n"
 	"Accept all IMSIs (DANGEROUS)\n"
 	"Accept only home network subscribers or those in the ACL\n"
-	"Accept only subscribers in the ACL\n")
+	"Accept only subscribers in the ACL\n"
+	"Use remote subscription data only (HLR)\n")
 {
 	int val = get_string_value(sgsn_auth_pol_strs, argv[0]);
-	OSMO_ASSERT(val >= SGSN_AUTH_POLICY_OPEN && val <= SGSN_AUTH_POLICY_ACL_ONLY);
+	OSMO_ASSERT(val >= SGSN_AUTH_POLICY_OPEN && val <= SGSN_AUTH_POLICY_REMOTE);
 	g_cfg->auth_policy = val;
 
 	return CMD_SUCCESS;
diff --git a/openbsc/tests/sgsn/Makefile.am b/openbsc/tests/sgsn/Makefile.am
index 0e5d009..970311d 100644
--- a/openbsc/tests/sgsn/Makefile.am
+++ b/openbsc/tests/sgsn/Makefile.am
@@ -7,7 +7,8 @@
 
 sgsn_test_SOURCES = sgsn_test.c
 sgsn_test_LDFLAGS = \
-	-Wl,--wrap=sgsn_update_subscriber_data
+	-Wl,--wrap=sgsn_update_subscriber_data \
+	-Wl,--wrap=gprs_subscr_request_update
 
 sgsn_test_LDADD = \
 	$(top_builddir)/src/gprs/gprs_llc_parse.o \
diff --git a/openbsc/tests/sgsn/sgsn_test.c b/openbsc/tests/sgsn/sgsn_test.c
index 2eb6f38..981a557 100644
--- a/openbsc/tests/sgsn/sgsn_test.c
+++ b/openbsc/tests/sgsn/sgsn_test.c
@@ -67,6 +67,14 @@
 	(*update_subscriber_data_cb)(mmctx, subscr);
 }
 
+/* override, requires '-Wl,--wrap=gprs_subscr_request_update' */
+int __real_gprs_subscr_request_update(struct sgsn_mm_ctx *mmctx);
+int (*subscr_request_update_cb)(struct sgsn_mm_ctx *mmctx) =
+	&__real_gprs_subscr_request_update;
+
+int __wrap_gprs_subscr_request_update(struct sgsn_mm_ctx *mmctx) {
+	return (*subscr_request_update_cb)(mmctx);
+};
 
 static int count(struct llist_head *head)
 {
@@ -477,8 +485,6 @@
 	 * again */
 	srand(1);
 
-	sgsn_acl_add("123456789012345", &sgsn->cfg);
-
 	foreign_tlli = gprs_tmsi2tlli(0xc0000023, TLLI_FOREIGN);
 
 	/* Create a LLE/LLME */
@@ -537,8 +543,52 @@
 	OSMO_ASSERT(count(gprs_llme_list()) == 0);
 	ictx = sgsn_mm_ctx_by_tlli(local_tlli, &raid);
 	OSMO_ASSERT(!ictx);
+}
 
+static void test_gmm_attach_acl(void)
+{
+	const enum sgsn_auth_policy saved_auth_policy = sgsn->cfg.auth_policy;
+
+	sgsn_inst.cfg.auth_policy = SGSN_AUTH_POLICY_CLOSED;
+	sgsn_acl_add("123456789012345", &sgsn->cfg);
+	printf("Auth policy 'closed': ");
+	test_gmm_attach();
 	sgsn_acl_del("123456789012345", &sgsn->cfg);
+
+	sgsn->cfg.auth_policy = saved_auth_policy;
+}
+
+int my_subscr_request_update(struct sgsn_mm_ctx *mmctx) {
+	int rc;
+	rc = __real_gprs_subscr_request_update(mmctx);
+	if (rc == -ENOTSUP) {
+		OSMO_ASSERT(mmctx->subscr);
+		gprs_subscr_update(mmctx->subscr);
+	}
+	return rc;
+};
+
+static void test_gmm_attach_subscr(void)
+{
+	const enum sgsn_auth_policy saved_auth_policy = sgsn->cfg.auth_policy;
+	struct gsm_subscriber *subscr;
+
+	sgsn_inst.cfg.auth_policy = SGSN_AUTH_POLICY_REMOTE;
+	subscr_request_update_cb = my_subscr_request_update;
+
+	subscr = gprs_subscr_get_or_create("123456789012345");
+	subscr->authorized = 1;
+	subscr_put(subscr);
+
+	printf("Auth policy 'remote': ");
+	test_gmm_attach();
+
+	subscr = gprs_subscr_get_by_imsi("123456789012345");
+	OSMO_ASSERT(subscr != NULL);
+	gprs_subscr_delete(subscr);
+
+	sgsn->cfg.auth_policy = saved_auth_policy;
+	subscr_request_update_cb = __real_gprs_subscr_request_update;
 }
 
 /*
@@ -944,7 +994,8 @@
 	test_gmm_detach_no_mmctx();
 	test_gmm_detach_accept_unexpected();
 	test_gmm_status_no_mmctx();
-	test_gmm_attach();
+	test_gmm_attach_acl();
+	test_gmm_attach_subscr();
 	test_gmm_reject();
 	test_gmm_ptmsi_allocation();
 	printf("Done\n");
diff --git a/openbsc/tests/sgsn/sgsn_test.ok b/openbsc/tests/sgsn/sgsn_test.ok
index 1ee80be..86dd0a2 100644
--- a/openbsc/tests/sgsn/sgsn_test.ok
+++ b/openbsc/tests/sgsn/sgsn_test.ok
@@ -5,7 +5,8 @@
 Testing GMM detach (no MMCTX)
 Testing GMM detach accept (unexpected)
 Testing GMM Status (no MMCTX)
-Testing GMM attach
+Auth policy 'closed': Testing GMM attach
+Auth policy 'remote': Testing GMM attach
 Testing GMM reject
   - Attach Request (invalid MI length)
   - Attach Request (invalid MI type)
diff --git a/openbsc/tests/vty_test_runner.py b/openbsc/tests/vty_test_runner.py
index 40053e3..64437a1 100644
--- a/openbsc/tests/vty_test_runner.py
+++ b/openbsc/tests/vty_test_runner.py
@@ -752,6 +752,9 @@
         self.assertTrue(self.vty.verify('auth-policy closed', ['']))
         res = self.vty.command("show running-config")
         self.assert_(res.find('auth-policy closed') > 0)
+        self.assertTrue(self.vty.verify('auth-policy remote', ['']))
+        res = self.vty.command("show running-config")
+        self.assert_(res.find('auth-policy remote') > 0)
 
 def add_nat_test(suite, workdir):
     if not os.path.isfile(os.path.join(workdir, "src/osmo-bsc_nat/osmo-bsc_nat")):