Merge branch 'zecke/features/extended-control'
diff --git a/openbsc/doc/examples/osmo-bsc_nat/osmo-bsc_nat.cfg b/openbsc/doc/examples/osmo-bsc_nat/osmo-bsc_nat.cfg
index 78eb4ba..737d104 100644
--- a/openbsc/doc/examples/osmo-bsc_nat/osmo-bsc_nat.cfg
+++ b/openbsc/doc/examples/osmo-bsc_nat/osmo-bsc_nat.cfg
@@ -62,6 +62,7 @@
  timeout ping 20
  timeout pong 5
  ip-dscp 0
+ access-list bla imsi-allow ^11$
 
  bsc 0
   token bla
diff --git a/openbsc/include/openbsc/gsm_data.h b/openbsc/include/openbsc/gsm_data.h
index 404dfe4..1b4720f 100644
--- a/openbsc/include/openbsc/gsm_data.h
+++ b/openbsc/include/openbsc/gsm_data.h
@@ -433,4 +433,7 @@
 extern const struct value_string bts_type_names[_NUM_GSM_BTS_TYPE+1];
 extern const struct value_string bts_type_descs[_NUM_GSM_BTS_TYPE+1];
 
+/* control interface handling */
+int bsc_base_ctrl_cmds_install(void);
+
 #endif /* _GSM_DATA_H */
diff --git a/openbsc/src/libbsc/Makefile.am b/openbsc/src/libbsc/Makefile.am
index 42fabab..5330091 100644
--- a/openbsc/src/libbsc/Makefile.am
+++ b/openbsc/src/libbsc/Makefile.am
@@ -22,5 +22,5 @@
 			bsc_api.c bsc_msc.c bsc_vty.c \
 			gsm_04_08_utils.c \
 			bsc_init.c bts_init.c bsc_rf_ctrl.c \
-			arfcn_range_encode.c
+			arfcn_range_encode.c bsc_ctrl_commands.c
 
