/* OsmoHLR Control Interface implementation */ /* (C) 2017 sysmocom s.f.m.c. GmbH * All Rights Reserved * * Author: Max Suraev * * 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 . * */ #include #include #include #include #include #include #include #include #include #include #define SEL_BY "by-" #define SEL_BY_IMSI SEL_BY "imsi-" #define SEL_BY_MSISDN SEL_BY "msisdn-" #define SEL_BY_ID SEL_BY "id-" extern bool auth_algo_parse(const char *alg_str, enum osmo_auth_algo *algo, int *minlen, int *maxlen); #define hexdump_buf(buf) osmo_hexdump_nospc((void*)buf, sizeof(buf)) static bool startswith(const char *str, const char *start) { return strncmp(str, start, strlen(start)) == 0; } static int _get_subscriber(struct db_context *dbc, const char *by_selector, struct hlr_subscriber *subscr) { const char *val; if (startswith(by_selector, SEL_BY_IMSI)) { val = by_selector + strlen(SEL_BY_IMSI); if (!osmo_imsi_str_valid(val)) return -EINVAL; return db_subscr_get_by_imsi(dbc, val, subscr); } if (startswith(by_selector, SEL_BY_MSISDN)) { val = by_selector + strlen(SEL_BY_MSISDN); if (!osmo_msisdn_str_valid(val)) return -EINVAL; return db_subscr_get_by_msisdn(dbc, val, subscr); } if (startswith(by_selector, SEL_BY_ID)) { int64_t id; char *endptr; val = by_selector + strlen(SEL_BY_ID); if (*val == '+') return -EINVAL; errno = 0; id = strtoll(val, &endptr, 10); if (errno || *endptr) return -EINVAL; return db_subscr_get_by_id(dbc, id, subscr); } return -ENOTSUP; } static bool get_subscriber(struct db_context *dbc, const char *by_selector, struct hlr_subscriber *subscr, struct ctrl_cmd *cmd) { int rc = _get_subscriber(dbc, by_selector, subscr); switch (rc) { case 0: return true; case -ENOTSUP: cmd->reply = "Not a known subscriber 'by-xxx-' selector."; return false; case -EINVAL: cmd->reply = "Invalid value part of 'by-xxx-value' selector."; return false; case -ENOENT: cmd->reply = "No such subscriber."; return false; default: cmd->reply = "An unknown error has occurred during get_subscriber()."; return false; } } /* Optimization: if a subscriber operation is requested by-imsi, just return * the IMSI right back. */ static const char *get_subscriber_imsi(struct db_context *dbc, const char *by_selector, struct ctrl_cmd *cmd) { static struct hlr_subscriber subscr; if (startswith(by_selector, SEL_BY_IMSI)) return by_selector + strlen(SEL_BY_IMSI); if (!get_subscriber(dbc, by_selector, &subscr, cmd)) return NULL; return subscr.imsi; } /* printf fmt and arg to completely omit a string if it is empty. */ #define FMT_S "%s%s%s%s" #define ARG_S(name, val) \ (val) && *(val) ? "\n" : "", \ (val) && *(val) ? name : "", \ (val) && *(val) ? "\t" : "", \ (val) && *(val) ? (val) : "" \ /* printf fmt and arg to completely omit bool of given value. */ #define FMT_BOOL "%s" #define ARG_BOOL(name, val) \ val ? "\n" name "\t1" : "\n" name "\t0" static void print_subscr_info(struct ctrl_cmd *cmd, struct hlr_subscriber *subscr) { ctrl_cmd_reply_printf(cmd, "\nid\t%" PRIu64 FMT_S FMT_S FMT_BOOL FMT_BOOL FMT_S FMT_S FMT_S FMT_BOOL FMT_BOOL "\nperiodic_lu_timer\t%u" "\nperiodic_rau_tau_timer\t%u" "\nlmsi\t%08x" , subscr->id, ARG_S("imsi", subscr->imsi), ARG_S("msisdn", subscr->msisdn), ARG_BOOL("nam_cs", subscr->nam_cs), ARG_BOOL("nam_ps", subscr->nam_ps), ARG_S("vlr_number", subscr->vlr_number), ARG_S("sgsn_number", subscr->sgsn_number), ARG_S("sgsn_address", subscr->sgsn_address), ARG_BOOL("ms_purged_cs", subscr->ms_purged_cs), ARG_BOOL("ms_purged_ps", subscr->ms_purged_ps), subscr->periodic_lu_timer, subscr->periodic_rau_tau_timer, subscr->lmsi ); } static void print_subscr_info_aud2g(struct ctrl_cmd *cmd, struct osmo_sub_auth_data *aud) { if (aud->algo == OSMO_AUTH_ALG_NONE) return; ctrl_cmd_reply_printf(cmd, "\naud2g.algo\t%s" "\naud2g.ki\t%s" , osmo_auth_alg_name(aud->algo), hexdump_buf(aud->u.gsm.ki)); } static void print_subscr_info_aud3g(struct ctrl_cmd *cmd, struct osmo_sub_auth_data *aud) { if (aud->algo == OSMO_AUTH_ALG_NONE) return; ctrl_cmd_reply_printf(cmd, "\naud3g.algo\t%s" "\naud3g.k\t%s" , osmo_auth_alg_name(aud->algo), hexdump_buf(aud->u.umts.k)); /* hexdump uses a static string buffer, hence only one hexdump per * printf(). */ ctrl_cmd_reply_printf(cmd, "\naud3g.%s\t%s" "\naud3g.ind_bitlen\t%u" "\naud3g.sqn\t%" PRIu64 , aud->u.umts.opc_is_op? "op" : "opc", hexdump_buf(aud->u.umts.opc), aud->u.umts.ind_bitlen, aud->u.umts.sqn); } CTRL_CMD_DEFINE_WO_NOVRF(subscr_create, "create"); static int set_subscr_create(struct ctrl_cmd *cmd, void *data) { struct hlr_subscriber subscr; struct hlr *hlr = data; const char *imsi = cmd->value; int rc; if (!osmo_imsi_str_valid(imsi)) { cmd->reply = "Invalid IMSI value."; return CTRL_CMD_ERROR; } /* Create the subscriber in the DB */ rc = db_subscr_create(g_hlr->dbc, imsi, DB_SUBSCR_FLAG_NAM_CS | DB_SUBSCR_FLAG_NAM_PS); if (rc) { if (rc == -EEXIST) cmd->reply = "Subscriber already exists."; else cmd->reply = "Cannot create subscriber."; return CTRL_CMD_ERROR; } LOGP(DCTRL, LOGL_INFO, "Created subscriber IMSI='%s'\n", imsi); /* Retrieve data of newly created subscriber: */ rc = db_subscr_get_by_imsi(hlr->dbc, imsi, &subscr); if (rc < 0) { cmd->reply = "Failed retrieving ID of newly created subscriber."; return CTRL_CMD_ERROR; } cmd->reply = talloc_asprintf(cmd, "%" PRIu64, subscr.id); return CTRL_CMD_REPLY; } CTRL_CMD_DEFINE_WO_NOVRF(subscr_delete, "delete"); static int set_subscr_delete(struct ctrl_cmd *cmd, void *data) { struct hlr_subscriber subscr; struct hlr *hlr = data; const char *imsi = cmd->value; int rc; if (!osmo_imsi_str_valid(imsi)) { cmd->reply = "Invalid IMSI value."; return CTRL_CMD_ERROR; } /* Retrieve data of newly created subscriber: */ rc = db_subscr_get_by_imsi(hlr->dbc, imsi, &subscr); if (rc < 0) { cmd->reply = "Subscriber doesn't exist."; return CTRL_CMD_ERROR; } /* Create the subscriber in the DB */ rc = db_subscr_delete_by_id(g_hlr->dbc, subscr.id); if (rc) { cmd->reply = "Cannot delete subscriber."; return CTRL_CMD_ERROR; } LOGP(DCTRL, LOGL_INFO, "Deleted subscriber IMSI='%s'\n", imsi); cmd->reply = talloc_asprintf(cmd, "%" PRIu64, subscr.id); return CTRL_CMD_REPLY; } CTRL_CMD_DEFINE_RO(subscr_info, "info"); static int get_subscr_info(struct ctrl_cmd *cmd, void *data) { struct hlr_subscriber subscr; struct hlr *hlr = data; const char *by_selector = cmd->node; if (!get_subscriber(hlr->dbc, by_selector, &subscr, cmd)) return CTRL_CMD_ERROR; print_subscr_info(cmd, &subscr); return CTRL_CMD_REPLY; } CTRL_CMD_DEFINE_RO(subscr_info_aud, "info-aud"); static int get_subscr_info_aud(struct ctrl_cmd *cmd, void *data) { const char *imsi; struct osmo_sub_auth_data aud2g; struct osmo_sub_auth_data aud3g; struct hlr *hlr = data; const char *by_selector = cmd->node; int rc; imsi = get_subscriber_imsi(hlr->dbc, by_selector, cmd); if (!imsi) return CTRL_CMD_ERROR; rc = db_get_auth_data(hlr->dbc, imsi, &aud2g, &aud3g, NULL); switch (rc) { case 0: break; case -ENOENT: case -ENOKEY: /* No auth data found, tell the print*() functions about it. */ aud2g.algo = OSMO_AUTH_ALG_NONE; aud3g.algo = OSMO_AUTH_ALG_NONE; break; default: cmd->reply = "Error retrieving authentication data."; return CTRL_CMD_ERROR; } print_subscr_info_aud2g(cmd, &aud2g); print_subscr_info_aud3g(cmd, &aud3g); return CTRL_CMD_REPLY; } CTRL_CMD_DEFINE_RO(subscr_info_all, "info-all"); static int get_subscr_info_all(struct ctrl_cmd *cmd, void *data) { struct hlr_subscriber subscr; struct osmo_sub_auth_data aud2g; struct osmo_sub_auth_data aud3g; struct hlr *hlr = data; const char *by_selector = cmd->node; 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, &aud3g, NULL); switch (rc) { case 0: break; case -ENOENT: case -ENOKEY: /* No auth data found, tell the print*() functions about it. */ aud2g.algo = OSMO_AUTH_ALG_NONE; aud3g.algo = OSMO_AUTH_ALG_NONE; break; default: cmd->reply = "Error retrieving authentication data."; return CTRL_CMD_ERROR; } print_subscr_info(cmd, &subscr); print_subscr_info_aud2g(cmd, &aud2g); print_subscr_info_aud3g(cmd, &aud3g); return CTRL_CMD_REPLY; } static int verify_subscr_cs_ps_enabled(struct ctrl_cmd *cmd, const char *value, void *data) { if (!value || !*value || (strcmp(value, "0") && strcmp(value, "1"))) return 1; return 0; } static int get_subscr_cs_ps_enabled(struct ctrl_cmd *cmd, void *data, bool is_ps) { struct hlr_subscriber subscr; struct hlr *hlr = data; const char *by_selector = cmd->node; if (!get_subscriber(hlr->dbc, by_selector, &subscr, cmd)) return CTRL_CMD_ERROR; cmd->reply = (is_ps ? subscr.nam_ps : subscr.nam_cs) ? "1" : "0"; return CTRL_CMD_REPLY; } static int set_subscr_cs_ps_enabled(struct ctrl_cmd *cmd, void *data, bool is_ps) { const char *imsi; struct hlr *hlr = data; const char *by_selector = cmd->node; imsi = get_subscriber_imsi(hlr->dbc, by_selector, cmd); if (!imsi) return CTRL_CMD_ERROR; if (db_subscr_nam(hlr->dbc, imsi, strcmp(cmd->value, "1") == 0, is_ps)) return CTRL_CMD_ERROR; cmd->reply = "OK"; return CTRL_CMD_REPLY; } CTRL_CMD_DEFINE(subscr_ps_enabled, "ps-enabled"); static int verify_subscr_ps_enabled(struct ctrl_cmd *cmd, const char *value, void *data) { return verify_subscr_cs_ps_enabled(cmd, value, data); } static int get_subscr_ps_enabled(struct ctrl_cmd *cmd, void *data) { return get_subscr_cs_ps_enabled(cmd, data, true); } static int set_subscr_ps_enabled(struct ctrl_cmd *cmd, void *data) { return set_subscr_cs_ps_enabled(cmd, data, true); } CTRL_CMD_DEFINE(subscr_cs_enabled, "cs-enabled"); static int verify_subscr_cs_enabled(struct ctrl_cmd *cmd, const char *value, void *data) { return verify_subscr_cs_ps_enabled(cmd, value, data); } static int get_subscr_cs_enabled(struct ctrl_cmd *cmd, void *data) { return get_subscr_cs_ps_enabled(cmd, data, false); } static int set_subscr_cs_enabled(struct ctrl_cmd *cmd, void *data) { return set_subscr_cs_ps_enabled(cmd, data, false); } CTRL_CMD_DEFINE(subscr_msisdn, "msisdn"); static int verify_subscr_msisdn(struct ctrl_cmd *cmd, const char *value, void *data) { struct hlr_subscriber subscr; if (!value) return 1; if (strlen(value) > sizeof(subscr.msisdn) - 1) return 1; if (strcmp(value, "none") != 0 && !osmo_msisdn_str_valid(value)) return 1; return 0; } static int get_subscr_msisdn(struct ctrl_cmd *cmd, void *data) { struct hlr_subscriber subscr; struct hlr *hlr = data; const char *by_selector = cmd->node; if (!get_subscriber(hlr->dbc, by_selector, &subscr, cmd)) return CTRL_CMD_ERROR; if (strlen(subscr.msisdn) == 0) snprintf(subscr.msisdn, sizeof(subscr.msisdn), "none"); cmd->reply = talloc_asprintf(cmd, "%s", subscr.msisdn); return CTRL_CMD_REPLY; } static int set_subscr_msisdn(struct ctrl_cmd *cmd, void *data) { struct hlr_subscriber subscr; struct hlr *hlr = data; const char *by_selector = cmd->node; const char *msisdn; if (!get_subscriber(hlr->dbc, by_selector, &subscr, cmd)) return CTRL_CMD_ERROR; if (strcmp(cmd->value, "none") == 0) msisdn = NULL; else msisdn = cmd->value; if (db_subscr_update_msisdn_by_imsi(g_hlr->dbc, subscr.imsi, msisdn)) { cmd->reply = "Update MSISDN failed"; return CTRL_CMD_ERROR; } cmd->reply = "OK"; return CTRL_CMD_REPLY; } /* value format: */ CTRL_CMD_DEFINE(subscr_aud2g, "aud2g"); static int verify_subscr_aud2g(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_aud2g(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; struct osmo_sub_auth_data aud3g_unused; 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, &aud3g_unused, NULL); switch (rc) { case 0: break; case -ENOENT: case -ENOKEY: aud2g.algo = OSMO_AUTH_ALG_NONE; break; default: cmd->reply = "Error retrieving data from database."; return CTRL_CMD_ERROR; } if (aud2g.algo == OSMO_AUTH_ALG_NONE) { cmd->reply = "none"; return CTRL_CMD_REPLY; } cmd->reply = talloc_asprintf(cmd, "%s,%s", osmo_auth_alg_name(aud2g.algo), hexdump_buf(aud2g.u.gsm.ki)); return CTRL_CMD_REPLY; } static int set_subscr_aud2g(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 aud2g = { .type = OSMO_AUTH_TYPE_GSM }; 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) { aud2g.algo = OSMO_AUTH_ALG_NONE; } else if (!auth_algo_parse(tok, &aud2g.algo, &minlen, &maxlen)) { cmd->reply = "Unknown auth algorithm."; return CTRL_CMD_ERROR; } if (aud2g.algo != OSMO_AUTH_ALG_NONE) { tok = strtok_r(NULL, "\0", &saveptr); if (!tok) { cmd->reply = "Invalid format."; return CTRL_CMD_ERROR; } aud2g.u.gsm.ki = tok; if (!osmo_is_hexstr(aud2g.u.gsm.ki, minlen * 2, maxlen * 2, true)) { cmd->reply = "Invalid KI."; return CTRL_CMD_ERROR; } } if (db_subscr_update_aud_by_id(g_hlr->dbc, subscr.id, &aud2g)) { cmd->reply = "Update aud2g failed."; return CTRL_CMD_ERROR; } cmd->reply = "OK"; return CTRL_CMD_REPLY; } /* value format: */ 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) { const char *token = vector_slot(vline, *i); switch (*node_type) { case CTRL_NODE_ROOT: if (strcmp(token, "subscriber") != 0) return 0; *node_data = NULL; *node_type = CTRL_NODE_SUBSCR; break; case CTRL_NODE_SUBSCR: if (!startswith(token, "by-")) return 0; *node_data = (void*)token; *node_type = CTRL_NODE_SUBSCR_BY; break; default: return 0; } return 1; } static int hlr_ctrl_cmds_install(void) { int rc = 0; rc |= ctrl_cmd_install(CTRL_NODE_SUBSCR, &cmd_subscr_create); rc |= ctrl_cmd_install(CTRL_NODE_SUBSCR, &cmd_subscr_delete); rc |= ctrl_cmd_install(CTRL_NODE_SUBSCR_BY, &cmd_subscr_info); rc |= ctrl_cmd_install(CTRL_NODE_SUBSCR_BY, &cmd_subscr_info_aud); rc |= ctrl_cmd_install(CTRL_NODE_SUBSCR_BY, &cmd_subscr_info_all); rc |= ctrl_cmd_install(CTRL_NODE_SUBSCR_BY, &cmd_subscr_ps_enabled); 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; } struct ctrl_handle *hlr_controlif_setup(struct hlr *hlr) { int rc; struct ctrl_handle *hdl = ctrl_interface_setup2(hlr, OSMO_CTRL_PORT_HLR, hlr_ctrl_node_lookup, _LAST_CTRL_NODE_HLR); if (!hdl) return NULL; rc = hlr_ctrl_cmds_install(); if (rc) /* FIXME: close control interface? */ return NULL; return hdl; }