sgsn: Add 'acl-only' authentication policy

Currently the VTY 'auth-policy' command results in setting or clearing
the acl_enabled flag. This also enables the matching of the MCC/MNC
prefix of the IMSI.

This patch adds an additional policy 'acl-only' which disables the
MCC/MNC matching and relies on the ACL only.

Sponsored-by: On-Waves ehf
diff --git a/openbsc/include/openbsc/sgsn.h b/openbsc/include/openbsc/sgsn.h
index f7af750..33bc72f 100644
--- a/openbsc/include/openbsc/sgsn.h
+++ b/openbsc/include/openbsc/sgsn.h
@@ -7,6 +7,12 @@
 #include <osmocom/gprs/gprs_ns.h>
 #include <openbsc/gprs_sgsn.h>
 
+enum sgsn_auth_policy {
+	SGSN_AUTH_POLICY_OPEN,
+	SGSN_AUTH_POLICY_CLOSED,
+	SGSN_AUTH_POLICY_ACL_ONLY
+};
+
 struct sgsn_config {
 	/* parsed from config file */
 
@@ -16,7 +22,7 @@
 	/* misc */
 	struct gprs_ns_inst *nsi;
 
-	int acl_enabled;
+	enum sgsn_auth_policy auth_policy;
 	struct llist_head imsi_acl;
 };
 
diff --git a/openbsc/src/gprs/sgsn_auth.c b/openbsc/src/gprs/sgsn_auth.c
index e123909..d2d4913 100644
--- a/openbsc/src/gprs/sgsn_auth.c
+++ b/openbsc/src/gprs/sgsn_auth.c
@@ -83,25 +83,41 @@
 				     struct sgsn_config *cfg)
 {
 	char mccmnc[16];
+	int check_net = 0;
+	int check_acl = 0;
 
 	OSMO_ASSERT(mmctx);
 
-	if (!sgsn->cfg.acl_enabled)
+	switch (sgsn->cfg.auth_policy) {
+	case SGSN_AUTH_POLICY_OPEN:
 		return SGSN_AUTH_ACCEPTED;
 
+	case SGSN_AUTH_POLICY_CLOSED:
+		check_net = 1;
+		check_acl = 1;
+		break;
+
+	case SGSN_AUTH_POLICY_ACL_ONLY:
+		check_acl = 1;
+		break;
+	}
+
 	if (!strlen(mmctx->imsi)) {
 		LOGMMCTXP(LOGL_NOTICE, mmctx,
 			  "Missing IMSI, authorization state not known\n");
 		return SGSN_AUTH_UNKNOWN;
 	}
 
-	/* As a temorary hack, we simply assume that the IMSI exists,
-	 * as long as it is part of 'our' network */
-	snprintf(mccmnc, sizeof(mccmnc), "%03d%02d", mmctx->ra.mcc, mmctx->ra.mnc);
-	if (strncmp(mccmnc, mmctx->imsi, 5) == 0)
-		return SGSN_AUTH_ACCEPTED;
+	if (check_net) {
+		/* We simply assume that the IMSI exists, as long as it is part
+		 * of 'our' network */
+		snprintf(mccmnc, sizeof(mccmnc), "%03d%02d",
+			 mmctx->ra.mcc, mmctx->ra.mnc);
+		if (strncmp(mccmnc, mmctx->imsi, 5) == 0)
+			return SGSN_AUTH_ACCEPTED;
+	}
 
-	if (sgsn_acl_lookup(mmctx->imsi, &sgsn->cfg))
+	if (check_acl && sgsn_acl_lookup(mmctx->imsi, &sgsn->cfg))
 		return SGSN_AUTH_ACCEPTED;
 
 	return SGSN_AUTH_REJECTED;
diff --git a/openbsc/src/gprs/sgsn_main.c b/openbsc/src/gprs/sgsn_main.c
index d8e01ff..c7c852d 100644
--- a/openbsc/src/gprs/sgsn_main.c
+++ b/openbsc/src/gprs/sgsn_main.c
@@ -78,7 +78,7 @@
 	.config_file = "osmo_sgsn.cfg",
 	.cfg = {
 		.gtp_statedir = "./",
-		.acl_enabled = 1,
+		.auth_policy = SGSN_AUTH_POLICY_CLOSED,
 	},
 };
 struct sgsn_instance *sgsn = &sgsn_inst;