diff --git a/openbsc/src/libbsc/bsc_ctrl_commands.c b/openbsc/src/libbsc/bsc_ctrl_commands.c
new file mode 100644
index 0000000..a137efa
--- /dev/null
+++ b/openbsc/src/libbsc/bsc_ctrl_commands.c
@@ -0,0 +1,165 @@
+/*
+ * (C) 2013 by Holger Hans Peter Freyther
+ * (C) 2013 by sysmocom s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <openbsc/control_cmd.h>
+#include <openbsc/ipaccess.h>
+#include <openbsc/gsm_data.h>
+
+#define CTRL_CMD_VTY_STRING(cmdname, cmdstr, dtype, element) \
+	CTRL_HELPER_GET_STRING(cmdname, dtype, element) \
+	CTRL_HELPER_SET_STRING(cmdname, dtype, element) \
+static struct ctrl_cmd_element cmd_##cmdname = { \
+	.name = cmdstr, \
+	.param = NULL, \
+	.get = get_##cmdname, \
+	.set = set_##cmdname, \
+	.verify = verify_vty_description_string, \
+}
+
+/**
+ * Check that there are no newlines or comments or other things
+ * that could make the VTY configuration unparsable.
+ */
+static int verify_vty_description_string(struct ctrl_cmd *cmd,
+			const char *value, void *data)
+{
+	int i;
+	const size_t len = strlen(value);
+
+	for (i = 0; i < len; ++i) {
+		switch(value[i]) {
+		case '#':
+		case '\n':
+		case '\r':
+			cmd->reply = "String includes illegal character";
+			return -1;
+		default:
+			break;
+		}
+	}
+
+	return 0;
+}
+
+CTRL_CMD_DEFINE_RANGE(net_mnc, "mnc", struct gsm_network, network_code, 0, 999);
+CTRL_CMD_DEFINE_RANGE(net_mcc, "mcc", struct gsm_network, country_code, 1, 999);
+CTRL_CMD_VTY_STRING(net_short_name, "short-name", struct gsm_network, name_short);
+CTRL_CMD_VTY_STRING(net_long_name, "long-name", struct gsm_network, name_long);
+
+static int verify_net_apply_config(struct ctrl_cmd *cmd, const char *v, void *d)
+{
+	return 0;
+}
+
+static int get_net_apply_config(struct ctrl_cmd *cmd, void *data)
+{
+	cmd->reply = "Write only attribute";
+	return CTRL_CMD_ERROR;
+}
+
+static int set_net_apply_config(struct ctrl_cmd *cmd, void *data)
+{
+	struct gsm_network *net = cmd->node;
+	struct gsm_bts *bts;
+
+	llist_for_each_entry(bts, &net->bts_list, list) {
+		if (!is_ipaccess_bts(bts))
+			continue;
+
+		ipaccess_drop_oml(bts);
+	}
+
+	cmd->reply = "Tried to drop the BTS";
+	return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE(net_apply_config, "apply-configuration");
+
+static int verify_net_mcc_mnc_apply(struct ctrl_cmd *cmd, const char *value, void *d)
+{
+	char *tmp, *saveptr, *mcc, *mnc;
+
+	tmp = talloc_strdup(cmd, value);
+	if (!tmp)
+		return 1;
+
+	mcc = strtok_r(tmp, ",", &saveptr);
+	mnc = strtok_r(NULL, ",", &saveptr);
+	talloc_free(tmp);
+
+	if (!mcc || !mnc)
+		return 1;
+	return 0;
+}
+
+static int get_net_mcc_mnc_apply(struct ctrl_cmd *cmd, void *data)
+{
+	cmd->reply = "Write only attribute";
+	return CTRL_CMD_ERROR;
+}
+
+static int set_net_mcc_mnc_apply(struct ctrl_cmd *cmd, void *data)
+{
+	struct gsm_network *net = cmd->node;
+	char *tmp, *saveptr, *mcc_str, *mnc_str;
+	int mcc, mnc;
+
+	tmp = talloc_strdup(cmd, cmd->value);
+	if (!tmp)
+		goto oom;
+
+
+	mcc_str = strtok_r(tmp, ",", &saveptr);
+	mnc_str = strtok_r(NULL, ",", &saveptr);
+
+	mcc = atoi(mcc_str);
+	mnc = atoi(mnc_str);
+
+	talloc_free(tmp);
+
+	if (net->network_code == mnc && net->country_code == mcc) {
+		cmd->reply = "Nothing changed";
+		return CTRL_CMD_REPLY;
+	}
+
+	net->network_code = mnc;
+	net->country_code = mcc;
+
+	return set_net_apply_config(cmd, data);
+
+oom:
+	cmd->reply = "OOM";
+	return CTRL_CMD_ERROR;
+}
+CTRL_CMD_DEFINE(net_mcc_mnc_apply, "mcc-mnc-apply");
+
+int bsc_base_ctrl_cmds_install(void)
+{
+	int rc = 0;
+	rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mnc);
+	rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mcc);
+	rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_short_name);
+	rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_long_name);
+	rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_apply_config);
+	rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mcc_mnc_apply);
+
+	return rc;
+}
diff --git a/openbsc/src/osmo-bsc/osmo_bsc_ctrl.c b/openbsc/src/osmo-bsc/osmo_bsc_ctrl.c
index 1d0e2aa..e32218d 100644
--- a/openbsc/src/osmo-bsc/osmo_bsc_ctrl.c
+++ b/openbsc/src/osmo-bsc/osmo_bsc_ctrl.c
@@ -208,7 +208,11 @@
 	admin = osmo_bsc_rf_get_adminstate_name(osmo_bsc_rf_get_adminstate_by_bts(bts));
 	policy = osmo_bsc_rf_get_policy_name(osmo_bsc_rf_get_policy_by_bts(bts));
 
-	cmd->reply = talloc_asprintf_append(cmd->reply, ",%s,%s,%s", oper, admin, policy);
+	cmd->reply = talloc_asprintf_append(cmd->reply,
+				",%s,%s,%s,%d,%d",
+				oper, admin, policy,
+				bts->network->country_code,
+				bts->network->network_code);
 
 	osmo_bsc_send_trap(cmd, msc_con);
 	talloc_free(cmd);
