ctrl: Introduce CTRL command subscriber.by-*.aud3g <algo[,KI,(op|opc),OP_C[,ind_bitlen]]>

This command provides getter and setter to set and retrieve the
authentication data for 3g subscribers.

Change-Id: Ibe7aeec3cabab0406eb7a84ecd24e529ef1696c2
Related: SYS#5993
diff --git a/include/osmocom/hlr/hlr_vty.h b/include/osmocom/hlr/hlr_vty.h
index c026d91..10eddc3 100644
--- a/include/osmocom/hlr/hlr_vty.h
+++ b/include/osmocom/hlr/hlr_vty.h
@@ -37,6 +37,12 @@
 	MSLOOKUP_CLIENT_NODE,
 };
 
+
+#define A38_XOR_MIN_KEY_LEN	12
+#define A38_XOR_MAX_KEY_LEN	16
+#define A38_COMP128_KEY_LEN	16
+#define MILENAGE_KEY_LEN	16
+
 int hlr_vty_is_config_node(struct vty *vty, int node);
 int hlr_vty_go_parent(struct vty *vty);
 void hlr_vty_init(void);
diff --git a/src/ctrl.c b/src/ctrl.c
index c543559..f101d25 100644
--- a/src/ctrl.c
+++ b/src/ctrl.c
@@ -31,6 +31,7 @@
 #include <osmocom/hlr/hlr.h>
 #include <osmocom/hlr/ctrl.h>
 #include <osmocom/hlr/db.h>
+#include <osmocom/hlr/hlr_vty.h>
 
 #define SEL_BY "by-"
 #define SEL_BY_IMSI SEL_BY "imsi-"
@@ -576,6 +577,153 @@
 	return CTRL_CMD_REPLY;
 }
 
