nat: Allow to have a regexp to match the MSISDN

The idea that MCC and MNC is enough to classify a subscriber
turns out to be wrong. Certain operatos license a number range
of IMSIs to others. When we see a '^' in the MCC field we treat
it as a regexp. The code now turns the MCC/MNC into a regexp
for the IMSI. It is not using extended POSIX regexp to match
the behavior of the access list.
diff --git a/openbsc/include/openbsc/bsc_nat.h b/openbsc/include/openbsc/bsc_nat.h
index c3f32eb..650d113 100644
--- a/openbsc/include/openbsc/bsc_nat.h
+++ b/openbsc/include/openbsc/bsc_nat.h
@@ -267,7 +267,7 @@
 
 	/* number rewriting */
 	char *num_rewr_name;
-	struct osmo_config_list *num_rewr;
+	struct llist_head num_rewr;
 
 	/* USSD messages  we want to match */
 	char *ussd_lst_name;
@@ -394,4 +394,18 @@
 void bsc_nat_paging_group_add_lac(struct bsc_nat_paging_group *grp, int lac);
 void bsc_nat_paging_group_del_lac(struct bsc_nat_paging_group *grp, int lac);
 
+/**
+ * Number rewriting support below
+ */
+struct bsc_nat_num_rewr_entry {
+	struct llist_head list;
+
+	regex_t msisdn_reg;
+	regex_t num_reg;
+
+	char *replace;
+};
+
+void bsc_nat_num_rewr_entry_adapt(struct bsc_nat *nat, const struct osmo_config_list *);
+
 #endif
diff --git a/openbsc/src/osmo-bsc_nat/bsc_nat_utils.c b/openbsc/src/osmo-bsc_nat/bsc_nat_utils.c
index 76b56f6..a7b0044 100644
--- a/openbsc/src/osmo-bsc_nat/bsc_nat_utils.c
+++ b/openbsc/src/osmo-bsc_nat/bsc_nat_utils.c
@@ -94,6 +94,7 @@
 	INIT_LLIST_HEAD(&nat->bsc_configs);
 	INIT_LLIST_HEAD(&nat->access_lists);
 	INIT_LLIST_HEAD(&nat->dests);
+	INIT_LLIST_HEAD(&nat->num_rewr);
 
 	nat->stats.sccp.conn = osmo_counter_alloc("nat.sccp.conn");
 	nat->stats.sccp.calls = osmo_counter_alloc("nat.sccp.calls");