@@ -605,6 +609,9 @@
 {
 	int rc;
 
+	rc = bsc_base_ctrl_cmds_install();
+	if (rc)
+		goto end;
 	rc = ctrl_cmd_install(CTRL_NODE_BTS, &cmd_bts_rf_state);
 	if (rc)
 		goto end;
diff --git a/openbsc/src/osmo-bsc_nat/bsc_nat_ctrl.c b/openbsc/src/osmo-bsc_nat/bsc_nat_ctrl.c
index 6ff4541..2836a19 100644
--- a/openbsc/src/osmo-bsc_nat/bsc_nat_ctrl.c
+++ b/openbsc/src/osmo-bsc_nat/bsc_nat_ctrl.c
@@ -186,6 +186,26 @@
 	}
 }
 
+static int extract_bsc_nr_variable(char *variable, unsigned int *nr, char **bsc_variable)
+{
+	char *nr_str, *tmp, *saveptr = NULL;
+
+	tmp = strtok_r(variable, ".", &saveptr);
+	tmp = strtok_r(NULL, ".", &saveptr);
+	tmp = strtok_r(NULL, ".", &saveptr);
+	nr_str = strtok_r(NULL, ".", &saveptr);
+	if (!nr_str)
+		return 0;
+	*nr = atoi(nr_str);
+
+	tmp = strtok_r(NULL, "\0", &saveptr);
+	if (!tmp)
+		return 0;
+
+	*bsc_variable = tmp;
+	return 1;
+}
+
 static int forward_to_bsc(struct ctrl_cmd *cmd)
 {
 	int ret = CTRL_CMD_HANDLED;
@@ -193,24 +213,14 @@
 	struct bsc_connection *bsc;
 	struct bsc_cmd_list *pending;
 	unsigned int nr;
-	char *nr_str, *tmp, *saveptr = NULL;
+	char *bsc_variable;
 
 	/* Skip over the beginning (bsc.) */
-	tmp = strtok_r(cmd->variable, ".", &saveptr);
-	tmp = strtok_r(NULL, ".", &saveptr);
-	tmp = strtok_r(NULL, ".", &saveptr);
-	nr_str = strtok_r(NULL, ".", &saveptr);
-	if (!nr_str) {
+	if (!extract_bsc_nr_variable(cmd->variable, &nr, &bsc_variable)) {
 		cmd->reply = "command incomplete";
 		goto err;
 	}
-	nr = atoi(nr_str);
 
-	tmp = strtok_r(NULL, "\0", &saveptr);
-	if (!tmp) {
-		cmd->reply = "command incomplete";
-		goto err;
-	}
 
 	llist_for_each_entry(bsc, &g_nat->bsc_connections, list_entry) {
 		if (!bsc->cfg)
@@ -245,7 +255,7 @@
 			}
 
 			talloc_free(bsc_cmd->variable);
-			bsc_cmd->variable = talloc_strdup(bsc_cmd, tmp);
+			bsc_cmd->variable = talloc_strdup(bsc_cmd, bsc_variable);
 			if (!bsc_cmd->variable) {
 				cmd->reply = "OOM";
 				goto err;
@@ -274,8 +284,7 @@
 err:
 	ret = CTRL_CMD_ERROR;
 done:
-	if (bsc_cmd)
-		talloc_free(bsc_cmd);
+	talloc_free(bsc_cmd);
 	return ret;
 
 }
@@ -297,6 +306,74 @@
 	return 0;
 }
 
+static int extract_bsc_cfg_variable(struct ctrl_cmd *cmd, struct bsc_config **cfg,
+				char **bsc_variable)
+{
+	unsigned int nr;
+
+	if (!extract_bsc_nr_variable(cmd->variable, &nr, bsc_variable)) {
+		cmd->reply = "command incomplete";
+		return 0;
+	}
+
+	*cfg = bsc_config_num(g_nat, nr);
+	if (!*cfg) {
+		cmd->reply = "Unknown BSC";
+		return 0;
+	}
+
+	return 1;
+}
+
+CTRL_CMD_DEFINE(net_cfg_cmd, "net 0 bsc_cfg *");
+static int get_net_cfg_cmd(struct ctrl_cmd *cmd, void *data)
+{
+	char *bsc_variable;
+	struct bsc_config *bsc_cfg;
+
+	if (!extract_bsc_cfg_variable(cmd, &bsc_cfg, &bsc_variable))
+		return CTRL_CMD_ERROR;
+
+	if (strcmp(bsc_variable, "access-list-name") == 0) {
+		cmd->reply = talloc_asprintf(cmd, "%s",
+				bsc_cfg->acc_lst_name ? bsc_cfg->acc_lst_name : "");
+		return CTRL_CMD_REPLY;
+	}
+
+	cmd->reply = "unknown command";
+	return CTRL_CMD_ERROR;
+}
+
+static int set_net_cfg_cmd(struct ctrl_cmd *cmd, void *data)
+{
+	char *bsc_variable;
+	struct bsc_config *bsc_cfg;
+
+	if (!extract_bsc_cfg_variable(cmd, &bsc_cfg, &bsc_variable))
+		return CTRL_CMD_ERROR;
+
+	if (strcmp(bsc_variable, "access-list-name") == 0) {
+		bsc_replace_string(bsc_cfg, &bsc_cfg->acc_lst_name, cmd->value);
+		cmd->reply = talloc_asprintf(cmd, "%s",
+				bsc_cfg->acc_lst_name ? bsc_cfg->acc_lst_name : "");
+		return CTRL_CMD_REPLY;
+	} else if (strcmp(bsc_variable, "no-access-list-name") == 0) {
+		talloc_free(bsc_cfg->acc_lst_name);
+		bsc_cfg->acc_lst_name = NULL;
+		cmd->reply = "";
+		return CTRL_CMD_REPLY;
+	}
+
+	cmd->reply = "unknown command";
+	return CTRL_CMD_ERROR;
+}
+
+static int verify_net_cfg_cmd(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+	return 0;
+}
+
+
 struct ctrl_handle *bsc_nat_controlif_setup(struct bsc_nat *nat, int port)
 {
 	struct ctrl_handle *ctrl;
@@ -312,13 +389,21 @@
 	rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_fwd_cmd);
 	if (rc) {
 		fprintf(stderr, "Failed to install the control command. Exiting.\n");
-		osmo_fd_unregister(&ctrl->listen_fd);
-		close(ctrl->listen_fd.fd);
-		talloc_free(ctrl);
-		return NULL;
+		goto error;
+	}
+	rc = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_cfg_cmd);
+	if (rc) {
+		fprintf(stderr, "Failed to install the net cfg command. Exiting.\n");
+		goto error;
 	}
 
 	g_nat = nat;
 	return ctrl;