+/* value format: <algo[,KI,(op|opc),OP_C[,ind_bitlen]]> */
+CTRL_CMD_DEFINE(subscr_aud3g, "aud3g");
+static int verify_subscr_aud3g(struct ctrl_cmd *cmd, const char *value, void *data)
+{
+	if (!value)
+		return 1;
+	if (strcasecmp(value, "none") != 0 && !strchr(value, ','))
+		return 1;
+	return 0;
+}
+static int get_subscr_aud3g(struct ctrl_cmd *cmd, void *data)
+{
+	struct hlr_subscriber subscr;
+	struct hlr *hlr = data;
+	const char *by_selector = cmd->node;
+	struct osmo_sub_auth_data aud2g_unused;
+	struct osmo_sub_auth_data aud3g;
+	int rc;
+
+	if (!get_subscriber(hlr->dbc, by_selector, &subscr, cmd))
+		return CTRL_CMD_ERROR;
+
+	rc = db_get_auth_data(hlr->dbc, subscr.imsi, &aud2g_unused, &aud3g, NULL);
+	switch (rc) {
+	case 0:
+		break;
+	case -ENOENT:
+	case -ENOKEY:
+		aud3g.algo = OSMO_AUTH_ALG_NONE;
+		break;
+	default:
+		cmd->reply = "Error retrieving data from database.";
+		return CTRL_CMD_ERROR;
+	}
+
+	if (aud3g.algo ==  OSMO_AUTH_ALG_NONE) {
+		cmd->reply = "none";
+		return CTRL_CMD_REPLY;
+	}
+
+	cmd->reply = talloc_asprintf(cmd, "%s,%s,%s,%s,%u", osmo_auth_alg_name(aud3g.algo),
+				     osmo_hexdump_nospc_c(cmd, aud3g.u.umts.k, sizeof(aud3g.u.umts.k)),
+				     aud3g.u.umts.opc_is_op ? "OP" : "OPC",
+				     osmo_hexdump_nospc_c(cmd, aud3g.u.umts.opc, sizeof(aud3g.u.umts.opc)),
+				     aud3g.u.umts.ind_bitlen);
+	return CTRL_CMD_REPLY;
+}
+static int set_subscr_aud3g(struct ctrl_cmd *cmd, void *data)
+{
+	struct hlr_subscriber subscr;
+	struct hlr *hlr = data;
+	const char *by_selector = cmd->node;
+	char *tmp = NULL, *tok, *saveptr;
+	int minlen = 0;
+	int maxlen = 0;
+	struct sub_auth_data_str aud3g = {
+		.type = OSMO_AUTH_TYPE_UMTS,
+		.u.umts = {
+			.ind_bitlen = 5,
+		},
+	};
+	bool ind_bitlen_present;
+
+	if (!get_subscriber(hlr->dbc, by_selector, &subscr, cmd))
+		return CTRL_CMD_ERROR;
+
+	tmp = talloc_strdup(cmd, cmd->value);
+	if (!tmp) {
+		cmd->reply = "OOM";
+		return CTRL_CMD_ERROR;
+	}
+
+	/* Parse alg_type: */
+	tok = strtok_r(tmp, ",", &saveptr);
+	if (!tok) {
+		cmd->reply = "Invalid format.";
+		return CTRL_CMD_ERROR;
+	}
+	if (strcmp(tok, "none") == 0) {
+		aud3g.algo = OSMO_AUTH_ALG_NONE;
+	} else if (!auth_algo_parse(tok, &aud3g.algo, &minlen, &maxlen)) {
+		cmd->reply = "Unknown auth algorithm.";
+		return CTRL_CMD_ERROR;
+	}
+
+	if (aud3g.algo != OSMO_AUTH_ALG_NONE) {
+		/* Parse K */
+		tok = strtok_r(NULL, ",", &saveptr);
+		if (!tok) {
+			cmd->reply = "Invalid format.";
+			return CTRL_CMD_ERROR;
+		}
+		aud3g.u.umts.k = tok;
+		if (!osmo_is_hexstr(aud3g.u.umts.k, minlen * 2, maxlen * 2, true)) {
+			cmd->reply = "Invalid KI.";
+			return CTRL_CMD_ERROR;
+		}
+
+		/* Parse OP/OPC choice */
+		tok = strtok_r(NULL, ",", &saveptr);
+		if (!tok) {
+			cmd->reply = "Invalid format.";
+			return CTRL_CMD_ERROR;
+		}
+		if (strcasecmp(tok, "op") == 0) {
+			aud3g.u.umts.opc_is_op = true;
+		} else if (strcasecmp(tok, "opc") == 0) {
+			aud3g.u.umts.opc_is_op = false;
+		} else {
+			cmd->reply = "Invalid format.";
+			return CTRL_CMD_ERROR;
+		}
+
+		/* Parse OP/OPC value */
+		ind_bitlen_present = !!strchr(saveptr, ',');
+		tok = strtok_r(NULL, ind_bitlen_present ? "," : "\0", &saveptr);
+		if (!tok) {
+			cmd->reply = "Invalid format.";
+			return CTRL_CMD_ERROR;
+		}
+
+		aud3g.u.umts.opc = tok;
+		if (!osmo_is_hexstr(aud3g.u.umts.opc, MILENAGE_KEY_LEN * 2, MILENAGE_KEY_LEN * 2, true)) {
+			cmd->reply = talloc_asprintf(cmd, "Invalid OP/OPC.");
+			return CTRL_CMD_ERROR;
+		}
+
+		if (ind_bitlen_present) {
+			/* Parse bitlen_ind */
+			tok = strtok_r(NULL, "\0", &saveptr);
+			if (!tok || tok[0] == '\0') {
+				cmd->reply = "Invalid format.";
+				return CTRL_CMD_ERROR;
+			}
+			aud3g.u.umts.ind_bitlen = atoi(tok);
+		}
+	}
+
+	if (db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud3g)) {
+		cmd->reply = "Update aud3g failed.";
+		return CTRL_CMD_ERROR;
+	}
+
+	cmd->reply = "OK";
+	return CTRL_CMD_REPLY;
+}
+
 static int hlr_ctrl_node_lookup(void *data, vector vline, int *node_type,
 				void **node_data, int *i)
 {
@@ -615,6 +763,7 @@
 	rc |= ctrl_cmd_install(CTRL_NODE_SUBSCR_BY, &cmd_subscr_cs_enabled);
 	rc |= ctrl_cmd_install(CTRL_NODE_SUBSCR_BY, &cmd_subscr_msisdn);
 	rc |= ctrl_cmd_install(CTRL_NODE_SUBSCR_BY, &cmd_subscr_aud2g);
+	rc |= ctrl_cmd_install(CTRL_NODE_SUBSCR_BY, &cmd_subscr_aud3g);
 
 	return rc;
 }
diff --git a/src/hlr_vty_subscr.c b/src/hlr_vty_subscr.c
index a9f461d..c851062 100644
--- a/src/hlr_vty_subscr.c
+++ b/src/hlr_vty_subscr.c
@@ -31,6 +31,7 @@
 #include <osmocom/hlr/hlr.h>
 #include <osmocom/hlr/db.h>
 #include <osmocom/hlr/timestamp.h>
+#include <osmocom/hlr/hlr_vty.h>
 
 struct vty;
 
