526 lines
12 KiB
C
526 lines
12 KiB
C
/*
|
|
* 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 <http://www.fsf.org/copyleft/gpl.txt>.
|
|
*
|
|
* 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 <inttypes.h>
|
|
|
|
#include <daemon.h>
|
|
#include <collections/array.h>
|
|
|
|
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, 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))
|
|
{
|
|
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;
|
|
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");
|
|
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, &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);
|
|
}
|
|
|
|
/**
|
|
* Find reqid of an existing CHILD_SA
|
|
*/
|
|
static u_int32_t find_reqid(child_cfg_t *cfg)
|
|
{
|
|
enumerator_t *enumerator, *children;
|
|
child_sa_t *child_sa;
|
|
ike_sa_t *ike_sa;
|
|
u_int32_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;
|
|
bool ok;
|
|
|
|
child = request->get_str(request, NULL, "child");
|
|
if (!child)
|
|
{
|
|
return send_reply(this, "missing configuration name");
|
|
}
|
|
|
|
DBG1(DBG_CFG, "vici install '%s'", child);
|
|
|
|
child_cfg = find_child_cfg(child, &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, 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)
|
|
{
|
|
child_sa_t *child_sa;
|
|
enumerator_t *enumerator;
|
|
u_int32_t reqid = 0;
|
|
char *child;
|
|
|
|
child = request->get_str(request, NULL, "child");
|
|
if (!child)
|
|
{
|
|
return send_reply(this, "missing configuration name");
|
|
}
|
|
|
|
DBG1(DBG_CFG, "vici uninstall '%s'", child);
|
|
|
|
if (charon->shunts->uninstall(charon->shunts, child))
|
|
{
|
|
return send_reply(this, NULL);
|
|
}
|
|
|
|
enumerator = charon->traps->create_enumerator(charon->traps);
|
|
while (enumerator->enumerate(enumerator, NULL, &child_sa))
|
|
{
|
|
if (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))
|
|
{
|
|
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, "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;
|
|
}
|