sgsn: Support subscriber based authentication

This commit mainly extends sgsn_auth.c to use and support the
auth_state SGSN_AUTH_AUTHENTICATE. It will be activated when IMSI and
IMEI are available, authentication is required
(subscr->sgsn_data->authenticate is set), but the MM context is not
marked as authenticated. If the state has been set to
SGSN_AUTH_AUTHENTICATE and sgsn_auth_update() is called, the GMM
layer will be informed by invoking gsm0408_gprs_authenticate().

Sponsored-by: On-Waves ehf
diff --git a/openbsc/include/openbsc/gprs_sgsn.h b/openbsc/include/openbsc/gprs_sgsn.h
index 1b63389..d3cd8bb 100644
--- a/openbsc/include/openbsc/gprs_sgsn.h
+++ b/openbsc/include/openbsc/gprs_sgsn.h
@@ -272,8 +272,8 @@
 };
 
 struct sgsn_subscriber_data {
-	struct sgsn_mm_ctx *mm;
-	enum sgsn_auth_state auth_state;
+	struct sgsn_mm_ctx	*mm;
+	int			authenticate;
 };
 
 struct sgsn_config;
diff --git a/openbsc/src/gprs/sgsn_auth.c b/openbsc/src/gprs/sgsn_auth.c
index 1f11948..1219176 100644
--- a/openbsc/src/gprs/sgsn_auth.c
+++ b/openbsc/src/gprs/sgsn_auth.c
@@ -110,6 +110,10 @@
 		if (mmctx->subscr->flags & GPRS_SUBSCRIBER_UPDATE_PENDING)
 			return mmctx->auth_state;
 
+		if (mmctx->subscr->sgsn_data->authenticate &&
+		    !mmctx->is_authenticated)
+			return SGSN_AUTH_AUTHENTICATE;
+
 		if (mmctx->subscr->authorized)
 			return SGSN_AUTH_ACCEPTED;
 
@@ -180,6 +184,9 @@
 	mmctx->auth_state = auth_state;
 
 	switch (auth_state) {
+	case SGSN_AUTH_AUTHENTICATE:
+		gsm0408_gprs_authenticate(mmctx);
+		break;
 	case SGSN_AUTH_ACCEPTED:
 		gsm0408_gprs_access_granted(mmctx);
 		break;
diff --git a/openbsc/src/gprs/sgsn_vty.c b/openbsc/src/gprs/sgsn_vty.c
index a73b771..4bb6a86 100644
--- a/openbsc/src/gprs/sgsn_vty.c
+++ b/openbsc/src/gprs/sgsn_vty.c
@@ -441,15 +441,15 @@
 	"The IMSI\n"
 
 DEFUN(update_subscr_insert, update_subscr_insert_cmd,
-	UPDATE_SUBSCR_STR "insert authorized (0|1)",
+	UPDATE_SUBSCR_STR "insert (authorized|authenticate) (0|1)",
 	UPDATE_SUBSCR_HELP
 	"Insert data into the subscriber record\n"
 	"Authorize the subscriber to attach\n"
 	"New option value\n")
 {
 	const char *imsi = argv[0];
-	const char *option = "authorized";
-	const char *value = argv[1];
+	const char *option = argv[1];
+	const char *value = argv[2];
 
 	struct gsm_subscriber *subscr;
 
@@ -461,6 +461,8 @@
 
 	if (!strcmp(option, "authorized"))
 		subscr->authorized = atoi(value);
+	else
+		subscr->sgsn_data->authenticate = atoi(value);
 
 	subscr_put(subscr);
 
diff --git a/openbsc/tests/sgsn/sgsn_test.c b/openbsc/tests/sgsn/sgsn_test.c
index 26d81a7..f5f6820 100644
--- a/openbsc/tests/sgsn/sgsn_test.c
+++ b/openbsc/tests/sgsn/sgsn_test.c
@@ -591,6 +591,60 @@
 	subscr_request_update_cb = __real_gprs_subscr_request_update;
 }
 
+int my_subscr_request_update_fake_auth(struct sgsn_mm_ctx *mmctx) {
+	int rc;
+	rc = __real_gprs_subscr_request_update(mmctx);
+	if (rc == -ENOTSUP) {
+		struct gsm_subscriber *subscr;
+		int old_sgsn_tx_counter = sgsn_tx_counter;
+
+		OSMO_ASSERT(mmctx->subscr);
+		/* Prevent subscr from being deleted */
+		subscr = subscr_get(mmctx->subscr);
+
+		/* Start authentication procedure */
+		gprs_subscr_update(subscr);
+
+		/* This will cause a GPRS AUTH AND CIPHERING REQ (cksn broken) */
+		OSMO_ASSERT(old_sgsn_tx_counter == sgsn_tx_counter - 1);
+
+		/* Restore sgsn_tx_counter to keep test_gmm_attach happy */
+		sgsn_tx_counter = old_sgsn_tx_counter;
+
+		/* Fake an authentication */
+		OSMO_ASSERT(subscr->sgsn_data->mm);
+		subscr->sgsn_data->mm->is_authenticated = 1;
+		gprs_subscr_update(subscr);
+
+		subscr_put(subscr);
+	}
+	return rc;
+};
+
+static void test_gmm_attach_subscr_fake_auth(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_fake_auth;
+
+	subscr = gprs_subscr_get_or_create("123456789012345");
+	subscr->authorized = 1;
+	subscr->sgsn_data->authenticate = 1;
+	subscr_put(subscr);
+
+	printf("Auth policy 'remote', auth faked: ");
+	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;
+}
+
 /*
  * Test the GMM Rejects
  */
@@ -1112,6 +1166,7 @@
 	test_gmm_status_no_mmctx();
 	test_gmm_attach_acl();
 	test_gmm_attach_subscr();
+	test_gmm_attach_subscr_fake_auth();
 	test_gmm_reject();
 	test_gmm_cancel();
 	test_gmm_ptmsi_allocation();
diff --git a/openbsc/tests/sgsn/sgsn_test.ok b/openbsc/tests/sgsn/sgsn_test.ok
index 4b061a5..7d739ad 100644
--- a/openbsc/tests/sgsn/sgsn_test.ok
+++ b/openbsc/tests/sgsn/sgsn_test.ok
@@ -7,6 +7,7 @@
 Testing GMM Status (no MMCTX)
 Auth policy 'closed': Testing GMM attach
 Auth policy 'remote': Testing GMM attach
+Auth policy 'remote', auth faked: Testing GMM attach
 Testing GMM reject
   - Attach Request (invalid MI length)
   - Attach Request (invalid MI type)