@@ -470,12 +471,6 @@
 #define AUTH_ALG_TYPES_3G_HELP \
 	"Use Milenage algorithm\n"
 
-#define A38_XOR_MIN_KEY_LEN	12
-#define A38_XOR_MAX_KEY_LEN	16
-#define A38_COMP128_KEY_LEN	16
-
-#define MILENAGE_KEY_LEN 16
-
 bool auth_algo_parse(const char *alg_str, enum osmo_auth_algo *algo,
 		     int *minlen, int *maxlen)
 {
diff --git a/tests/test_subscriber.ctrl b/tests/test_subscriber.ctrl
index dadad35..ff26d4c 100644
--- a/tests/test_subscriber.ctrl
+++ b/tests/test_subscriber.ctrl
@@ -686,5 +686,26 @@
 GET 116 subscriber.by-imsi-901991234567891.aud2g
 GET_REPLY 116 subscriber.by-imsi-901991234567891.aud2g none
 
-SET 117 subscriber.delete 901991234567891
-SET_REPLY 117 subscriber.delete 124
+GET 117 subscriber.by-imsi-901991234567891.aud3g
+GET_REPLY 117 subscriber.by-imsi-901991234567891.aud3g none
+
+SET 118 subscriber.by-imsi-901991234567891.aud3g milenage,c01ffedc1cadaeac1d1f1edacac1ab0a,OP,FB2A3D1B360F599ABAB99DB8669F8308
+SET_REPLY 118 subscriber.by-imsi-901991234567891.aud3g OK
+
+GET 119 subscriber.by-imsi-901991234567891.aud3g
+GET_REPLY 119 subscriber.by-imsi-901991234567891.aud3g MILENAGE,c01ffedc1cadaeac1d1f1edacac1ab0a,OP,fb2a3d1b360f599abab99db8669f8308,5
+
+SET 120 subscriber.by-imsi-901991234567891.aud3g milenage,c01ffedc1cadaeac1d1f1edacac1ab0a,OPC,FB2A3D1B360F599ABAB99DB8669F8308,7
+SET_REPLY 120 subscriber.by-imsi-901991234567891.aud3g OK
+
+GET 121 subscriber.by-imsi-901991234567891.aud3g
+GET_REPLY 121 subscriber.by-imsi-901991234567891.aud3g MILENAGE,c01ffedc1cadaeac1d1f1edacac1ab0a,OPC,fb2a3d1b360f599abab99db8669f8308,7
+
+SET 122 subscriber.by-imsi-901991234567891.aud3g none
+SET_REPLY 122 subscriber.by-imsi-901991234567891.aud3g OK
+
+GET 123 subscriber.by-imsi-901991234567891.aud3g
+GET_REPLY 123 subscriber.by-imsi-901991234567891.aud3g none
+
+SET 124 subscriber.delete 901991234567891
+SET_REPLY 124 subscriber.delete 124
diff --git a/tests/test_subscriber_errors.ctrl b/tests/test_subscriber_errors.ctrl
index ac9eec7..6b1ade6 100644
--- a/tests/test_subscriber_errors.ctrl
+++ b/tests/test_subscriber_errors.ctrl
@@ -132,3 +132,24 @@
 
 SET 56 subscriber.by-imsi-901990000000003.aud2g xor,2134
 ERROR 56 Invalid KI.
+
+SET 57 subscriber.by-imsi-901990000000003.aud3g foobar
+ERROR 57 Value failed verification.
+
+SET 58 subscriber.by-imsi-901990000000003.aud3g foobar,2134
+ERROR 58 Unknown auth algorithm.
+
+SET 60 subscriber.by-imsi-901990000000003.aud3g milenage,2134
+ERROR 60 Invalid KI.
+
+SET 61 subscriber.by-imsi-901990000000003.aud3g milenage,c01ffedc1cadaeac1d1f1edacac1ab0a,AAA
+ERROR 61 Invalid format.
+
+SET 62 subscriber.by-imsi-901990000000003.aud3g milenage,c01ffedc1cadaeac1d1f1edacac1ab0a,OPC
+ERROR 62 Invalid format.
+
+SET 63 subscriber.by-imsi-901990000000003.aud3g milenage,c01ffedc1cadaeac1d1f1edacac1ab0a,OPC,zzz
+ERROR 63 Invalid OP/OPC.
+
+SET 64 subscriber.by-imsi-901990000000003.aud3g milenage,c01ffedc1cadaeac1d1f1edacac1ab0a,OPC,fb2a3d1b360f599abab99db8669f8308,
+ERROR 64 Invalid format.