+
+error:
+	osmo_fd_unregister(&ctrl->listen_fd);
+	close(ctrl->listen_fd.fd);
+	talloc_free(ctrl);
+	return NULL;
 }
 
diff --git a/openbsc/src/osmo-nitb/bsc_hack.c b/openbsc/src/osmo-nitb/bsc_hack.c
index 96ae0b8..d746bf1 100644
--- a/openbsc/src/osmo-nitb/bsc_hack.c
+++ b/openbsc/src/osmo-nitb/bsc_hack.c
@@ -290,6 +290,11 @@
 		return -1;
 	}
 
+	if (bsc_base_ctrl_cmds_install() != 0) {
+		printf("Failed to initialize the control commands. Exiting.\n");
+		return -1;
+	}
+
 	/* seed the PRNG */
 	srand(time(NULL));
 
diff --git a/openbsc/tests/ctrl_test_runner.py b/openbsc/tests/ctrl_test_runner.py
index 48d0734..22f378d 100644
--- a/openbsc/tests/ctrl_test_runner.py
+++ b/openbsc/tests/ctrl_test_runner.py
@@ -1,6 +1,7 @@
 #!/usr/bin/env python
 
 # (C) 2013 by Jacob Erlbeck <jerlbeck@sysmocom.de>
+# (C) 2014 by Holger Hans Peter Freyther
 # based on vty_test_runner.py:
 # (C) 2013 by Katerina Barone-Adesi <kat.obsc@gmail.com>
 # (C) 2013 by Holger Hans Peter Freyther
@@ -131,8 +132,12 @@
             if mtype == "ERROR":
                 rsp['error'] = msg
             else:
-                [rsp['var'], rsp['value']]  = msg.split(None, 2)
-
+                split = msg.split(None, 1)
+                rsp['var'] = split[0]
+                if len(split) > 1:
+                    rsp['value'] = split[1]
+                else:
+                    rsp['value'] = None
             responses[id] = rsp
 
         if verbose:
@@ -239,6 +244,83 @@
         self.assertEquals(r['var'], 'bts.0.timezone')
         self.assertEquals(r['value'], 'off')
 