@@ -801,10 +802,10 @@
 static char *rewrite_non_international(struct bsc_nat *nat, void *ctx, const char *imsi,
 				       struct gsm_mncc_number *called)
 {
-	struct osmo_config_entry *entry;
+	struct bsc_nat_num_rewr_entry *entry;
 	char *new_number = NULL;
 
-	if (!nat->num_rewr)
+	if (llist_empty(&nat->num_rewr))
 		return NULL;
 
 	if (called->plan != 1)
@@ -813,36 +814,19 @@
 		return NULL;
 
 	/* need to find a replacement and then fix it */
-	llist_for_each_entry(entry, &nat->num_rewr->entry, list) {
-		regex_t reg;
+	llist_for_each_entry(entry, &nat->num_rewr, list) {
 		regmatch_t matches[2];
 
-		if (entry->mcc[0] != '*' && strncmp(entry->mcc, imsi, 3) != 0)
+		/* check the IMSI match */
+		if (regexec(&entry->msisdn_reg, imsi, 0, NULL, 0) != 0)
 			continue;
-		if (entry->mnc[0] != '*' && strncmp(entry->mnc, imsi + 3, 2) != 0)
-			continue;
-
-		if (entry->text[0] == '+') {
-			LOGP(DNAT, LOGL_ERROR,
-				"Plus is not allowed in the number");
-			continue;
-		}
-
-		/* We have an entry for the IMSI. Need to match now */
-		if (regcomp(&reg, entry->option, REG_EXTENDED) != 0) {
-			LOGP(DNAT, LOGL_ERROR,
-				"Regexp '%s' is not valid.\n", entry->option);
-			continue;
-		}
 
 		/* this regexp matches... */
-		if (regexec(&reg, called->number, 2, matches, 0) == 0 &&
+		if (regexec(&entry->num_reg, called->number, 2, matches, 0) == 0 &&
 		    matches[1].rm_eo != -1)
 			new_number = talloc_asprintf(ctx, "%s%s",
-					entry->text,
+					entry->replace,
 					&called->number[matches[1].rm_so]);
-		regfree(&reg);
-
 		if (new_number)
 			break;
 	}
@@ -973,3 +957,87 @@
 	return sccp;
 }
 
+static void num_rewr_free_data(struct bsc_nat_num_rewr_entry *entry)
+{
+	regfree(&entry->msisdn_reg);
+	regfree(&entry->num_reg);
+	talloc_free(entry->replace);
+}
+
+void bsc_nat_num_rewr_entry_adapt(struct bsc_nat *nat, const struct osmo_config_list *list)
+{
+	struct bsc_nat_num_rewr_entry *entry, *tmp;
+	struct osmo_config_entry *cfg_entry;
+
+	/* free the old data */
+	llist_for_each_entry_safe(entry, tmp, &nat->num_rewr, list) {
+		num_rewr_free_data(entry);
+		llist_del(&entry->list);
+		talloc_free(entry);
+	}
+
+
+	if (!list)
+		return;
+
+	llist_for_each_entry(cfg_entry, &list->entry, list) {
+		char *regexp;
+		if (cfg_entry->text[0] == '+') {
+			LOGP(DNAT, LOGL_ERROR,
+				"Plus is not allowed in the number\n");
+			continue;
+		}
+
+		entry = talloc_zero(nat, struct bsc_nat_num_rewr_entry);
+		if (!entry) {
+			LOGP(DNAT, LOGL_ERROR,
+				"Allication of the num_rewr entry failed.\n");
+			continue;
+		}
+
+		entry->replace = talloc_strdup(entry, cfg_entry->text);
+		if (!entry->replace) {
+			LOGP(DNAT, LOGL_ERROR,
+				"Failed to copy the replacement text.\n");
+			talloc_free(entry);
+			continue;
+		}
+
+		/* we will now build a regexp string */
+		if (cfg_entry->mcc[0] == '^') {
+			regexp = talloc_strdup(entry, cfg_entry->mcc);
+		} else {
+			regexp = talloc_asprintf(entry, "^%s%s",
+					cfg_entry->mcc[0] == '*' ?
+						"[0-9][0-9][0-9]" : cfg_entry->mcc,
+					cfg_entry->mnc[0] == '*' ?
+						"[0-9][0-9]" : cfg_entry->mnc);
+		}
+
+		if (!regexp) {
+			LOGP(DNAT, LOGL_ERROR, "Failed to create a regexp string.\n");
+			talloc_free(entry);
+			continue;
+		}
+
+		if (regcomp(&entry->msisdn_reg, regexp, 0) != 0) {
+			LOGP(DNAT, LOGL_ERROR,
+				"Failed to compile regexp '%s'\n", regexp);
+			talloc_free(regexp);
+			talloc_free(entry);
+			continue;
+		}
+
+		talloc_free(regexp);
+		if (regcomp(&entry->num_reg, cfg_entry->option, REG_EXTENDED) != 0) {
+			LOGP(DNAT, LOGL_ERROR,
+				"Failed to compile regexp '%s\n'", cfg_entry->option);
+			regfree(&entry->msisdn_reg);
+			talloc_free(entry);
+			continue;
+		}
+
+		/* we have copied the number */
+		llist_add_tail(&entry->list, &nat->num_rewr);
+	}
+}
diff --git a/openbsc/src/osmo-bsc_nat/bsc_nat_vty.c b/openbsc/src/osmo-bsc_nat/bsc_nat_vty.c
index 0375cf8..38b5a09 100644
--- a/openbsc/src/osmo-bsc_nat/bsc_nat_vty.c
+++ b/openbsc/src/osmo-bsc_nat/bsc_nat_vty.c
@@ -461,16 +461,16 @@
       "number-rewrite FILENAME",
       "Set the file with rewriting rules.\n" "Filename")
 {
+	struct osmo_config_list *rewr = NULL;
+
 	bsc_replace_string(_nat, &_nat->num_rewr_name, argv[0]);
 	if (_nat->num_rewr_name) {
-		if (_nat->num_rewr)
-			talloc_free(_nat->num_rewr);
-		_nat->num_rewr = osmo_config_list_parse(_nat, _nat->num_rewr_name);
-		return _nat->num_rewr == NULL ? CMD_WARNING : CMD_SUCCESS;
+		rewr = osmo_config_list_parse(_nat, _nat->num_rewr_name);
+		bsc_nat_num_rewr_entry_adapt(_nat, rewr);
+		talloc_free(rewr);
+		return CMD_SUCCESS;
 	} else {
-		if (_nat->num_rewr)
-			talloc_free(_nat->num_rewr);
-		_nat->num_rewr = NULL;
+		bsc_nat_num_rewr_entry_adapt(_nat, NULL);
 		return CMD_SUCCESS;
 	}
 }
diff --git a/openbsc/tests/bsc-nat/bsc_nat_test.c b/openbsc/tests/bsc-nat/bsc_nat_test.c
index 1645f70..32f4f72 100644
--- a/openbsc/tests/bsc-nat/bsc_nat_test.c
+++ b/openbsc/tests/bsc-nat/bsc_nat_test.c
@@ -854,7 +854,7 @@
 	entry.option = "^0([1-9])";
 	entry.text = "0049";
 	llist_add_tail(&entry.list, &entries.entry);
-	nat->num_rewr = &entries;
+	bsc_nat_num_rewr_entry_adapt(nat, &entries);
 
 	/* verify that nothing changed */
 	msgb_reset(msg);
@@ -917,6 +917,7 @@
 
 	/* Make sure that a wildcard is matching */
 	entry.mnc = "*";
+	bsc_nat_num_rewr_entry_adapt(nat, &entries);
 	msg = msgb_alloc(4096, "test_dt_filter");
 	copy_to_msg(msg, cc_setup_national, ARRAY_SIZE(cc_setup_national));
 	parsed = bsc_nat_parse(msg);
@@ -951,6 +952,7 @@
 
 	/* Make sure that a wildcard is matching */
 	entry.mnc = "09";
+	bsc_nat_num_rewr_entry_adapt(nat, &entries);
 	msg = msgb_alloc(4096, "test_dt_filter");
 	copy_to_msg(msg, cc_setup_national, ARRAY_SIZE(cc_setup_national));
 	parsed = bsc_nat_parse(msg);