/* * Copyright (C) 2015-2017 Tobias Brunner * HSR Hochschule fuer Technik Rapperswil * * Copyright (C) 2014 Martin Willi * Copyright (C) 2014 revosec AG * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * 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 General Public License * for more details. */ #include "vici_control.h" #include "vici_builder.h" #include #include #include #include #include #include typedef struct private_vici_control_t private_vici_control_t; /** * Private data of an vici_control_t object. */ struct private_vici_control_t { /** * Public vici_control_t interface. */ vici_control_t public; /** * Dispatcher */ vici_dispatcher_t *dispatcher; }; /** * Log callback helper data */ typedef struct { /** dispatcher to send log messages over */ vici_dispatcher_t *dispatcher; /** connection ID to send messages to */ u_int id; /** loglevel */ level_t level; /** prevent recursive log */ u_int recursive; } log_info_t; /** * Log using vici event messages */ static bool log_vici(log_info_t *info, debug_t group, level_t level, ike_sa_t *ike_sa, char *text) { if (level <= info->level) { if (info->recursive++ == 0) { vici_message_t *message; vici_builder_t *builder; builder = vici_builder_create(); builder->add_kv(builder, "group", "%N", debug_names, group); builder->add_kv(builder, "level", "%d", level); if (ike_sa) { builder->add_kv(builder, "ikesa-name", "%s", ike_sa->get_name(ike_sa)); builder->add_kv(builder, "ikesa-uniqueid", "%u", ike_sa->get_unique_id(ike_sa)); } builder->add_kv(builder, "msg", "%s", text); message = builder->finalize(builder); if (message) { info->dispatcher->raise_event(info->dispatcher, "control-log", info->id, message); } } info->recursive--; } return TRUE; } /** * Send a (error) reply message */ static vici_message_t* send_reply(private_vici_control_t *this, char *fmt, ...) { vici_builder_t *builder; va_list args; builder = vici_builder_create(); builder->add_kv(builder, "success", fmt ? "no" : "yes"); if (fmt) { va_start(args, fmt); builder->vadd_kv(builder, "errmsg", fmt, args); va_end(args); } return builder->finalize(builder); } /** * Get the child_cfg having name from peer_cfg */ static child_cfg_t* get_child_from_peer(peer_cfg_t *peer_cfg, char *name) { child_cfg_t *current, *found = NULL; enumerator_t *enumerator; enumerator = peer_cfg->create_child_cfg_enumerator(peer_cfg); while (enumerator->enumerate(enumerator, ¤t)) { if (streq(current->get_name(current), name)) { found = current; found->get_ref(found); break; } } enumerator->destroy(enumerator); return found; } /** * Find a peer/child config from a child config name */ static child_cfg_t* find_child_cfg(char *name, char *pname, peer_cfg_t **out) { enumerator_t *enumerator; peer_cfg_t *peer_cfg; child_cfg_t *child_cfg = NULL; enumerator = charon->backends->create_peer_cfg_enumerator( charon->backends, NULL, NULL, NULL, NULL, IKE_ANY); while (enumerator->enumerate(enumerator, &peer_cfg)) { if (pname && !streq(pname, peer_cfg->get_name(peer_cfg))) { continue; } child_cfg = get_child_from_peer(peer_cfg, name); if (child_cfg) { *out = peer_cfg->get_ref(peer_cfg); break; } } enumerator->destroy(enumerator); return child_cfg; } CALLBACK(initiate, vici_message_t*, private_vici_control_t *this, char *name, u_int id, vici_message_t *request) { child_cfg_t *child_cfg = NULL; peer_cfg_t *peer_cfg; char *child, *ike; int timeout; bool limits; controller_cb_t log_cb = NULL; log_info_t log = { .dispatcher = this->dispatcher, .id = id, }; child = request->get_str(request, NULL, "child"); ike = request->get_str(request, NULL, "ike"); timeout = request->get_int(request, 0, "timeout"); limits = request->get_bool(request, FALSE, "init-limits"); log.level = request->get_int(request, 1, "loglevel"); if (!child) { return send_reply(this, "missing configuration name"); } if (timeout >= 0) { log_cb = (controller_cb_t)log_vici; } DBG1(DBG_CFG, "vici initiate '%s'", child); child_cfg = find_child_cfg(child, ike, &peer_cfg); if (!child_cfg) { return send_reply(this, "CHILD_SA config '%s' not found", child); } switch (charon->controller->initiate(charon->controller, peer_cfg, child_cfg, log_cb, &log, timeout, limits)) { case SUCCESS: return send_reply(this, NULL); case OUT_OF_RES: return send_reply(this, "CHILD_SA '%s' not established after %dms", child, timeout); case INVALID_STATE: return send_reply(this, "establishing CHILD_SA '%s' not possible " "at the moment due to limits", child); case FAILED: default: return send_reply(this, "establishing CHILD_SA '%s' failed", child); } } CALLBACK(terminate, vici_message_t*, private_vici_control_t *this, char *name, u_int id, vici_message_t *request) { enumerator_t *enumerator, *isas, *csas; char *child, *ike, *errmsg = NULL; u_int child_id, ike_id, current, *del, done = 0; int timeout; ike_sa_t *ike_sa; child_sa_t *child_sa; array_t *ids; vici_builder_t *builder; controller_cb_t log_cb = NULL; log_info_t log = { .dispatcher = this->dispatcher, .id = id, }; child = request->get_str(request, NULL, "child"); ike = request->get_str(request, NULL, "ike"); child_id = request->get_int(request, 0, "child-id"); ike_id = request->get_int(request, 0, "ike-id"); timeout = request->get_int(request, 0, "timeout"); log.level = request->get_int(request, 1, "loglevel"); if (!child && !ike && !ike_id && !child_id) { return send_reply(this, "missing terminate selector"); } if (ike_id) { DBG1(DBG_CFG, "vici terminate IKE_SA #%d", ike_id); } if (child_id) { DBG1(DBG_CFG, "vici terminate CHILD_SA #%d", child_id); } if (ike) { DBG1(DBG_CFG, "vici terminate IKE_SA '%s'", ike); } if (child) { DBG1(DBG_CFG, "vici terminate CHILD_SA '%s'", child); } if (timeout >= 0) { log_cb = (controller_cb_t)log_vici; } ids = array_create(sizeof(u_int), 0); isas = charon->controller->create_ike_sa_enumerator(charon->controller, TRUE); while (isas->enumerate(isas, &ike_sa)) { if (child || child_id) { if (ike && !streq(ike, ike_sa->get_name(ike_sa))) { continue; } if (ike_id && ike_id != ike_sa->get_unique_id(ike_sa)) { continue; } csas = ike_sa->create_child_sa_enumerator(ike_sa); while (csas->enumerate(csas, &child_sa)) { if (child && !streq(child, child_sa->get_name(child_sa))) { continue; } if (child_id && child_sa->get_unique_id(child_sa) != child_id) { continue; } current = child_sa->get_unique_id(child_sa); array_insert(ids, ARRAY_TAIL, ¤t); } csas->destroy(csas); } else if (ike && streq(ike, ike_sa->get_name(ike_sa))) { current = ike_sa->get_unique_id(ike_sa); array_insert(ids, ARRAY_TAIL, ¤t); } else if (ike_id && ike_id == ike_sa->get_unique_id(ike_sa)) { array_insert(ids, ARRAY_TAIL, &ike_id); } } isas->destroy(isas); enumerator = array_create_enumerator(ids); while (enumerator->enumerate(enumerator, &del)) { if (child || child_id) { if (charon->controller->terminate_child(charon->controller, *del, log_cb, &log, timeout) == SUCCESS) { done++; } } else { if (charon->controller->terminate_ike(charon->controller, *del, log_cb, &log, timeout) == SUCCESS) { done++; } } } enumerator->destroy(enumerator); builder = vici_builder_create(); if (array_count(ids) == 0) { errmsg = "no matching SAs to terminate found"; } else if (done < array_count(ids)) { if (array_count(ids) == 1) { errmsg = "terminating SA failed"; } else { errmsg = "not all matching SAs could be terminated"; } } builder->add_kv(builder, "success", errmsg ? "no" : "yes"); builder->add_kv(builder, "matches", "%u", array_count(ids)); builder->add_kv(builder, "terminated", "%u", done); if (errmsg) { builder->add_kv(builder, "errmsg", "%s", errmsg); } array_destroy(ids); return builder->finalize(builder); } CALLBACK(rekey, vici_message_t*, private_vici_control_t *this, char *name, u_int id, vici_message_t *request) { enumerator_t *isas, *csas; char *child, *ike, *errmsg = NULL; u_int child_id, ike_id, found = 0; ike_sa_t *ike_sa; child_sa_t *child_sa; vici_builder_t *builder; child = request->get_str(request, NULL, "child"); ike = request->get_str(request, NULL, "ike"); child_id = request->get_int(request, 0, "child-id"); ike_id = request->get_int(request, 0, "ike-id"); if (!child && !ike && !ike_id && !child_id) { return send_reply(this, "missing rekey selector"); } if (ike_id) { DBG1(DBG_CFG, "vici rekey IKE_SA #%d", ike_id); } if (child_id) { DBG1(DBG_CFG, "vici rekey CHILD_SA #%d", child_id); } if (ike) { DBG1(DBG_CFG, "vici rekey IKE_SA '%s'", ike); } if (child) { DBG1(DBG_CFG, "vici rekey CHILD_SA '%s'", child); } isas = charon->controller->create_ike_sa_enumerator(charon->controller, TRUE); while (isas->enumerate(isas, &ike_sa)) { if (child || child_id) { if (ike && !streq(ike, ike_sa->get_name(ike_sa))) { continue; } if (ike_id && ike_id != ike_sa->get_unique_id(ike_sa)) { continue; } csas = ike_sa->create_child_sa_enumerator(ike_sa); while (csas->enumerate(csas, &child_sa)) { if (child && !streq(child, child_sa->get_name(child_sa))) { continue; } if (child_id && child_sa->get_unique_id(child_sa) != child_id) { continue; } lib->processor->queue_job(lib->processor, (job_t*)rekey_child_sa_job_create( child_sa->get_protocol(child_sa), child_sa->get_spi(child_sa, TRUE), ike_sa->get_my_host(ike_sa))); found++; } csas->destroy(csas); } else if ((ike && streq(ike, ike_sa->get_name(ike_sa))) || (ike_id && ike_id == ike_sa->get_unique_id(ike_sa))) { lib->processor->queue_job(lib->processor, (job_t*)rekey_ike_sa_job_create(ike_sa->get_id(ike_sa), FALSE)); found++; } } isas->destroy(isas); builder = vici_builder_create(); if (!found) { errmsg = "no matching SAs to rekey found"; } builder->add_kv(builder, "success", errmsg ? "no" : "yes"); builder->add_kv(builder, "matches", "%u", found); if (errmsg) { builder->add_kv(builder, "errmsg", "%s", errmsg); } return builder->finalize(builder); } /** * Parse a peer-ip specified, which can be a subnet in CIDR notation, a range * or a single IP address. */ static traffic_selector_t *parse_peer_ip(char *ip) { traffic_selector_t *ts; host_t *from, *to; ts_type_t type; if (host_create_from_range(ip, &from, &to)) { if (to->get_family(to) == AF_INET) { type = TS_IPV4_ADDR_RANGE; } else { type = TS_IPV6_ADDR_RANGE; } ts = traffic_selector_create_from_bytes(0, type, from->get_address(from), 0, to->get_address(to), 0xFFFF); from->destroy(from); to->destroy(to); return ts; } return traffic_selector_create_from_cidr(ip, 0, 0, 0xFFFF); } CALLBACK(redirect, vici_message_t*, private_vici_control_t *this, char *name, u_int id, vici_message_t *request) { enumerator_t *sas; char *ike, *peer_ip, *peer_id, *gw, *errmsg = NULL; u_int ike_id, current, found = 0; identification_t *gateway, *identity = NULL, *other_id; traffic_selector_t *ts = NULL; ike_sa_t *ike_sa; vici_builder_t *builder; ike = request->get_str(request, NULL, "ike"); ike_id = request->get_int(request, 0, "ike-id"); peer_ip = request->get_str(request, NULL, "peer-ip"); peer_id = request->get_str(request, NULL, "peer-id"); gw = request->get_str(request, NULL, "gateway"); if (!gw || !(gateway = identification_create_from_string(gw))) { return send_reply(this, "missing target gateway"); } switch (gateway->get_type(gateway)) { case ID_IPV4_ADDR: case ID_IPV6_ADDR: case ID_FQDN: break; default: return send_reply(this, "unsupported gateway identity"); } if (peer_ip) { ts = parse_peer_ip(peer_ip); if (!ts) { return send_reply(this, "invalid peer IP selector"); } DBG1(DBG_CFG, "vici redirect IKE_SAs with src %R to %Y", ts, gateway); } if (peer_id) { identity = identification_create_from_string(peer_id); if (!identity) { DESTROY_IF(ts); return send_reply(this, "invalid peer identity selector"); } DBG1(DBG_CFG, "vici redirect IKE_SAs with ID '%Y' to %Y", identity, gateway); } if (ike_id) { DBG1(DBG_CFG, "vici redirect IKE_SA #%d to '%Y'", ike_id, gateway); } if (ike) { DBG1(DBG_CFG, "vici redirect IKE_SA '%s' to '%Y'", ike, gateway); } if (!peer_ip && !peer_id && !ike && !ike_id) { return send_reply(this, "missing redirect selector"); } sas = charon->controller->create_ike_sa_enumerator(charon->controller, TRUE); while (sas->enumerate(sas, &ike_sa)) { if (ike_sa->get_version(ike_sa) != IKEV2) { continue; } current = ike_sa->get_unique_id(ike_sa); if (ike_id && ike_id != current) { continue; } if (ike && !streq(ike, ike_sa->get_name(ike_sa))) { continue; } if (ts && !ts->includes(ts, ike_sa->get_other_host(ike_sa))) { continue; } if (identity) { other_id = ike_sa->get_other_eap_id(ike_sa); if (!other_id->matches(other_id, identity)) { continue; } } lib->processor->queue_job(lib->processor, (job_t*)redirect_job_create(ike_sa->get_id(ike_sa), gateway)); found++; } sas->destroy(sas); builder = vici_builder_create(); if (!found) { errmsg = "no matching SAs to redirect found"; } builder->add_kv(builder, "success", errmsg ? "no" : "yes"); builder->add_kv(builder, "matches", "%u", found); if (errmsg) { builder->add_kv(builder, "errmsg", "%s", errmsg); } gateway->destroy(gateway); DESTROY_IF(identity); DESTROY_IF(ts); return builder->finalize(builder); } /** * Find reqid of an existing CHILD_SA */ static uint32_t find_reqid(child_cfg_t *cfg) { enumerator_t *enumerator, *children; child_sa_t *child_sa; ike_sa_t *ike_sa; uint32_t reqid; reqid = charon->traps->find_reqid(charon->traps, cfg); if (reqid) { /* already trapped */ return reqid; } enumerator = charon->controller->create_ike_sa_enumerator( charon->controller, TRUE); while (!reqid && enumerator->enumerate(enumerator, &ike_sa)) { children = ike_sa->create_child_sa_enumerator(ike_sa); while (children->enumerate(children, &child_sa)) { if (streq(cfg->get_name(cfg), child_sa->get_name(child_sa))) { reqid = child_sa->get_reqid(child_sa); break; } } children->destroy(children); } enumerator->destroy(enumerator); return reqid; } CALLBACK(install, vici_message_t*, private_vici_control_t *this, char *name, u_int id, vici_message_t *request) { child_cfg_t *child_cfg = NULL; peer_cfg_t *peer_cfg; char *child, *ike; bool ok; child = request->get_str(request, NULL, "child"); ike = request->get_str(request, NULL, "ike"); if (!child) { return send_reply(this, "missing configuration name"); } DBG1(DBG_CFG, "vici install '%s'", child); child_cfg = find_child_cfg(child, ike, &peer_cfg); if (!child_cfg) { return send_reply(this, "configuration name not found"); } switch (child_cfg->get_mode(child_cfg)) { case MODE_PASS: case MODE_DROP: ok = charon->shunts->install(charon->shunts, peer_cfg->get_name(peer_cfg), child_cfg); break; default: ok = charon->traps->install(charon->traps, peer_cfg, child_cfg, find_reqid(child_cfg)); break; } peer_cfg->destroy(peer_cfg); child_cfg->destroy(child_cfg); return send_reply(this, ok ? NULL : "installing policy '%s' failed", child); } CALLBACK(uninstall, vici_message_t*, private_vici_control_t *this, char *name, u_int id, vici_message_t *request) { peer_cfg_t *peer_cfg; child_cfg_t *child_cfg; child_sa_t *child_sa; enumerator_t *enumerator; uint32_t reqid = 0; char *child, *ike, *ns; child = request->get_str(request, NULL, "child"); ike = request->get_str(request, NULL, "ike"); if (!child) { return send_reply(this, "missing configuration name"); } DBG1(DBG_CFG, "vici uninstall '%s'", child); if (!ike) { enumerator = charon->shunts->create_enumerator(charon->shunts); while (enumerator->enumerate(enumerator, &ns, &child_cfg)) { if (ns && streq(child, child_cfg->get_name(child_cfg))) { ike = strdup(ns); break; } } enumerator->destroy(enumerator); if (ike) { if (charon->shunts->uninstall(charon->shunts, ike, child)) { free(ike); return send_reply(this, NULL); } free(ike); return send_reply(this, "uninstalling policy '%s' failed", child); } } else if (charon->shunts->uninstall(charon->shunts, ike, child)) { return send_reply(this, NULL); } enumerator = charon->traps->create_enumerator(charon->traps); while (enumerator->enumerate(enumerator, &peer_cfg, &child_sa)) { if ((!ike || streq(ike, peer_cfg->get_name(peer_cfg))) && streq(child, child_sa->get_name(child_sa))) { reqid = child_sa->get_reqid(child_sa); break; } } enumerator->destroy(enumerator); if (reqid) { if (charon->traps->uninstall(charon->traps, reqid)) { return send_reply(this, NULL); } return send_reply(this, "uninstalling policy '%s' failed", child); } return send_reply(this, "policy '%s' not found", child); } CALLBACK(reload_settings, vici_message_t*, private_vici_control_t *this, char *name, u_int id, vici_message_t *request) { if (lib->settings->load_files(lib->settings, lib->conf, FALSE)) { charon->load_loggers(charon); lib->plugins->reload(lib->plugins, NULL); return send_reply(this, NULL); } return send_reply(this, "reloading '%s' failed", lib->conf); } static void manage_command(private_vici_control_t *this, char *name, vici_command_cb_t cb, bool reg) { this->dispatcher->manage_command(this->dispatcher, name, reg ? cb : NULL, this); } /** * (Un-)register dispatcher functions */ static void manage_commands(private_vici_control_t *this, bool reg) { manage_command(this, "initiate", initiate, reg); manage_command(this, "terminate", terminate, reg); manage_command(this, "rekey", rekey, reg); manage_command(this, "redirect", redirect, reg); manage_command(this, "install", install, reg); manage_command(this, "uninstall", uninstall, reg); manage_command(this, "reload-settings", reload_settings, reg); this->dispatcher->manage_event(this->dispatcher, "control-log", reg); } METHOD(vici_control_t, destroy, void, private_vici_control_t *this) { manage_commands(this, FALSE); free(this); } /** * See header */ vici_control_t *vici_control_create(vici_dispatcher_t *dispatcher) { private_vici_control_t *this; INIT(this, .public = { .destroy = _destroy, }, .dispatcher = dispatcher, ); manage_commands(this, TRUE); return &this->public; }