+    def testMccMncApply(self):
+        # Test some invalid input
+        r = self.do_set('mcc-mnc-apply', 'WRONG')
+        self.assertEquals(r['mtype'], 'ERROR')
+
+        r = self.do_set('mcc-mnc-apply', '1,')
+        self.assertEquals(r['mtype'], 'ERROR')
+
+        r = self.do_set('mcc-mnc-apply', '200,3')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'mcc-mnc-apply')
+        self.assertEquals(r['value'], 'Tried to drop the BTS')
+
+        # Set it again
+        r = self.do_set('mcc-mnc-apply', '200,3')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'mcc-mnc-apply')
+        self.assertEquals(r['value'], 'Nothing changed')
+
+        # Change it
+        r = self.do_set('mcc-mnc-apply', '200,4')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'mcc-mnc-apply')
+        self.assertEquals(r['value'], 'Tried to drop the BTS')
+
+        # Change it
+        r = self.do_set('mcc-mnc-apply', '201,4')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'mcc-mnc-apply')
+        self.assertEquals(r['value'], 'Tried to drop the BTS')
+
+        # Verify
+        r = self.do_get('mnc')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'mnc')
+        self.assertEquals(r['value'], '4')
+
+        r = self.do_get('mcc')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'mcc')
+        self.assertEquals(r['value'], '201')
+
+class TestCtrlNAT(TestCtrlBase):
+
+    def ctrl_command(self):
+        return ["./src/osmo-bsc_nat/osmo-bsc_nat", "-c",
+                "doc/examples/osmo-bsc_nat/osmo-bsc_nat.cfg"]
+
+    def ctrl_app(self):
+        return (4250, "./src/osmo-bsc_nat/osmo-bsc_nat", "OsmoNAT", "nat")
+
+    def testAccessList(self):
+        r = self.do_get('net.0.bsc_cfg.0.access-list-name')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'net')
+        self.assertEquals(r['value'], None)
+
+        r = self.do_set('net.0.bsc_cfg.0.access-list-name', 'bla')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'net')
+        self.assertEquals(r['value'], 'bla')
+
+        r = self.do_get('net.0.bsc_cfg.0.access-list-name')
+        self.assertEquals(r['mtype'], 'GET_REPLY')
+        self.assertEquals(r['var'], 'net')
+        self.assertEquals(r['value'], 'bla')
+
+        r = self.do_set('net.0.bsc_cfg.0.no-access-list-name', '1')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'net')
+        self.assertEquals(r['value'], None)
+
+        r = self.do_set('net.0.bsc_cfg.0.no-access-list-name', '1')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'net')
+        self.assertEquals(r['value'], None)
+
 def add_bsc_test(suite, workdir):
     if not os.path.isfile(os.path.join(workdir, "src/osmo-bsc/osmo-bsc")):
         print("Skipping the BSC test")
@@ -246,6 +328,13 @@
     test = unittest.TestLoader().loadTestsFromTestCase(TestCtrlBSC)
     suite.addTest(test)
 
+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")
+        return
+    test = unittest.TestLoader().loadTestsFromTestCase(TestCtrlNAT)
+    suite.addTest(test)
+
 if __name__ == '__main__':
     import argparse
     import sys
@@ -277,5 +366,6 @@
     print "Running tests for specific control commands"
     suite = unittest.TestSuite()
     add_bsc_test(suite, workdir)
+    add_nat_test(suite, workdir)
     res = unittest.TextTestRunner(verbosity=verbose_level).run(suite)
     sys.exit(len(res.errors) + len(res.failures))
diff --git a/openbsc/tests/vty_test_runner.py b/openbsc/tests/vty_test_runner.py
index 092f5ae..7c8fe8c 100644
--- a/openbsc/tests/vty_test_runner.py
+++ b/openbsc/tests/vty_test_runner.py
@@ -525,7 +525,7 @@
         res = self.vty.command("show running-config").split("\r\n")
         asserted = False
         for line in res:
-           if line.startswith(" access-list"):
+           if line.startswith(" access-list test-default"):
                 self.assertEqual(line, " access-list test-default imsi-deny ^123[0-9]*$ 11 11")
                 asserted = True
         self.assert_(asserted)