diff --git a/openbsc/src/gprs/sgsn_vty.c b/openbsc/src/gprs/sgsn_vty.c
index 4c4eef3..6381671 100644
--- a/openbsc/src/gprs/sgsn_vty.c
+++ b/openbsc/src/gprs/sgsn_vty.c
@@ -41,6 +41,14 @@
 
 static struct sgsn_config *g_cfg = NULL;
 
+const struct value_string sgsn_auth_pol_strs[] = {
+	{ SGSN_AUTH_POLICY_OPEN,	"accept-all" },
+	{ SGSN_AUTH_POLICY_CLOSED,	"closed" },
+	{ SGSN_AUTH_POLICY_ACL_ONLY,    "acl-only" },
+	{ 0, NULL }
+};
+
+
 #define GSM48_MAX_APN_LEN	102	/* 10.5.6.1 */
 static char *gprs_apn2str(uint8_t *apn, unsigned int len)
 {
@@ -127,7 +135,8 @@
 	}
 
 	vty_out(vty, " auth-policy %s%s",
-		g_cfg->acl_enabled ? "closed" : "accept-all", VTY_NEWLINE);
+		get_value_string(sgsn_auth_pol_strs, g_cfg->auth_policy),
+		VTY_NEWLINE);
 	llist_for_each_entry(acl, &g_cfg->imsi_acl, list)
 		vty_out(vty, " imsi-acl add %s%s", acl->imsi, VTY_NEWLINE);
 
@@ -349,15 +358,15 @@
 }
 
 DEFUN(cfg_auth_policy, cfg_auth_policy_cmd,
-	"auth-policy (accept-all|closed)",
+	"auth-policy (accept-all|closed|acl-only)",
 	"Autorization Policy of SGSN\n"
-	"Accept all IMSIs (DANGEROUS\n"
-	"Accept only home network subscribers or those in ACL\n")
+	"Accept all IMSIs (DANGEROUS)\n"
+	"Accept only home network subscribers or those in the ACL\n"
+	"Accept only subscribers in the ACL\n")
 {
-	if (!strcmp(argv[0], "accept-all"))
-		g_cfg->acl_enabled = 0;
-	else
-		g_cfg->acl_enabled = 1;
+	int val = get_string_value(sgsn_auth_pol_strs, argv[0]);
+	OSMO_ASSERT(val >= SGSN_AUTH_POLICY_OPEN && val <= SGSN_AUTH_POLICY_ACL_ONLY);
+	g_cfg->auth_policy = val;
 
 	return CMD_SUCCESS;
 }
diff --git a/openbsc/tests/sgsn/sgsn_test.c b/openbsc/tests/sgsn/sgsn_test.c
index 218092f..00259a5 100644
--- a/openbsc/tests/sgsn/sgsn_test.c
+++ b/openbsc/tests/sgsn/sgsn_test.c
@@ -41,7 +41,7 @@
 	.config_file = "osmo_sgsn.cfg",
 	.cfg = {
 		.gtp_statedir = "./",
-		.acl_enabled = 1,
+		.auth_policy = SGSN_AUTH_POLICY_CLOSED,
 	},
 };
 struct sgsn_instance *sgsn = &sgsn_inst;
diff --git a/openbsc/tests/vty_test_runner.py b/openbsc/tests/vty_test_runner.py
index 82c0871..468d415 100644
--- a/openbsc/tests/vty_test_runner.py
+++ b/openbsc/tests/vty_test_runner.py
@@ -722,6 +722,22 @@
         res = self.vty.command("show llc")
         self.assert_(res.find('State of LLC Entities') >= 0)
 
+    def testVtyAuth(self):
+        self.vty.enable()
+        self.assertTrue(self.vty.verify('configure terminal', ['']))
+        self.assertEquals(self.vty.node(), 'config')
+        self.assertTrue(self.vty.verify('sgsn', ['']))
+        self.assertEquals(self.vty.node(), 'config-sgsn')
+        self.assertTrue(self.vty.verify('auth-policy accept-all', ['']))
+        res = self.vty.command("show running-config")
+        self.assert_(res.find('auth-policy accept-all') > 0)
+        self.assertTrue(self.vty.verify('auth-policy acl-only', ['']))
+        res = self.vty.command("show running-config")
+        self.assert_(res.find('auth-policy acl-only') > 0)
+        self.assertTrue(self.vty.verify('auth-policy closed', ['']))
+        res = self.vty.command("show running-config")
+        self.assert_(res.find('auth-policy closed') > 0)
+
 def add_nat_test(suite, workdir):
     if not os.path.isfile(os.path.join(workdir, "src/osmo-bsc_nat/osmo-bsc_nat")):
         print("Skipping the NAT test")