sgsn: Add functions to handle APN contexts

This commit adds the exported functions apn_ctx_find_alloc,
apn_ctx_free, apn_ctx_by_name, and apn_ctx_match to manage and
retrieve APN to GGSN mappings.

The following VTY commands are added to 'config-sgsn':

 - apn APN ggsn <0-255>
 - apn APN imsi-prefix PREFIX ggsn <0-255>

which maps an APN gateway string to an SGSN id. The SGSN must be
configured in advance. When matching an APN string, entries with a
leading '*' are used for suffix matching, otherwise an exact match is
done.  When a prefix is given, it is matched against the IMSI. If
several entries match, a longer matching IMSI prefix has precedence.
If there are several matching entries with the same PREFIX, the entry
with longest matching APN is returned.

Ticket: OW#1334
Sponsored-by: On-Waves ehf
diff --git a/openbsc/src/gprs/gprs_sgsn.c b/openbsc/src/gprs/gprs_sgsn.c
index 555be57..54fe15c 100644
--- a/openbsc/src/gprs/gprs_sgsn.c
+++ b/openbsc/src/gprs/gprs_sgsn.c
@@ -387,41 +387,100 @@
 
 /* APN contexts */
 
-#if 0
-struct apn_ctx *apn_ctx_alloc(const char *ap_name)
+static struct apn_ctx *sgsn_apn_ctx_alloc(const char *ap_name, const char *imsi_prefix)
 {
 	struct apn_ctx *actx;
 
-	actx = talloc_zero(talloc_bsc_ctx, struct apn_ctx);
+	actx = talloc_zero(tall_bsc_ctx, struct apn_ctx);
 	if (!actx)
 		return NULL;
 	actx->name = talloc_strdup(actx, ap_name);
+	actx->imsi_prefix = talloc_strdup(actx, imsi_prefix);
+
+	llist_add_tail(&actx->list, &sgsn_apn_ctxts);
 
 	return actx;
 }
 
-struct apn_ctx *apn_ctx_by_name(const char *name)
+void sgsn_apn_ctx_free(struct apn_ctx *actx)
+{
+	llist_del(&actx->list);
+	talloc_free(actx);
+}
+
+struct apn_ctx *sgsn_apn_ctx_match(const char *name, const char *imsi)
+{
+	struct apn_ctx *actx;
+	struct apn_ctx *found_actx = NULL;
+	size_t imsi_prio = 0;
+	size_t name_prio = 0;
+	size_t name_req_len = strlen(name);
+
+	llist_for_each_entry(actx, &sgsn_apn_ctxts, list) {
+		size_t name_ref_len, imsi_ref_len;
+		const char *name_ref_start, *name_match_start;
+
+		imsi_ref_len = strlen(actx->imsi_prefix);
+		if (strncmp(actx->imsi_prefix, imsi, imsi_ref_len) != 0)
+			continue;
+
+		if (imsi_ref_len < imsi_prio)
+			continue;
+
+		/* IMSI matches */
+
+		name_ref_start = &actx->name[0];
+		if (name_ref_start[0] == '*') {
+			/* Suffix match */
+			name_ref_start += 1;
+			name_ref_len = strlen(name_ref_start);
+			if (name_ref_len > name_req_len)
+				continue;
+		} else {
+			name_ref_len = strlen(name_ref_start);
+			if (name_ref_len != name_req_len)
+				continue;
+		}
+
+		name_match_start = name + (name_req_len - name_ref_len);
+		if (strcasecmp(name_match_start, name_ref_start) != 0)
+			continue;
+
+		/* IMSI and name match */
+
+		if (imsi_ref_len == imsi_prio && name_ref_len < name_prio)
+			/* Lower priority, skip */
+			continue;
+
+		imsi_prio = imsi_ref_len;
+		name_prio = name_ref_len;
+		found_actx = actx;
+	}
+	return found_actx;
+}
+
+struct apn_ctx *sgsn_apn_ctx_by_name(const char *name, const char *imsi_prefix)
 {
 	struct apn_ctx *actx;
 
 	llist_for_each_entry(actx, &sgsn_apn_ctxts, list) {
-		if (!strcmp(name, actx->name))
+		if (strcasecmp(name, actx->name) == 0 &&
+		    strcasecmp(imsi_prefix, actx->imsi_prefix) == 0)
 			return actx;
 	}
 	return NULL;
 }
 
-struct apn_ctx *apn_ctx_find_alloc(const char *name)
+struct apn_ctx *sgsn_apn_ctx_find_alloc(const char *name, const char *imsi_prefix)
 {
 	struct apn_ctx *actx;
 
-	actx = apn_ctx_by_name(name);
+	actx = sgsn_apn_ctx_by_name(name, imsi_prefix);
 	if (!actx)
-		actx = apn_ctx_alloc(name);
+		actx = sgsn_apn_ctx_alloc(name, imsi_prefix);
 
 	return actx;
 }
-#endif
 
 uint32_t sgsn_alloc_ptmsi(void)
 {