ctrl: Extend ctrl command to optionally handle alg+ki

Extend the existing ctrl command to be able to specify the
algorithm and Ki. In contrast to the VTY no size check is
done. Together with the VTY this code only supports a small
part of what is supported by libosmocore.

The algorithm and ki are considered optional but if a valid
algorithm other than "none" is passed, a KI must be passed as
well.

Extend the test coverage by passing the potential values. It
is not verified that the KI/algorithm is stored.
diff --git a/openbsc/src/libmsc/ctrl_commands.c b/openbsc/src/libmsc/ctrl_commands.c
index e48c6a3..9ac39de 100644
--- a/openbsc/src/libmsc/ctrl_commands.c
+++ b/openbsc/src/libmsc/ctrl_commands.c
@@ -24,9 +24,25 @@
 #include <openbsc/db.h>
 #include <openbsc/debug.h>
 
+static bool alg_supported(const char *alg)
+{
+	/*
+	 * TODO: share this with the vty_interface and extend to all
+	 * algorithms supported by libosmocore now. Make it table based
+	 * as well.
+	 */
+	if (strcasecmp(alg, "none") == 0)
+		return true;
+	if (strcasecmp(alg, "xor") == 0)
+		return true;
+	if (strcasecmp(alg, "comp128v1") == 0)
+		return true;
+	return false;
+}
+
 static int verify_subscriber_modify(struct ctrl_cmd *cmd, const char *value, void *d)
 {
-	char *tmp, *imsi, *msisdn, *saveptr = NULL;
+	char *tmp, *imsi, *msisdn, *alg, *ki, *saveptr = NULL;
 	int rc = 0;
 
 	tmp = talloc_strdup(cmd, value);
@@ -35,6 +51,8 @@
 
 	imsi = strtok_r(tmp, ",", &saveptr);
 	msisdn = strtok_r(NULL, ",", &saveptr);
+	alg = strtok_r(NULL, ",", &saveptr);
+	ki = strtok_r(NULL, ",", &saveptr);
 
 	if (!imsi || !msisdn)
 		rc = 1;
@@ -42,6 +60,12 @@
 		rc = 1;
 	else if (strlen(msisdn) >= GSM_EXTENSION_LENGTH)
 		rc = 1;
+	else if (alg) {
+		if (!alg_supported(alg))
+			rc = 1;
+		else if (strcasecmp(alg, "none") != 0 && !ki)
+			rc = 1;
+	}
 
 	talloc_free(tmp);
 	return rc;
@@ -56,7 +80,7 @@
 static int set_subscriber_modify(struct ctrl_cmd *cmd, void *data)
 {
 	struct gsm_network *net = cmd->node;
-	char *tmp, *imsi, *msisdn, *saveptr = NULL;
+	char *tmp, *imsi, *msisdn, *alg, *ki, *saveptr = NULL;
 	struct gsm_subscriber* subscr;
 	int rc;
 
@@ -66,6 +90,8 @@
 
 	imsi = strtok_r(tmp, ",", &saveptr);
 	msisdn = strtok_r(NULL, ",", &saveptr);
+	alg = strtok_r(NULL, ",", &saveptr);
+	ki = strtok_r(NULL, ",", &saveptr);
 
 	subscr = subscr_get_by_imsi(net->subscr_group, imsi);
 	if (!subscr)
@@ -80,6 +106,36 @@
 	/* put it back to the db */
 	rc = db_sync_subscriber(subscr);
 	db_subscriber_update(subscr);
+
+	/* handle optional ciphering */
+	if (alg) {
+		if (strcasecmp(alg, "none") == 0)
+			db_sync_authinfo_for_subscr(NULL, subscr);
+		else {
+			struct gsm_auth_info ainfo = { 0, };
+			/* the verify should make sure that this is okay */
+			OSMO_ASSERT(alg);
+			OSMO_ASSERT(ki);
+
+			if (strcasecmp(alg, "xor") == 0)
+				ainfo.auth_algo = AUTH_ALGO_XOR;
+			else if (strcasecmp(alg, "comp128v1") == 0)
+				ainfo.auth_algo = AUTH_ALGO_COMP128v1;
+
+			rc = osmo_hexparse(ki, ainfo.a3a8_ki, sizeof(ainfo.a3a8_ki));
+			if (rc < 0) {
+				subscr_put(subscr);
+				talloc_free(tmp);
+				cmd->reply = "Failed to parse KI";
+				return CTRL_CMD_ERROR;
+			}
+
+			ainfo.a3a8_ki_len = rc;
+			db_sync_authinfo_for_subscr(&ainfo, subscr);
+			rc = 0;
+		}
+		db_sync_lastauthtuple_for_subscr(NULL, subscr);
+	}
 	subscr_put(subscr);
 
 	talloc_free(tmp);
diff --git a/openbsc/tests/ctrl_test_runner.py b/openbsc/tests/ctrl_test_runner.py
index 21850e3..7a12643 100644
--- a/openbsc/tests/ctrl_test_runner.py
+++ b/openbsc/tests/ctrl_test_runner.py
@@ -469,6 +469,33 @@
         self.assertEquals(r['var'], 'number-of-bts')
         self.assertEquals(r['value'], '1')
 
+    def testSubscriberAddWithKi(self):
+        """Test that we can set the algorithm to none, xor, comp128v1"""
+
+        r = self.do_set('subscriber-modify-v1', '2620345,445566')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'subscriber-modify-v1')
+        self.assertEquals(r['value'], 'OK')
+
+        r = self.do_set('subscriber-modify-v1', '2620345,445566,none')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'subscriber-modify-v1')
+        self.assertEquals(r['value'], 'OK')
+
+        r = self.do_set('subscriber-modify-v1', '2620345,445566,xor')
+        self.assertEquals(r['mtype'], 'ERROR')
+        self.assertEquals(r['error'], 'Value failed verification.')
+
+        r = self.do_set('subscriber-modify-v1', '2620345,445566,comp128v1,00112233445566778899AABBCCDDEEFF')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'subscriber-modify-v1')
+        self.assertEquals(r['value'], 'OK')
+
+        r = self.do_set('subscriber-modify-v1', '2620345,445566,none')
+        self.assertEquals(r['mtype'], 'SET_REPLY')
+        self.assertEquals(r['var'], 'subscriber-modify-v1')
+        self.assertEquals(r['value'], 'OK')
+
     def testSubscriberAddRemove(self):
         r = self.do_set('subscriber-modify-v1', '2620345,445566')
         self.assertEquals(r['mtype'], 'SET_REPLY')