Merge branch 'vici-ruby'

Adds a ruby gem for the VICI protocol, along with some documentation
improvements and some minor fixes to vici and swanctl.
This commit is contained in:
Martin Willi
2014-10-10 11:43:43 +02:00
24 changed files with 1374 additions and 37 deletions
+5
View File
@@ -17,6 +17,11 @@ strongswan-5.2.1
- The new ext-auth plugin calls an external script to implement custom IKE_SA
authorization logic, courtesy of Vyronas Tsingaras.
- For the vici plugin a ruby gem has been added to allow ruby applications
to control or monitor the IKE daemon. The vici documentation has been updated
to include a description of the available operations and some simple examples
using both the libvici C interface and the ruby gem.
strongswan-5.2.0
----------------
+15
View File
@@ -68,6 +68,7 @@ ARG_WITH_SET([capabilities], [no], [set capability dropping library. Cur
ARG_WITH_SET([mpz_powm_sec], [yes], [use the more side-channel resistant mpz_powm_sec in libgmp, if available])
ARG_WITH_SET([dev-headers], [no], [install strongSwan development headers to directory.])
ARG_WITH_SET([printf-hooks], [auto], [force the use of a specific printf hook implementation (auto, builtin, glibc, vstr).])
ARG_WITH_SET([rubygemdir], ["gem environment gemdir"], [path to install ruby gems to])
if test -n "$PKG_CONFIG"; then
systemdsystemunitdir_default=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)
@@ -285,6 +286,7 @@ ARG_ENABL_SET([integrity-test], [enable integrity testing of libstrongswan and p
ARG_DISBL_SET([load-warning], [disable the charon plugin load option warning in starter.])
ARG_ENABL_SET([mediation], [enable IKEv2 Mediation Extension.])
ARG_ENABL_SET([unwind-backtraces],[use libunwind to create backtraces for memory leaks and segfaults.])
ARG_ENABL_SET([ruby-gems], [enable installation of provided ruby gems.])
# compile options
ARG_ENABL_SET([coverage], [enable lcov coverage report generation.])
ARG_ENABL_SET([leak-detective], [enable malloc hooks to find memory leaks.])
@@ -1152,6 +1154,17 @@ if test x$coverage = xtrue; then
CFLAGS="${CFLAGS} -g -O0"
fi
if test x$ruby_gems = xtrue; then
AC_PATH_PROG([GEM], [gem], [], [$PATH:/bin:/usr/bin:/usr/local/bin])
if test x$GEM = x; then
AC_MSG_ERROR(RubyGems package manager not found)
fi
if test "x$rubygemdir" = "xgem environment gemdir"; then
rubygemdir=$($GEM environment gemdir)
fi
AC_SUBST(RUBYGEMDIR, "$rubygemdir")
fi
# ===============================================
# collect plugin list for strongSwan components
# ===============================================
@@ -1511,6 +1524,7 @@ AM_CONDITIONAL(USE_SWANCTL, test x$swanctl = xtrue)
AM_CONDITIONAL(USE_SVC, test x$svc = xtrue)
AM_CONDITIONAL(USE_SYSTEMD, test x$systemd = xtrue)
AM_CONDITIONAL(USE_LEGACY_SYSTEMD, test -n "$systemdsystemunitdir" -a "x$systemdsystemunitdir" != xno)
AM_CONDITIONAL(USE_RUBY_GEMS, test x$ruby_gems = xtrue)
# ========================
# set global definitions
@@ -1713,6 +1727,7 @@ AC_CONFIG_FILES([
src/libcharon/plugins/maemo/Makefile
src/libcharon/plugins/stroke/Makefile
src/libcharon/plugins/vici/Makefile
src/libcharon/plugins/vici/ruby/Makefile
src/libcharon/plugins/updown/Makefile
src/libcharon/plugins/dhcp/Makefile
src/libcharon/plugins/unit_tester/Makefile
+7
View File
@@ -67,3 +67,10 @@ vici_tests_LDFLAGS = @COVERAGE_LDFLAGS@
vici_tests_LDADD = \
$(top_builddir)/src/libstrongswan/libstrongswan.la \
$(top_builddir)/src/libstrongswan/tests/libtest.la
SUBDIRS =
if USE_RUBY_GEMS
SUBDIRS += ruby
endif
+689 -9
View File
@@ -84,12 +84,12 @@ The message encoding consists of a sequence of elements. Each element starts
with the element type, optionally followed by an element name and/or an element
value. Currently the following message element types are defined:
* _SECTION_START = 0_: Begin a new section having a name
* _SECTION_END = 1_: End a previously started section
* _KEY_VALUE = 2_: Define a value for a named key in the current section
* _LIST_START = 3_: Begin a named list for list items
* _LIST_ITEM = 4_: Define an unnamed item value in the current list
* _LIST_END = 5_: End a previously started list
* _SECTION_START = 1_: Begin a new section having a name
* _SECTION_END = 2_: End a previously started section
* _KEY_VALUE = 3_: Define a value for a named key in the current section
* _LIST_START = 4_: Begin a named list for list items
* _LIST_ITEM = 5_: Define an unnamed item value in the current list
* _LIST_END = 6_: End a previously started list
Types are encoded as 8-bit values. Types having a name (SECTION_START,
KEY_VALUE and LIST_START) have an ASCII string following the type, which itself
@@ -103,7 +103,8 @@ the length field itself.
The interpretation of any value is not defined by the message format; it can
take arbitrary blobs. The application may specify types for specific keys, such
as strings or integer representations.
as strings or integer representations. The vici plugin currently uses
non-null terminated strings as values only; numbers get encoded as strings.
### Sections ###
@@ -165,6 +166,513 @@ the following C array:
1,
};
## Client-initiated commands ##
Based on the packet layer, VICI implements commands requested by the client
and responded to by the server using named _CMD_REQUEST_ and _CMD_RESPONSE_
packets wrapping messages. The request message may contain command arguments,
the response message the reply.
Some commands use response streaming, that is, a request triggers a series of
events to consecutively stream data to the client before the response message
completes the stream. A client must register for the appropriate event to
receive the stream, and unregister after the response has been received.
The following client issued commands with the appropriate command input and
output messages are currently defined:
### version() ###
Returns daemon and system specific version information.
{} => {
daemon = <IKE daemon name>
version = <strongSwan version>
sysname = <operating system name>
release = <operating system release>
machine = <hardware identifier>
}
### stats() ###
Returns IKE daemon statistics and load information.
{} => {
uptime = {
running = <relative uptime in human-readable form>
since = <absolute startup time>
}
workers = {
total = <total number of worker threads>
idle = <worker threads currently idle>
active = {
critical = <threads processing "critical" priority jobs>
high = <threads processing "high" priority jobs>
medium = <threads processing "medium" priority jobs>
low = <threads processing "low" priority jobs>
}
}
queues = {
critical = <jobs queued with "critical" priority>
high = <jobs queued with "high" priority>
medium = <jobs queued with "medium" priority>
low = <jobs queued with "low" priority>
}
scheduled = <number of jobs scheduled for timed execution>
ikesas = {
total = <total number of IKE_SAs active>
half-open = <number of IKE_SAs in half-open state>
}
plugins = [
<names of loaded plugins>
]
mem = { # available if built with leak-detective or on Windows
total = <total heap memory usage in bytes>
allocs = <total heap allocation blocks>
<heap-name>* = { # on Windows only
total = <heap memory usage in bytes by this heap>
allocs = <allocated blocks for this heap>
}
}
mallinfo = { # available with mallinfo() support
sbrk = <non-mmaped space available>
mmap = <mmaped space available>
used = <total number of bytes used>
free = <available but unused bytes>
}
}
### reload-settings() ###
Reloads _strongswan.conf_ settings and all plugins supporting configuration
reload.
{} => {
success = <yes or no>
errmsg = <error string on failure>
}
### initiate() ###
Initiates an SA while streaming _control-log_ events.
{
child = <CHILD_SA configuration name to initiate>
timeout = <timeout in seconds before returning>
loglevel = <loglevel to issue "control-log" events for>
} => {
success = <yes or no>
errmsg = <error string on failure or timeout>
}
### terminate() ###
Terminates an SA while streaming _control-log_ events.
{
child = <terminate a CHILD_SA by configuration name>
ike = <terminate an IKE_SA by configuration name>
child_id = <terminate a CHILD_SA by its reqid>
ike_id = <terminate an IKE_SA by its unique id>
timeout = <timeout in seconds before returning>
loglevel = <loglevel to issue "control-log" events for>
} => {
success = <yes or no>
errmsg = <error string on failure or timeout>
}
### install() ###
Install a trap, drop or bypass policy defined by a CHILD_SA config.
{
child = <CHILD_SA configuration name to install>
} => {
success = <yes or no>
errmsg = <error string on failure>
}
### uninstall() ###
Uninstall a trap, drop or bypass policy defined by a CHILD_SA config.
{
child = <CHILD_SA configuration name to install>
} => {
success = <yes or no>
errmsg = <error string on failure>
}
### list-sas() ###
Lists currently active IKE_SAs and associated CHILD_SAs by streaming _list-sa_
events.
{
noblock = <use non-blocking mode if key is set>
ike = <filter listed IKE_SAs by its name>
ike_id = <filter listed IKE_SA by its unique id>
} => {
# completes after streaming list-sa events
}
### list-policies() ###
List currently installed trap, drop and bypass policies by streaming
_list-policy_ events.
{
drop = <set to yes to list drop policies>
pass = <set to yes to list bypass policies>
trap = <set to yes to list trap policies>
child = <filter by CHILD_SA configuration name>
} => {
# completes after streaming list-sa events
}
### list-conns() ###
List currently loaded connections by streaming _list-conn_ events. This
call includes all connections known by the daemon, not only those loaded
over vici.
{
ike = <list connections matching a given configuration name only>
} => {
# completes after streaming list-conn events
}
### get-conns() ###
Return a list of connection names loaded exclusively over vici, not including
connections found in other backends.
{} => {
conns = [
<list of connection names>
]
}
### list-certs() ###
List currently loaded certificates by streaming _list-cert_ events. This
call includes all certificates known by the daemon, not only those loaded
over vici.
{
type = <certificate type to filter for, or ANY>
subject = <set to list only certificates having subject>
} => {
# completes after streaming list-cert events
}
### load-conn() ###
Load a single connection definition into the daemon. An existing connection
with the same name gets updated or replaced.
{
<IKE_SA config name> = {
# IKE configuration parameters with authentication and CHILD_SA
# subsections. Refer to swanctl.conf(5) for details.
} => {
success = <yes or no>
errmsg = <error string on failure>
}
}
### unload-conn() ###
Unload a previously loaded connection definition by name.
{
name = <IKE_SA config name>
} => {
success = <yes or no>
errmsg = <error string on failure>
}
### load-cert() ###
Load a certificate into the daemon.
{
type = <certificate type, X509|X509CA|X509AA|X509CRL|X509AC>
data = <PEM or DER encoded certificate data>
} => {
success = <yes or no>
errmsg = <error string on failure>
}
### load-key() ###
Load a private key into the daemon.
{
type = <private key type, RSA|ECDSA>
data = <PEM or DER encoded key data>
} => {
success = <yes or no>
errmsg = <error string on failure>
}
### load-shared() ###
Load a shared IKE PSK, EAP or XAuth secret into the daemon.
{
type = <private key type, IKE|EAP|XAUTH>
data = <raw shared key data>
owners = [
<list of shared key owner identities>
]
} => {
success = <yes or no>
errmsg = <error string on failure>
}
### clear-creds() ###
Clear all loaded certificate, private key and shared key credentials. This
affects only credentials loaded over vici, but additionally flushes the
credential cache.
{} => {
success = <yes or no>
errmsg = <error string on failure>
}
### load-pool() ###
Load an in-memory virtual IP and configuration attribute pool. Existing
pools with the same name get updated, if possible.
{
<pool name> = {
addrs = <subnet of virtual IP pool addresses>
<attribute type>* = [
# attribute type is one of address, dns, nbns, dhcp, netmask,
# server, subnet, split_include, split_exclude or a numerical
# attribute type identifier.
<list of attributes for type>
]
}
} => {
success = <yes or no>
errmsg = <error string on failure>
}
### unload-pool() ###
Unload a previously loaded virtual IP and configuration attribute pool.
Unloading fails for pools with leases currently online.
{
name = <virtual IP address pool to delete>
} => {
success = <yes or no>
errmsg = <error string on failure>
}
### get-pools() ###
List the currently loaded pools.
{} => {
<pool name>* = {
base = <virtual IP pool base address>
size = <total number of addresses in the pool>
online = <number of leases online>
offline = <number of leases offline>
}
}
## Server-issued events ##
Based on the packet layer, the vici plugin raises event messages using named
EVENT packets wrapping messages. The message contains event details.
### log ###
The _log_ event is issued to registered clients for each debug log message.
This event is not associated with a command.
{
group = <subsystem identifier for debug message>
level = <log level, 0-4>
thread = <numerical thread identifier issuing the log message>
ikesa-name = <name of IKE_SA, if log is associated with any>
ikesa-uniqued = <unique identifier of IKE_A, if log associated with any>
msg = <log message text>
}
### control-log ###
The _control-log_ event is issued for log events during active _initiate_ or
_terminate_ commands. It is issued only to clients currently having such
a command active.
{
group = <subsystem identifier for debug message>
level = <log level, 0-4>
ikesa-name = <name of IKE_SA, if log associated with any>
ikesa-uniqued = <unique identifier of IKE_A, if log associated with any>
msg = <log message text>
}
### list-sa ###
The _list-sa_ event is issued to stream IKE_SAs during an active _list-sas_
command.
{
<IKE_SA config name> = {
uniqueid = <IKE_SA unique identifier>
version = <IKE version, 1 or 2>
state = <IKE_SA state name>
local-host = <local IKE endpoint address>
local-id = <local IKE identity>
remote-host = <remote IKE endpoint address>
remote-id = <remote IKE identity>
remote-xauth-id = <remote XAuth identity, if XAuth-authenticated>
remote-eap-id = <remote EAP identity, if EAP-authenticated>
initiator = <yes, if initiator of IKE_SA>
initiator-spi = <hex encoded initiator SPI / cookie>
responder-spi = <hex encoded responder SPI / cookie>
encr-alg = <IKE encryption algorithm string>
encr-keysize = <key size for encr-alg, if applicable>
integ-alg = <IKE integrity algorithm string>
integ-keysize = <key size for encr-alg, if applicable>
prf-alg = <IKE pseudo random function string>
dh-group = <IKE Diffie-Hellman group string>
established = <seconds the IKE_SA has been established>
rekey-time = <seconds before IKE_SA gets rekeyed>
reauth-time = <seconds before IKE_SA gets re-authenticated>
tasks-queued = [
<list of currently queued tasks for execution>
]
tasks-active = [
<list of tasks currently initiating actively>
]
tasks-passive = [
<list of tasks currently handling passively>
]
child-sas = {
<child-sa-name>* = {
reqid = <reqid of CHILD_SA>
state = <state string of CHILD_SA>
mode = <IPsec mode, tunnel|transport|beet>
protocol = <IPsec protocol AH|ESP>
encap = <yes if using UDP encapsulation>
spi-in = <hex encoded inbound SPI>
spi-out = <hex encoded outbound SPI>
cpi-in = <hex encoded inbound CPI, if using compression>
cpi-out = <hex encoded outbound CPI, if using compression>
encr-alg = <ESP encryption algorithm name, if any>
encr-keysize = <ESP encryption key size, if applicable>
integ-alg = <ESP or AH integrity algorithm name, if any>
integ-keysize = <ESP or AH integrity key size, if applicable>
prf-alg = <CHILD_SA pseudo random function name>
dh-group = <CHILD_SA PFS rekeying DH group name, if any>
esn = <1 if using extended sequence numbers>
bytes-in = <number of input bytes processed>
packets-in = <number of input packets processed>
use-in = <seconds since last inbound packet, if any>
bytes-out = <number of output bytes processed>
packets-out = <number of output packets processed>
use-out = <seconds since last outbound packet, if any>
rekey-time = <seconds before CHILD_SA gets rekeyed>
life-time = <seconds before CHILD_SA expires>
install-time = <seconds the CHILD_SA has been installed>
local-ts = [
<list of local traffic selectors>
]
remote-ts = [
<list of remote traffic selectors>
]
}
}
}
}
### list-policy ###
The _list-policy_ event is issued to stream installed policies during an active
_list-policies_ command.
{
<child-sa-config-name> = {
mode = <policy mode, tunnel|transport|pass|drop>
local-ts = [
<list of local traffic selectors>
]
remote-ts = [
<list of remote traffic selectors>
]
}
}
### list-conn ###
The _list-conn_ event is issued to stream loaded connection during an active
_list-conns_ command.
{
<IKE_SA connection name> = {
local_addrs = [
<list of valid local IKE endpoint addresses>
]
remote_addrs = [
<list of valid remote IKE endpoint addresses>
]
version = <IKE version as string, IKEv1|IKEv2 or 0 for any>
local*, remote* = { # multiple local and remote auth sections
class = <authentication type>
eap-type = <EAP type to authenticate if when using EAP>
eap-vendor = <EAP vendor for type, if any>
xauth = <xauth backend name>
revocation = <revocation policy>
id = <IKE identity>
aaa_id = <AAA authentication backend identity>
eap_id = <EAP identity for authentication>
xauth_id = <XAuth username for authentication>
groups = [
<group membership required to use connection>
]
certs = [
<certificates allowed for authentication>
]
cacerts = [
<CA certificates allowed for authentication>
]
}
children = {
<CHILD_SA config name>* = {
mode = <IPsec mode>
local-ts = [
<list of local traffic selectors>
]
remote-ts = [
<list of remote traffic selectors>
]
}
}
}
}
### list-cert ###
The _list-cert_ event is issued to stream loaded certificates during an active
_list-certs_ command.
{
type = <certificate type>
has_privkey = <set if a private key for the certificate is available>
data = <ASN1 encoded certificate data>
}
# libvici C client library #
libvici is the reference implementation of a C client library implementing
@@ -172,5 +680,177 @@ the vici protocol. It builds upon libstrongswan, but provides a stable API
to implement client applications in the C programming language. libvici uses
the libstrongswan thread pool to deliver event messages asynchronously.
More information about the libvici API is available in the libvici.h header
file.
## Connecting to the daemon ##
This example shows how to connect to the daemon using the default URI, and
then perform proper cleanup:
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <libvici.h>
int main(int argc, char *argv[])
{
vici_conn_t *conn;
int ret = 0;
vici_init();
conn = vici_connect(NULL);
if (conn)
{
/* do stuff */
vici_disconnect(conn);
}
else
{
ret = errno;
fprintf(stderr, "connecting failed: %s\n", strerror(errno));
}
vici_deinit();
return ret;
}
## A simple client request ##
In the following example, a simple _version_ request is issued to the daemon
and the result is printed:
int get_version(vici_conn_t *conn)
{
vici_req_t *req;
vici_res_t *res;
int ret = 0;
req = vici_begin("version");
res = vici_submit(req, conn);
if (res)
{
printf("%s %s (%s, %s, %s)\n",
vici_find_str(res, "", "daemon"),
vici_find_str(res, "", "version"),
vici_find_str(res, "", "sysname"),
vici_find_str(res, "", "release"),
vici_find_str(res, "", "machine"));
vici_free_res(res);
}
else
{
ret = errno;
fprintf(stderr, "version request failed: %s\n", strerror(errno));
}
return ret;
}
## A request with event streaming and callback parsing ##
In this more advanced example, the _list-conns_ command is used to stream
loaded connections with the _list-conn_ event. The event message is parsed
with a simple callback to print the connection name:
int conn_cb(void *null, vici_res_t *res, char *name)
{
printf("%s\n", name);
return 0;
}
void list_cb(void *null, char *name, vici_res_t *res)
{
if (vici_parse_cb(res, conn_cb, NULL, NULL, NULL) != 0)
{
fprintf(stderr, "parsing failed: %s\n", strerror(errno));
}
}
int list_conns(vici_conn_t *conn)
{
vici_req_t *req;
vici_res_t *res;
int ret = 0;
if (vici_register(conn, "list-conn", list_cb, NULL) == 0)
{
req = vici_begin("list-conns");
res = vici_submit(req, conn);
if (res)
{
vici_free_res(res);
}
else
{
ret = errno;
fprintf(stderr, "request failed: %s\n", strerror(errno));
}
vici_register(conn, "list-conn", NULL, NULL);
}
else
{
ret = errno;
fprintf(stderr, "registration failed: %s\n", strerror(errno));
}
return ret;
}
## API documentation ##
More information about the libvici API is available in the _libvici.h_ header
file or the generated Doxygen documentation.
# vici ruby gem #
The _vici ruby gem_ is a pure ruby implementation of the VICI protocol to
implement client applications. It is provided in the _ruby_ subdirectory, and
gets built and installed if strongSwan has been _./configure_'d with
_--enable-vici_ and _--enable-ruby-gems_.
The _Connection_ class from the _Vici_ module provides the high level interface,
the underlying classes are usually not required to build ruby applications
using VICI. The _Connection_ class provides methods for the supported VICI
commands and an event listening mechanism.
To represent the VICI message data tree, the gem converts the binary encoding
to ruby data types. The _Connection_ class takes and returns ruby objects for
the exchanged message data:
* Sections get encoded as Hash, containing other sections as Hash, or
* Key/Values, where the values are Strings as Hash values
* Lists get encoded as Arrays with String values
Non-String values that are not a Hash nor an Array get converted with .to_s
during encoding.
## Connecting to the daemon ##
To create a connection to the daemon, a socket must be passed to the
_Connection_ constructor. There is no default, but on Unix systems usually
a Unix socket over _/var/run/charon.vici_ is used:
require "vici"
require "socket"
v = Vici::Connection.new(UNIXSocket.new("/var/run/charon.vici"))
## A simple client request ##
An example to print the daemon version information is as simple as:
x = v.version
puts "%s %s (%s, %s, %s)" % [
x["daemon"], x["version"], x["sysname"], x["release"], x["machine"]
]
## A request with closure invocation ##
The _Connection_ class takes care of event streaming by invoking a closure
for each event. The following example lists all loaded connections using the
_list-conns_ command and implicitly the _list-conn_ event:
v.list_conns { |conn|
conn.each { |key, value|
puts key
}
}
## API documentation ##
For more details about the ruby gem refer to the comments in the gem source
code or the generated documentation.
+4 -1
View File
@@ -438,7 +438,7 @@ void vici_free_req(vici_req_t *req)
free(req);
}
int vici_dump(vici_res_t *res, char *label, bool pretty, FILE *out)
int vici_dump(vici_res_t *res, char *label, int pretty, FILE *out)
{
if (res->message->dump(res->message, label, pretty, out))
{
@@ -754,11 +754,14 @@ void vici_init()
library_init(NULL, "vici");
if (lib->processor->get_total_threads(lib->processor) < 4)
{
dbg_default_set_level(0);
lib->processor->set_threads(lib->processor, 4);
dbg_default_set_level(1);
}
}
void vici_deinit()
{
lib->processor->cancel(lib->processor);
library_deinit();
}
+1 -3
View File
@@ -75,8 +75,6 @@
#include <stdio.h>
#include <utils/utils.h>
/**
* Opaque vici connection contex.
*/
@@ -284,7 +282,7 @@ void vici_free_req(vici_req_t *req);
* @param out FILE to dump to
* @return 0 if dumped complete message, 1 on error
*/
int vici_dump(vici_res_t *res, char *label, bool pretty, FILE *out);
int vici_dump(vici_res_t *res, char *label, int pretty, FILE *out);
/**
* Parse next element from a vici response message.
@@ -0,0 +1,2 @@
*.gem
*.gemspec
@@ -0,0 +1,20 @@
vici.gemspec: $(srcdir)/vici.gemspec.in
$(AM_V_GEN) sed \
-e "s:@GEM_VERSION@:$(PACKAGE_VERSION):" \
$(srcdir)/vici.gemspec.in > $@
vici-$(PACKAGE_VERSION).gem: vici.gemspec
$(GEM) build vici.gemspec
all-local: vici-$(PACKAGE_VERSION).gem
clean-local:
rm -f vici.gemspec vici-$(PACKAGE_VERSION).gem
install-data-local: vici-$(PACKAGE_VERSION).gem
$(GEM) install --install-dir $(DESTDIR)$(RUBYGEMDIR) \
vici-$(PACKAGE_VERSION).gem
uninstall-local:
$(GEM) uninstall --install-dir $(DESTDIR)$(RUBYGEMDIR) \
--version $(PACKAGE_VERSION) vici
+569
View File
@@ -0,0 +1,569 @@
##
# The Vici module implements a native ruby client side library for the
# strongSwan VICI protocol. The Connection class provides a high-level
# interface to issue requests or listen for events.
#
# Copyright (C) 2014 Martin Willi
# Copyright (C) 2014 revosec AG
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
module Vici
##
# Vici specific exception all others inherit from
class Error < StandardError
end
##
# Error while parsing a vici message from the daemon
class ParseError < Error
end
##
# Error while encoding a vici message from ruby data structures
class EncodeError < Error
end
##
# Error while exchanging messages over the vici Transport layer
class TransportError < Error
end
##
# Generic vici command execution error
class CommandError < Error
end
##
# Error if an issued vici command is unknown by the daemon
class CommandUnknownError < CommandError
end
##
# Error if a command failed to execute in the daemon
class CommandExecError < CommandError
end
##
# Generic vici event handling error
class EventError < Error
end
##
# Tried to register to / unregister from an unknown vici event
class EventUnknownError < EventError
end
##
# Exception to raise from an event listening closure to stop listening
class StopEventListening < Exception
end
##
# The Message class provides the low level encoding and decoding of vici
# protocol messages. Directly using this class is usually not required.
class Message
SECTION_START = 1
SECTION_END = 2
KEY_VALUE = 3
LIST_START = 4
LIST_ITEM = 5
LIST_END = 6
def initialize(data = "")
if data == nil
@root = Hash.new()
elsif data.is_a?(Hash)
@root = data
else
@encoded = data
end
end
##
# Get the raw byte encoding of an on-the-wire message
def encoding
if @encoded == nil
@encoded = encode(@root)
end
@encoded
end
##
# Get the root element of the parsed ruby data structures
def root
if @root == nil
@root = parse(@encoded)
end
@root
end
private
def encode_name(name)
[name.length].pack("c") << name
end
def encode_value(value)
if value.class != String
value = value.to_s
end
[value.length].pack("n") << value
end
def encode_kv(encoding, key, value)
encoding << KEY_VALUE << encode_name(key) << encode_value(value)
end
def encode_section(encoding, key, value)
encoding << SECTION_START << encode_name(key)
encoding << encode(value) << SECTION_END
end
def encode_list(encoding, key, value)
encoding << LIST_START << encode_name(key)
value.each do |item|
encoding << LIST_ITEM << encode_value(item)
end
encoding << LIST_END
end
def encode(node)
encoding = ""
node.each do |key, value|
case value.class
when String, Fixnum, true, false
encoding = encode_kv(encoding, key, value)
else
if value.is_a?(Hash)
encoding = encode_section(encoding, key, value)
elsif value.is_a?(Array)
encoding = encode_list(encoding, key, value)
else
encoding = encode_kv(encoding, key, value)
end
end
end
encoding
end
def parse_name(encoding)
len = encoding.unpack("c")[0]
name = encoding[1, len]
return encoding[(1 + len)..-1], name
end
def parse_value(encoding)
len = encoding.unpack("n")[0]
value = encoding[2, len]
return encoding[(2 + len)..-1], value
end
def parse(encoding)
stack = [Hash.new]
list = nil
while encoding.length != 0 do
type = encoding.unpack("c")[0]
encoding = encoding[1..-1]
case type
when SECTION_START
encoding, name = parse_name(encoding)
stack.push(stack[-1][name] = Hash.new)
when SECTION_END
if stack.length() == 1
raise ParseError, "unexpected section end"
end
stack.pop()
when KEY_VALUE
encoding, name = parse_name(encoding)
encoding, value = parse_value(encoding)
stack[-1][name] = value
when LIST_START
encoding, name = parse_name(encoding)
stack[-1][name] = []
list = name
when LIST_ITEM
raise ParseError, "unexpected list item" if list == nil
encoding, value = parse_value(encoding)
stack[-1][list].push(value)
when LIST_END
raise ParseError, "unexpected list end" if list == nil
list = nil
else
raise ParseError, "invalid type: #{type}"
end
end
if stack.length() > 1
raise ParseError, "unexpected message end"
end
stack[0]
end
end
##
# The Transport class implements to low level segmentation of packets
# to the underlying transport stream. Directly using this class is usually
# not required.
class Transport
CMD_REQUEST = 0
CMD_RESPONSE = 1
CMD_UNKNOWN = 2
EVENT_REGISTER = 3
EVENT_UNREGISTER = 4
EVENT_CONFIRM = 5
EVENT_UNKNOWN = 6
EVENT = 7
##
# Create a transport layer using a provided socket for communication.
def initialize(socket)
@socket = socket
@events = Hash.new
end
##
# Write a packet prefixed by its length over the transport socket. Type
# specifies the message, the optional label and message get appended.
def write(type, label, message)
encoding = ""
if label
encoding << label.length << label
end
if message
encoding << message.encoding
end
@socket.send([encoding.length + 1, type].pack("Nc") + encoding, 0)
end
##
# Read a packet from the transport socket. Returns the packet type, and
# if available in the packet a label and the contained message.
def read
len = @socket.recv(4).unpack("N")[0]
encoding = @socket.recv(len)
type = encoding.unpack("c")[0]
len = 1
case type
when CMD_REQUEST, EVENT_REGISTER, EVENT_UNREGISTER, EVENT
label = encoding[2, encoding[1].unpack("c")[0]]
len += label.length + 1
when CMD_RESPONSE, CMD_UNKNOWN, EVENT_CONFIRM, EVENT_UNKNOWN
label = nil
else
raise TransportError, "invalid message: #{type}"
end
if encoding.length == len
return type, label, Message.new
end
return type, label, Message.new(encoding[len..-1])
end
def dispatch_event(name, message)
@events[name].each do |handler|
handler.call(name, message)
end
end
def read_and_dispatch_event
type, label, message = read
p
if type == EVENT
dispatch_event(label, message)
else
raise TransportError, "unexpected message: #{type}"
end
end
def read_and_dispatch_events
loop do
type, label, message = read
if type == EVENT
dispatch_event(label, message)
else
return type, label, message
end
end
end
##
# Send a command with a given name, and optionally a message. Returns
# the reply message on success.
def request(name, message = nil)
write(CMD_REQUEST, name, message)
type, label, message = read_and_dispatch_events
case type
when CMD_RESPONSE
return message
when CMD_UNKNOWN
raise CommandUnknownError, name
else
raise CommandError, "invalid response for #{name}"
end
end
##
# Register a handler method for the given event name
def register(name, handler)
write(EVENT_REGISTER, name, nil)
type, label, message = read_and_dispatch_events
case type
when EVENT_CONFIRM
if @events.has_key?(name)
@events[name] += [handler]
else
@events[name] = [handler];
end
when EVENT_UNKNOWN
raise EventUnknownError, name
else
raise EventError, "invalid response for #{name} register"
end
end
##
# Unregister a handler method for the given event name
def unregister(name, handler)
write(EVENT_UNREGISTER, name, nil)
type, label, message = read_and_dispatch_events
case type
when EVENT_CONFIRM
@events[name] -= [handler]
when EVENT_UNKNOWN
raise EventUnknownError, name
else
raise EventError, "invalid response for #{name} unregister"
end
end
end
##
# The Connection class provides the high-level interface to monitor, configure
# and control the IKE daemon. It takes a connected stream-oriented Socket for
# the communication with the IKE daemon.
#
# This class takes and returns ruby objects for the exchanged message data.
# * Sections get encoded as Hash, containing other sections as Hash, or
# * Key/Values, where the values are Strings as Hash values
# * Lists get encoded as Arrays with String values
# Non-String values that are not a Hash nor an Array get converted with .to_s
# during encoding.
class Connection
def initialize(socket)
@transp = Transport.new(socket)
end
##
# List matching loaded connections. The provided closure is invoked
# for each matching connection.
def list_conns(match = nil, &block)
call_with_event("list-conns", Message.new(match), "list-conn", &block)
end
##
# List matching active SAs. The provided closure is invoked for each
# matching SA.
def list_sas(match = nil, &block)
call_with_event("list-sas", Message.new(match), "list-sa", &block)
end
##
# List matching installed policies. The provided closure is invoked
# for each matching policy.
def list_policies(match, &block)
call_with_event("list-policies", Message.new(match), "list-policy",
&block)
end
##
# List matching loaded certificates. The provided closure is invoked
# for each matching certificate definition.
def list_certs(match = nil, &block)
call_with_event("list-certs", Message.new(match), "list-cert", &block)
end
##
# Load a connection into the daemon.
def load_conn(conn)
check_success(@transp.request("load-conn", Message.new(conn)))
end
##
# Unload a connection from the daemon.
def unload_conn(conn)
check_success(@transp.request("unload-conn", Message.new(conn)))
end
##
# Get the names of connections managed by vici.
def get_conns()
@transp.request("get-conns").root
end
##
# Clear all loaded credentials.
def clear_creds()
check_success(@transp.request("clear-creds"))
end
##
# Load a certificate into the daemon.
def load_cert(cert)
check_success(@transp.request("load-cert", Message.new(cert)))
end
##
# Load a private key into the daemon.
def load_key(key)
check_success(@transp.request("load-key", Message.new(key)))
end
##
# Load a shared key into the daemon.
def load_shared(shared)
check_success(@transp.request("load-shared", Message.new(shared)))
end
##
# Load a virtual IP / attribute pool
def load_pool(pool)
check_success(@transp.request("load-pool", Message.new(pool)))
end
##
# Unload a virtual IP / attribute pool
def unload_pool(pool)
check_success(@transp.request("unload-pool", Message.new(pool)))
end
##
# Get the currently loaded pools.
def get_pools()
@transp.request("get-pools").root
end
##
# Initiate a connection. The provided closure is invoked for each log line.
def initiate(options, &block)
check_success(call_with_event("initiate", Message.new(options),
"control-log", &block))
end
##
# Terminate a connection. The provided closure is invoked for each log line.
def terminate(options, &block)
check_success(call_with_event("terminate", Message.new(options),
"control-log", &block))
end
##
# Install a shunt/route policy.
def install(policy)
check_success(@transp.request("install", Message.new(policy)))
end
##
# Uninstall a shunt/route policy.
def uninstall(policy)
check_success(@transp.request("uninstall", Message.new(policy)))
end
##
# Reload strongswan.conf settings.
def reload_settings
check_success(@transp.request("reload-settings", nil))
end
##
# Get daemon statistics and information.
def stats
@transp.request("stats", nil).root
end
##
# Get daemon version information
def version
@transp.request("version", nil).root
end
##
# Listen for a set of event messages. This call is blocking, and invokes
# the passed closure for each event received. The closure receives the
# event name and the event message as argument. To stop listening, the
# closure may raise a StopEventListening exception, the only catched
# exception.
def listen_events(events, &block)
self.class.instance_eval do
define_method(:listen_event) do |label, message|
block.call(label, message.root)
end
end
events.each do |event|
@transp.register(event, method(:listen_event))
end
begin
loop do
@transp.read_and_dispatch_event
end
rescue StopEventListening
ensure
events.each do |event|
@transp.unregister(event, method(:listen_event))
end
end
end
##
# Issue a command request, but register for a specific event while the
# command is active. VICI uses this mechanism to stream potentially large
# data objects continuously. The provided closure is invoked for all
# event messages.
def call_with_event(command, request, event, &block)
self.class.instance_eval do
define_method(:call_event) do |label, message|
block.call(message.root)
end
end
@transp.register(event, method(:call_event))
begin
reply = @transp.request(command, request)
ensure
@transp.unregister(event, method(:call_event))
end
reply
end
##
# Check if the reply of a command indicates "success", otherwise raise a
# CommandExecError exception
def check_success(reply)
root = reply.root
if root["success"] != "yes"
raise CommandExecError, root["errmsg"]
end
root
end
end
end
@@ -0,0 +1,16 @@
Gem::Specification.new do |s|
s.name = "vici"
s.version = "@GEM_VERSION@"
s.authors = ["Martin Willi"]
s.email = ["[email protected]"]
s.description = %q{
The strongSwan VICI protocol allows external application to monitor,
configure and control the IKE daemon charon. This ruby gem provides a
native client side implementation of the VICI protocol, well suited to
script automated tasks in a relaible way.
}
s.summary = "Native ruby interface for strongSwan VICI"
s.homepage = "https://wiki.strongswan.org/projects/strongswan/wiki/Vici"
s.license = "MIT"
s.files = "lib/vici.rb"
end
+1 -4
View File
@@ -270,13 +270,10 @@ CALLBACK(load_shared, vici_message_t*,
CALLBACK(clear_creds, vici_message_t*,
private_vici_cred_t *this, char *name, u_int id, vici_message_t *message)
{
vici_builder_t *builder;
this->creds->clear(this->creds);
lib->credmgr->flush_cache(lib->credmgr, CERT_ANY);
builder = vici_builder_create();
return builder->finalize(builder);
return create_reply(NULL);
}
static void manage_command(private_vici_cred_t *this,
+2 -1
View File
@@ -267,9 +267,10 @@ static int call_command(command_t *cmd)
conn = vici_connect(uri);
if (!conn)
{
ret = errno;
command_usage("connecting to '%s' URI failed: %s",
uri ?: "default", strerror(errno));
return errno;
return ret;
}
ret = cmd->call(conn);
vici_disconnect(conn);
+4 -2
View File
@@ -71,8 +71,9 @@ static int initiate(vici_conn_t *conn)
if (vici_register(conn, "control-log", log_cb, &format) != 0)
{
ret = errno;
fprintf(stderr, "registering for log failed: %s\n", strerror(errno));
return errno;
return ret;
}
req = vici_begin("initiate");
if (child)
@@ -87,8 +88,9 @@ static int initiate(vici_conn_t *conn)
res = vici_submit(req, conn);
if (!res)
{
ret = errno;
fprintf(stderr, "initiate request failed: %s\n", strerror(errno));
return errno;
return ret;
}
if (format & COMMAND_FORMAT_RAW)
{
+2 -1
View File
@@ -55,8 +55,9 @@ static int manage_policy(vici_conn_t *conn, char *label)
res = vici_submit(req, conn);
if (!res)
{
ret = errno;
fprintf(stderr, "%s request failed: %s\n", label, strerror(errno));
return errno;
return ret;
}
if (format & COMMAND_FORMAT_RAW)
{
+5 -2
View File
@@ -590,6 +590,7 @@ static int list_certs(vici_conn_t *conn)
vici_res_t *res;
command_format_options_t format = COMMAND_FORMAT_NONE;
char *arg, *subject = NULL, *type = NULL;
int ret;
while (TRUE)
{
@@ -621,9 +622,10 @@ static int list_certs(vici_conn_t *conn)
}
if (vici_register(conn, "list-cert", list_cb, &format) != 0)
{
ret = errno;
fprintf(stderr, "registering for certificates failed: %s\n",
strerror(errno));
return errno;
return ret;
}
req = vici_begin("list-certs");
if (type)
@@ -637,8 +639,9 @@ static int list_certs(vici_conn_t *conn)
res = vici_submit(req, conn);
if (!res)
{
ret = errno;
fprintf(stderr, "list-certs request failed: %s\n", strerror(errno));
return errno;
return ret;
}
if (format & COMMAND_FORMAT_RAW)
{
+5 -2
View File
@@ -183,6 +183,7 @@ static int list_conns(vici_conn_t *conn)
vici_res_t *res;
command_format_options_t format = COMMAND_FORMAT_NONE;
char *arg;
int ret;
while (TRUE)
{
@@ -205,16 +206,18 @@ static int list_conns(vici_conn_t *conn)
}
if (vici_register(conn, "list-conn", list_cb, &format) != 0)
{
ret = errno;
fprintf(stderr, "registering for connections failed: %s\n",
strerror(errno));
return errno;
return ret;
}
req = vici_begin("list-conns");
res = vici_submit(req, conn);
if (!res)
{
ret = errno;
fprintf(stderr, "list-conns request failed: %s\n", strerror(errno));
return errno;
return ret;
}
if (format & COMMAND_FORMAT_RAW)
{
+5 -2
View File
@@ -116,6 +116,7 @@ static int list_pols(vici_conn_t *conn)
bool trap = FALSE, drop = FALSE, pass = FALSE;
command_format_options_t format = COMMAND_FORMAT_NONE;
char *arg, *child = NULL;
int ret;
while (TRUE)
{
@@ -154,9 +155,10 @@ static int list_pols(vici_conn_t *conn)
}
if (vici_register(conn, "list-policy", list_cb, &format) != 0)
{
ret = errno;
fprintf(stderr, "registering for policies failed: %s\n",
strerror(errno));
return errno;
return ret;
}
req = vici_begin("list-policies");
if (child)
@@ -178,8 +180,9 @@ static int list_pols(vici_conn_t *conn)
res = vici_submit(req, conn);
if (!res)
{
ret = errno;
fprintf(stderr, "list-policies request failed: %s\n", strerror(errno));
return errno;
return ret;
}
if (format & COMMAND_FORMAT_RAW)
{
+2 -1
View File
@@ -68,8 +68,9 @@ static int list_pools(vici_conn_t *conn)
res = vici_submit(req, conn);
if (!res)
{
ret = errno;
fprintf(stderr, "get-pools request failed: %s\n", strerror(errno));
return errno;
return ret;
}
if (format & COMMAND_FORMAT_RAW)
{
+5 -3
View File
@@ -283,7 +283,7 @@ static int list_sas(vici_conn_t *conn)
bool noblock = FALSE;
command_format_options_t format = COMMAND_FORMAT_NONE;
char *arg, *ike = NULL;
int ike_id = 0;
int ike_id = 0, ret;
while (TRUE)
{
@@ -315,8 +315,9 @@ static int list_sas(vici_conn_t *conn)
}
if (vici_register(conn, "list-sa", list_cb, &format) != 0)
{
ret = errno;
fprintf(stderr, "registering for SAs failed: %s\n", strerror(errno));
return errno;
return ret;
}
req = vici_begin("list-sas");
if (ike)
@@ -334,8 +335,9 @@ static int list_sas(vici_conn_t *conn)
res = vici_submit(req, conn);
if (!res)
{
ret = errno;
fprintf(stderr, "list-sas request failed: %s\n", strerror(errno));
return errno;
return ret;
}
if (format & COMMAND_FORMAT_RAW)
{
+3 -1
View File
@@ -50,6 +50,7 @@ static int logcmd(vici_conn_t *conn)
{
command_format_options_t format = COMMAND_FORMAT_NONE;
char *arg;
int ret;
while (TRUE)
{
@@ -73,8 +74,9 @@ static int logcmd(vici_conn_t *conn)
if (vici_register(conn, "log", log_cb, &format) != 0)
{
ret = errno;
fprintf(stderr, "registering for log failed: %s\n", strerror(errno));
return errno;
return ret;
}
wait_sigint();
+2 -1
View File
@@ -49,8 +49,9 @@ static int reload_settings(vici_conn_t *conn)
res = vici_submit(req, conn);
if (!res)
{
ret = errno;
fprintf(stderr, "reload-settings request failed: %s\n", strerror(errno));
return errno;
return ret;
}
if (format & COMMAND_FORMAT_RAW)
{
+3 -1
View File
@@ -23,6 +23,7 @@ static int stats(vici_conn_t *conn)
vici_res_t *res;
char *arg;
command_format_options_t format = COMMAND_FORMAT_NONE;
int ret;
while (TRUE)
{
@@ -48,8 +49,9 @@ static int stats(vici_conn_t *conn)
res = vici_submit(req, conn);
if (!res)
{
ret = errno;
fprintf(stderr, "stats request failed: %s\n", strerror(errno));
return errno;
return ret;
}
if (format & COMMAND_FORMAT_RAW)
{
+4 -2
View File
@@ -80,8 +80,9 @@ static int terminate(vici_conn_t *conn)
if (vici_register(conn, "control-log", log_cb, &format) != 0)
{
ret = errno;
fprintf(stderr, "registering for log failed: %s\n", strerror(errno));
return errno;
return ret;
}
req = vici_begin("terminate");
if (child)
@@ -108,8 +109,9 @@ static int terminate(vici_conn_t *conn)
res = vici_submit(req, conn);
if (!res)
{
ret = errno;
fprintf(stderr, "terminate request failed: %s\n", strerror(errno));
return errno;
return ret;
}
if (format & COMMAND_FORMAT_RAW)
{
+3 -1
View File
@@ -24,6 +24,7 @@ static int version(vici_conn_t *conn)
char *arg;
bool daemon = FALSE;
command_format_options_t format = COMMAND_FORMAT_NONE;
int ret;
while (TRUE)
{
@@ -58,8 +59,9 @@ static int version(vici_conn_t *conn)
res = vici_submit(req, conn);
if (!res)
{
ret = errno;
fprintf(stderr, "version request failed: %s\n", strerror(errno));
return errno;
return ret;
}
if (format & COMMAND_FORMAT_RAW)
{