Merge branch 'update-vici-bindings'

Updates the command wrappers in all the bindings and simplifies calling
new commands (i.e. not yet wrapped) with the Python and Ruby bindings.

Fixes #3028.
This commit is contained in:
Tobias Brunner 2019-04-26 10:19:21 +02:00
commit 6b952f6921
8 changed files with 675 additions and 440 deletions

View File

@ -448,11 +448,11 @@ with the same name gets updated or replaced.
<IKE_SA config name> = { <IKE_SA config name> = {
# IKE configuration parameters with authentication and CHILD_SA # IKE configuration parameters with authentication and CHILD_SA
# subsections. Refer to swanctl.conf(5) for details. # subsections. Refer to swanctl.conf(5) for details.
}
} => { } => {
success = <yes or no> success = <yes or no>
errmsg = <error string on failure> errmsg = <error string on failure>
} }
}
### unload-conn() ### ### unload-conn() ###
@ -603,11 +603,11 @@ authority with the same name gets replaced.
<certification authority name> = { <certification authority name> = {
# certification authority parameters # certification authority parameters
# refer to swanctl.conf(5) for details. # refer to swanctl.conf(5) for details.
}
} => { } => {
success = <yes or no> success = <yes or no>
errmsg = <error string on failure> errmsg = <error string on failure>
} }
}
### unload-authority() ### ### unload-authority() ###

View File

@ -36,6 +36,10 @@ sub terminate {
return request_vars_res('terminate', @_); return request_vars_res('terminate', @_);
} }
sub rekey {
return request_vars_res('rekey', @_);
}
sub redirect { sub redirect {
return request_vars_res('redirect', @_); return request_vars_res('redirect', @_);
} }
@ -89,13 +93,33 @@ sub load_cert {
} }
sub load_key { sub load_key {
return request_vars_res('load-key', @_); return request_vars('load-key', @_);
}
sub unload_key {
return request_vars_res('unload-key', @_);
}
sub get_keys {
return request('get-keys', @_);
}
sub load_token {
return request_vars('load-token', @_);
} }
sub load_shared { sub load_shared {
return request_vars_res('load-shared', @_); return request_vars_res('load-shared', @_);
} }
sub unload_shared {
return request_vars_res('unload-shared', @_);
}
sub get_shared {
return request('get-shared', @_);
}
sub flush_certs { sub flush_certs {
return request_vars_res('flush-certs', @_); return request_vars_res('flush-certs', @_);
} }
@ -128,6 +152,14 @@ sub get_algorithms {
return request('get-algorithms', @_); return request('get-algorithms', @_);
} }
sub get_counters {
return request_vars('get-counters', @_);
}
sub reset_counters {
return request_vars_res('reset-counters', @_);
}
# Private functions # Private functions
sub request { sub request {
@ -135,6 +167,11 @@ sub request {
return $self->{'Packet'}->request($command); return $self->{'Packet'}->request($command);
} }
sub request_vars {
my ($command, $self, $vars) = @_;
return $self->{'Packet'}->request($command, $vars);
}
sub request_res { sub request_res {
my ($command, $self) = @_; my ($command, $self) = @_;
my $msg = $self->{'Packet'}->request($command); my $msg = $self->{'Packet'}->request($command);

View File

@ -0,0 +1,354 @@
class CommandWrappers(object):
def version(self):
"""Retrieve daemon and system specific version information.
:return: daemon and system specific version information
:rtype: dict
"""
return self.request("version")
def stats(self):
"""Retrieve IKE daemon statistics and load information.
:return: IKE daemon statistics and load information
:rtype: dict
"""
return self.request("stats")
def reload_settings(self):
"""Reload strongswan.conf settings and any plugins supporting reload.
"""
self.request("reload-settings")
def initiate(self, sa):
"""Initiate an SA.
:param sa: the SA to initiate
:type sa: dict
:return: generator for logs emitted as dict
:rtype: generator
"""
return self.streamed_request("initiate", "control-log", sa)
def terminate(self, sa):
"""Terminate an SA.
:param sa: the SA to terminate
:type sa: dict
:return: generator for logs emitted as dict
:rtype: generator
"""
return self.streamed_request("terminate", "control-log", sa)
def rekey(self, sa):
"""Initiate the rekeying of an SA.
.. versionadded:: 5.5.2
:param sa: the SA to rekey
:type sa: dict
:return: number of matched SAs
:rtype: dict
"""
return self.request("rekey", sa)
def redirect(self, sa):
"""Redirect an IKE_SA.
.. versionchanged:: 5.5.2
The number of matched SAs is returned.
:param sa: the SA to redirect
:type sa: dict
:return: number of matched SAs
:rtype: dict
"""
return self.request("redirect", sa)
def install(self, policy):
"""Install a trap, drop or bypass policy defined by a CHILD_SA config.
:param policy: policy to install
:type policy: dict
"""
self.request("install", policy)
def uninstall(self, policy):
"""Uninstall a trap, drop or bypass policy defined by a CHILD_SA config.
:param policy: policy to uninstall
:type policy: dict
"""
self.request("uninstall", policy)
def list_sas(self, filters=None):
"""Retrieve active IKE_SAs and associated CHILD_SAs.
:param filters: retrieve only matching IKE_SAs (optional)
:type filters: dict
:return: generator for active IKE_SAs and associated CHILD_SAs as dict
:rtype: generator
"""
return self.streamed_request("list-sas", "list-sa", filters)
def list_policies(self, filters=None):
"""Retrieve installed trap, drop and bypass policies.
:param filters: retrieve only matching policies (optional)
:type filters: dict
:return: generator for installed trap, drop and bypass policies as dict
:rtype: generator
"""
return self.streamed_request("list-policies", "list-policy",
filters)
def list_conns(self, filters=None):
"""Retrieve loaded connections.
:param filters: retrieve only matching configuration names (optional)
:type filters: dict
:return: generator for loaded connections as dict
:rtype: generator
"""
return self.streamed_request("list-conns", "list-conn",
filters)
def get_conns(self):
"""Retrieve connection names loaded exclusively over vici.
:return: connection names
:rtype: dict
"""
return self.request("get-conns")
def list_certs(self, filters=None):
"""Retrieve loaded certificates.
:param filters: retrieve only matching certificates (optional)
:type filters: dict
:return: generator for loaded certificates as dict
:rtype: generator
"""
return self.streamed_request("list-certs", "list-cert", filters)
def list_authorities(self, filters=None):
"""Retrieve loaded certification authority information.
.. versionadded:: 5.3.3
:param filters: retrieve only matching CAs (optional)
:type filters: dict
:return: generator for loaded CAs as dict
:rtype: generator
"""
return self.streamed_request("list-authorities", "list-authority",
filters)
def get_authorities(self):
"""Retrieve certification authority names loaded exclusively over vici.
:return: CA names
:rtype: dict
"""
return self.request("get-authorities")
def load_conn(self, connection):
"""Load a connection definition into the daemon.
:param connection: connection definition
:type connection: dict
"""
self.request("load-conn", connection)
def unload_conn(self, name):
"""Unload a connection definition.
:param name: connection definition name
:type name: dict
"""
self.request("unload-conn", name)
def load_cert(self, certificate):
"""Load a certificate into the daemon.
:param certificate: PEM or DER encoded certificate
:type certificate: dict
"""
self.request("load-cert", certificate)
def load_key(self, private_key):
"""Load a private key into the daemon.
.. versionchanged:: 5.5.3
The key identifier of the loaded key is returned.
:param private_key: PEM or DER encoded key
:type private_key: dict
:return: key identifier
:rtype: dict
"""
return self.request("load-key", private_key)
def unload_key(self, key_id):
"""Unload the private key with the given key identifier.
.. versionadded:: 5.5.2
:param key_id: key identifier
:type key_id: dict
"""
self.request("unload-key", key_id)
def get_keys(self):
"""Retrieve identifiers of private keys loaded exclusively over vici.
.. versionadded:: 5.5.2
:return: key identifiers
:rtype: dict
"""
return self.request("get-keys")
def load_token(self, token):
"""Load a private key located on a token into the daemon.
.. versionadded:: 5.5.2
:param token: token details
:type token: dict
:return: key identifier
:rtype: dict
"""
return self.request("load-token", token)
def load_shared(self, secret):
"""Load a shared IKE PSK, EAP or XAuth secret into the daemon.
.. versionchanged:: 5.5.2
A unique identifier may be associated with the secret.
:param secret: shared IKE PSK, EAP or XAuth secret
:type secret: dict
"""
self.request("load-shared", secret)
def unload_shared(self, identifier):
"""Unload a previously loaded shared secret by its unique identifier.
.. versionadded:: 5.5.2
:param identifier: unique identifier
:type secret: dict
"""
self.request("unload-shared", identifier)
def get_shared(self):
"""Retrieve identifiers of shared keys loaded exclusively over vici.
.. versionadded:: 5.5.2
:return: identifiers
:rtype: dict
"""
return self.request("get-shared")
def flush_certs(self, filter=None):
"""Flush the volatile certificate cache.
Flush the certificate stored temporarily in the cache. The filter
allows to flush only a certain type of certificates, e.g. CRLs.
:param filter: flush only certificates of a given type (optional)
:type filter: dict
"""
self.request("flush-certs", filter)
def clear_creds(self):
"""Clear credentials loaded over vici.
Clear all loaded certificate, private key and shared key credentials.
This affects only credentials loaded over vici, but additionally
flushes the credential cache.
"""
self.request("clear-creds")
def load_authority(self, ca):
"""Load a certification authority definition into the daemon.
:param ca: certification authority definition
:type ca: dict
"""
self.request("load-authority", ca)
def unload_authority(self, ca):
"""Unload a previously loaded certification authority by name.
:param ca: certification authority name
:type ca: dict
"""
self.request("unload-authority", ca)
def load_pool(self, pool):
"""Load a virtual IP pool.
Load an in-memory virtual IP and configuration attribute pool.
Existing pools with the same name get updated, if possible.
:param pool: virtual IP and configuration attribute pool
:type pool: dict
"""
return self.request("load-pool", pool)
def unload_pool(self, pool_name):
"""Unload a virtual IP pool.
Unload a previously loaded virtual IP and configuration attribute pool.
Unloading fails for pools with leases currently online.
:param pool_name: pool by name
:type pool_name: dict
"""
self.request("unload-pool", pool_name)
def get_pools(self, options):
"""Retrieve loaded pools.
:param options: filter by name and/or retrieve leases (optional)
:type options: dict
:return: loaded pools
:rtype: dict
"""
return self.request("get-pools", options)
def get_algorithms(self):
"""List of currently loaded algorithms and their implementation.
.. versionadded:: 5.4.0
:return: algorithms
:rtype: dict
"""
return self.request("get-algorithms")
def get_counters(self, options=None):
"""List global or connection-specific counters for several IKE events.
.. versionadded:: 5.6.1
:param options: get global counters or those of all or one connection
:type options: dict
:return: counters
:rtype: dict
"""
return self.request("get-counters", options)
def reset_counters(self, options=None):
"""Reset global or connection-specific IKE event counters.
.. versionadded:: 5.6.1
:param options: reset global counters or those of all or one connection
:type options: dict
"""
self.request("reset-counters", options)

View File

@ -1,13 +1,17 @@
"""Exception types that may be thrown by this library.""" """Exception types that may be thrown by this library."""
class DeserializationException(Exception): class DeserializationException(Exception):
"""Encountered an unexpected byte sequence or missing element type.""" """Encountered an unexpected byte sequence or missing element type."""
class SessionException(Exception): class SessionException(Exception):
"""Session request exception.""" """Session request exception."""
class CommandException(Exception): class CommandException(Exception):
"""Command result exception.""" """Command result exception."""
class EventUnknownException(Exception): class EventUnknownException(Exception):
"""Event unknown exception.""" """Event unknown exception."""

View File

@ -3,237 +3,15 @@ import socket
from .exception import SessionException, CommandException, EventUnknownException from .exception import SessionException, CommandException, EventUnknownException
from .protocol import Transport, Packet, Message from .protocol import Transport, Packet, Message
from .command_wrappers import CommandWrappers
class Session(object): class Session(CommandWrappers, object):
def __init__(self, sock=None): def __init__(self, sock=None):
if sock is None: if sock is None:
sock = socket.socket(socket.AF_UNIX) sock = socket.socket(socket.AF_UNIX)
sock.connect("/var/run/charon.vici") sock.connect("/var/run/charon.vici")
self.handler = SessionHandler(Transport(sock)) self.transport = Transport(sock)
def version(self):
"""Retrieve daemon and system specific version information.
:return: daemon and system specific version information
:rtype: dict
"""
return self.handler.request("version")
def stats(self):
"""Retrieve IKE daemon statistics and load information.
:return: IKE daemon statistics and load information
:rtype: dict
"""
return self.handler.request("stats")
def reload_settings(self):
"""Reload strongswan.conf settings and any plugins supporting reload.
"""
self.handler.request("reload-settings")
def initiate(self, sa):
"""Initiate an SA.
:param sa: the SA to initiate
:type sa: dict
:return: generator for logs emitted as dict
:rtype: generator
"""
return self.handler.streamed_request("initiate", "control-log", sa)
def terminate(self, sa):
"""Terminate an SA.
:param sa: the SA to terminate
:type sa: dict
:return: generator for logs emitted as dict
:rtype: generator
"""
return self.handler.streamed_request("terminate", "control-log", sa)
def redirect(self, sa):
"""Redirect an IKE_SA.
:param sa: the SA to redirect
:type sa: dict
"""
self.handler.request("redirect", sa)
def install(self, policy):
"""Install a trap, drop or bypass policy defined by a CHILD_SA config.
:param policy: policy to install
:type policy: dict
"""
self.handler.request("install", policy)
def uninstall(self, policy):
"""Uninstall a trap, drop or bypass policy defined by a CHILD_SA config.
:param policy: policy to uninstall
:type policy: dict
"""
self.handler.request("uninstall", policy)
def list_sas(self, filters=None):
"""Retrieve active IKE_SAs and associated CHILD_SAs.
:param filters: retrieve only matching IKE_SAs (optional)
:type filters: dict
:return: generator for active IKE_SAs and associated CHILD_SAs as dict
:rtype: generator
"""
return self.handler.streamed_request("list-sas", "list-sa", filters)
def list_policies(self, filters=None):
"""Retrieve installed trap, drop and bypass policies.
:param filters: retrieve only matching policies (optional)
:type filters: dict
:return: generator for installed trap, drop and bypass policies as dict
:rtype: generator
"""
return self.handler.streamed_request("list-policies", "list-policy",
filters)
def list_conns(self, filters=None):
"""Retrieve loaded connections.
:param filters: retrieve only matching configuration names (optional)
:type filters: dict
:return: generator for loaded connections as dict
:rtype: generator
"""
return self.handler.streamed_request("list-conns", "list-conn",
filters)
def get_conns(self):
"""Retrieve connection names loaded exclusively over vici.
:return: connection names
:rtype: dict
"""
return self.handler.request("get-conns")
def list_certs(self, filters=None):
"""Retrieve loaded certificates.
:param filters: retrieve only matching certificates (optional)
:type filters: dict
:return: generator for loaded certificates as dict
:rtype: generator
"""
return self.handler.streamed_request("list-certs", "list-cert", filters)
def load_conn(self, connection):
"""Load a connection definition into the daemon.
:param connection: connection definition
:type connection: dict
"""
self.handler.request("load-conn", connection)
def unload_conn(self, name):
"""Unload a connection definition.
:param name: connection definition name
:type name: dict
"""
self.handler.request("unload-conn", name)
def load_cert(self, certificate):
"""Load a certificate into the daemon.
:param certificate: PEM or DER encoded certificate
:type certificate: dict
"""
self.handler.request("load-cert", certificate)
def load_key(self, private_key):
"""Load a private key into the daemon.
:param private_key: PEM or DER encoded key
"""
self.handler.request("load-key", private_key)
def load_shared(self, secret):
"""Load a shared IKE PSK, EAP or XAuth secret into the daemon.
:param secret: shared IKE PSK, EAP or XAuth secret
:type secret: dict
"""
self.handler.request("load-shared", secret)
def flush_certs(self, filter=None):
"""Flush the volatile certificate cache.
Flush the certificate stored temporarily in the cache. The filter
allows to flush only a certain type of certificates, e.g. CRLs.
:param filter: flush only certificates of a given type (optional)
:type filter: dict
"""
self.handler.request("flush-certs", filter)
def clear_creds(self):
"""Clear credentials loaded over vici.
Clear all loaded certificate, private key and shared key credentials.
This affects only credentials loaded over vici, but additionally
flushes the credential cache.
"""
self.handler.request("clear-creds")
def load_pool(self, pool):
"""Load a virtual IP pool.
Load an in-memory virtual IP and configuration attribute pool.
Existing pools with the same name get updated, if possible.
:param pool: virtual IP and configuration attribute pool
:type pool: dict
"""
return self.handler.request("load-pool", pool)
def unload_pool(self, pool_name):
"""Unload a virtual IP pool.
Unload a previously loaded virtual IP and configuration attribute pool.
Unloading fails for pools with leases currently online.
:param pool_name: pool by name
:type pool_name: dict
"""
self.handler.request("unload-pool", pool_name)
def get_pools(self, options):
"""Retrieve loaded pools.
:param options: filter by name and/or retrieve leases (optional)
:type options: dict
:return: loaded pools
:rtype: dict
"""
return self.handler.request("get-pools", options)
def listen(self, event_types):
"""Register and listen for the given events.
:param event_types: event types to register
:type event_types: list
:return: generator for streamed event responses as (event_type, dict)
:rtype: generator
"""
return self.handler.listen(event_types)
class SessionHandler(object):
"""Handles client command execution requests over vici."""
def __init__(self, transport):
self.transport = transport
def _communicate(self, packet): def _communicate(self, packet):
"""Send packet over transport and parse response. """Send packet over transport and parse response.
@ -322,7 +100,7 @@ class SessionHandler(object):
if message is not None: if message is not None:
message = Message.serialize(message) message = Message.serialize(message)
self._register_unregister(event_stream_type, True); self._register_unregister(event_stream_type, True)
try: try:
packet = Packet.request(command, message) packet = Packet.request(command, message)
@ -352,7 +130,7 @@ class SessionHandler(object):
) )
finally: finally:
self._register_unregister(event_stream_type, False); self._register_unregister(event_stream_type, False)
# evaluate command result, if any # evaluate command result, if any
if "success" in command_response: if "success" in command_response:
@ -379,7 +157,8 @@ class SessionHandler(object):
response = Packet.parse(self.transport.receive()) response = Packet.parse(self.transport.receive())
if response.response_type == Packet.EVENT: if response.response_type == Packet.EVENT:
try: try:
yield response.event_type, Message.deserialize(response.payload) msg = Message.deserialize(response.payload)
yield response.event_type, msg
except GeneratorExit: except GeneratorExit:
break break

View File

@ -0,0 +1,5 @@
Naming/AccessorMethodName:
Enabled: false
Style/StringLiterals:
EnforcedStyle: double_quotes

View File

@ -3,6 +3,9 @@
# strongSwan VICI protocol. The Connection class provides a high-level # strongSwan VICI protocol. The Connection class provides a high-level
# interface to issue requests or listen for events. # interface to issue requests or listen for events.
# #
# Copyright (C) 2019 Tobias Brunner
# HSR Hochschule fuer Technik Rapperswil
#
# Copyright (C) 2014 Martin Willi # Copyright (C) 2014 Martin Willi
# Copyright (C) 2014 revosec AG # Copyright (C) 2014 revosec AG
# #
@ -25,7 +28,6 @@
# THE SOFTWARE. # THE SOFTWARE.
module Vici module Vici
## ##
# Vici specific exception all others inherit from # Vici specific exception all others inherit from
class Error < StandardError class Error < StandardError
@ -76,12 +78,10 @@ module Vici
class StopEventListening < Exception class StopEventListening < Exception
end end
## ##
# The Message class provides the low level encoding and decoding of vici # The Message class provides the low level encoding and decoding of vici
# protocol messages. Directly using this class is usually not required. # protocol messages. Directly using this class is usually not required.
class Message class Message
SECTION_START = 1 SECTION_START = 1
SECTION_END = 2 SECTION_END = 2
KEY_VALUE = 3 KEY_VALUE = 3
@ -90,8 +90,8 @@ module Vici
LIST_END = 6 LIST_END = 6
def initialize(data = "") def initialize(data = "")
if data == nil if data.nil?
@root = Hash.new() @root = {}
elsif data.is_a?(Hash) elsif data.is_a?(Hash)
@root = data @root = data
else else
@ -102,18 +102,14 @@ module Vici
## ##
# Get the raw byte encoding of an on-the-wire message # Get the raw byte encoding of an on-the-wire message
def encoding def encoding
if @encoded == nil @encoded = encode(@root) if @encoded.nil?
@encoded = encode(@root)
end
@encoded @encoded
end end
## ##
# Get the root element of the parsed ruby data structures # Get the root element of the parsed ruby data structures
def root def root
if @root == nil @root = parse(@encoded) if @root.nil?
@root = parse(@encoded)
end
@root @root
end end
@ -124,9 +120,7 @@ module Vici
end end
def encode_value(value) def encode_value(value)
if value.class != String value = value.to_s if value.class != String
value = value.to_s
end
[value.length].pack("n") << value [value.length].pack("n") << value
end end
@ -150,17 +144,12 @@ module Vici
def encode(node) def encode(node)
encoding = "" encoding = ""
node.each do |key, value| node.each do |key, value|
case value.class encoding = if value.is_a?(Hash)
when String, Fixnum, true, false encode_section(encoding, key, value)
encoding = encode_kv(encoding, key, value)
else
if value.is_a?(Hash)
encoding = encode_section(encoding, key, value)
elsif value.is_a?(Array) elsif value.is_a?(Array)
encoding = encode_list(encoding, key, value) encode_list(encoding, key, value)
else else
encoding = encode_kv(encoding, key, value) encode_kv(encoding, key, value)
end
end end
end end
encoding encoding
@ -169,30 +158,28 @@ module Vici
def parse_name(encoding) def parse_name(encoding)
len = encoding.unpack("c")[0] len = encoding.unpack("c")[0]
name = encoding[1, len] name = encoding[1, len]
return encoding[(1 + len)..-1], name [encoding[(1 + len)..-1], name]
end end
def parse_value(encoding) def parse_value(encoding)
len = encoding.unpack("n")[0] len = encoding.unpack("n")[0]
value = encoding[2, len] value = encoding[2, len]
return encoding[(2 + len)..-1], value [encoding[(2 + len)..-1], value]
end end
def parse(encoding) def parse(encoding)
stack = [Hash.new] stack = [{}]
list = nil list = nil
while encoding.length != 0 do until encoding.empty?
type = encoding.unpack("c")[0] type = encoding.unpack("c")[0]
encoding = encoding[1..-1] encoding = encoding[1..-1]
case type case type
when SECTION_START when SECTION_START
encoding, name = parse_name(encoding) encoding, name = parse_name(encoding)
stack.push(stack[-1][name] = Hash.new) stack.push(stack[-1][name] = {})
when SECTION_END when SECTION_END
if stack.length() == 1 raise ParseError, "unexpected section end" if stack.length == 1
raise ParseError, "unexpected section end" stack.pop
end
stack.pop()
when KEY_VALUE when KEY_VALUE
encoding, name = parse_name(encoding) encoding, name = parse_name(encoding)
encoding, value = parse_value(encoding) encoding, value = parse_value(encoding)
@ -202,30 +189,26 @@ module Vici
stack[-1][name] = [] stack[-1][name] = []
list = name list = name
when LIST_ITEM when LIST_ITEM
raise ParseError, "unexpected list item" if list == nil raise ParseError, "unexpected list item" if list.nil?
encoding, value = parse_value(encoding) encoding, value = parse_value(encoding)
stack[-1][list].push(value) stack[-1][list].push(value)
when LIST_END when LIST_END
raise ParseError, "unexpected list end" if list == nil raise ParseError, "unexpected list end" if list.nil?
list = nil list = nil
else else
raise ParseError, "invalid type: #{type}" raise ParseError, "invalid type: #{type}"
end end
end end
if stack.length() > 1 raise ParseError, "unexpected message end" if stack.length > 1
raise ParseError, "unexpected message end"
end
stack[0] stack[0]
end end
end end
## ##
# The Transport class implements to low level segmentation of packets # The Transport class implements to low level segmentation of packets
# to the underlying transport stream. Directly using this class is usually # to the underlying transport stream. Directly using this class is usually
# not required. # not required.
class Transport class Transport
CMD_REQUEST = 0 CMD_REQUEST = 0
CMD_RESPONSE = 1 CMD_RESPONSE = 1
CMD_UNKNOWN = 2 CMD_UNKNOWN = 2
@ -239,18 +222,16 @@ module Vici
# Create a transport layer using a provided socket for communication. # Create a transport layer using a provided socket for communication.
def initialize(socket) def initialize(socket)
@socket = socket @socket = socket
@events = Hash.new @events = {}
end end
## ##
# Receive data from socket, until len bytes read # Receive data from socket, until len bytes read
def recv_all(len) def recv_all(len)
encoding = "" encoding = ""
while encoding.length < len do while encoding.length < len
data = @socket.recv(len - encoding.length) data = @socket.recv(len - encoding.length)
if data.empty? raise TransportError, "connection closed" if data.empty?
raise TransportError, "connection closed"
end
encoding << data encoding << data
end end
encoding encoding
@ -260,9 +241,7 @@ module Vici
# Send data to socket, until all bytes sent # Send data to socket, until all bytes sent
def send_all(encoding) def send_all(encoding)
len = 0 len = 0
while len < encoding.length do len += @socket.send(encoding[len..-1], 0) while len < encoding.length
len += @socket.send(encoding[len..-1], 0)
end
end end
## ##
@ -270,12 +249,8 @@ module Vici
# specifies the message, the optional label and message get appended. # specifies the message, the optional label and message get appended.
def write(type, label, message) def write(type, label, message)
encoding = "" encoding = ""
if label encoding << label.length << label if label
encoding << label.length << label encoding << message.encoding if message
end
if message
encoding << message.encoding
end
send_all([encoding.length + 1, type].pack("Nc") + encoding) send_all([encoding.length + 1, type].pack("Nc") + encoding)
end end
@ -296,10 +271,12 @@ module Vici
else else
raise TransportError, "invalid message: #{type}" raise TransportError, "invalid message: #{type}"
end end
if encoding.length == len message = if encoding.length == len
return type, label, Message.new Message.new
else
Message.new(encoding[len..-1])
end end
return type, label, Message.new(encoding[len..-1]) [type, label, message]
end end
def dispatch_event(name, message) def dispatch_event(name, message)
@ -310,22 +287,17 @@ module Vici
def read_and_dispatch_event def read_and_dispatch_event
type, label, message = read type, label, message = read
p raise TransportError, "unexpected message: #{type}" if type != EVENT
if type == EVENT
dispatch_event(label, message) dispatch_event(label, message)
else
raise TransportError, "unexpected message: #{type}"
end
end end
def read_and_dispatch_events def read_and_dispatch_events
loop do loop do
type, label, message = read type, label, message = read
if type == EVENT return type, label, message if type != EVENT
dispatch_event(label, message) dispatch_event(label, message)
else
return type, label, message
end
end end
end end
@ -334,7 +306,7 @@ module Vici
# the reply message on success. # the reply message on success.
def request(name, message = nil) def request(name, message = nil)
write(CMD_REQUEST, name, message) write(CMD_REQUEST, name, message)
type, label, message = read_and_dispatch_events type, _label, message = read_and_dispatch_events
case type case type
when CMD_RESPONSE when CMD_RESPONSE
return message return message
@ -349,13 +321,13 @@ module Vici
# Register a handler method for the given event name # Register a handler method for the given event name
def register(name, handler) def register(name, handler)
write(EVENT_REGISTER, name, nil) write(EVENT_REGISTER, name, nil)
type, label, message = read_and_dispatch_events type, _label, _message = read_and_dispatch_events
case type case type
when EVENT_CONFIRM when EVENT_CONFIRM
if @events.has_key?(name) if @events.key?(name)
@events[name] += [handler] @events[name] += [handler]
else else
@events[name] = [handler]; @events[name] = [handler]
end end
when EVENT_UNKNOWN when EVENT_UNKNOWN
raise EventUnknownError, name raise EventUnknownError, name
@ -368,7 +340,7 @@ module Vici
# Unregister a handler method for the given event name # Unregister a handler method for the given event name
def unregister(name, handler) def unregister(name, handler)
write(EVENT_UNREGISTER, name, nil) write(EVENT_UNREGISTER, name, nil)
type, label, message = read_and_dispatch_events type, _label, _message = read_and_dispatch_events
case type case type
when EVENT_CONFIRM when EVENT_CONFIRM
@events[name] -= [handler] @events[name] -= [handler]
@ -380,7 +352,6 @@ module Vici
end end
end end
## ##
# The Connection class provides the high-level interface to monitor, configure # The Connection class provides the high-level interface to monitor, configure
# and control the IKE daemon. It takes a connected stream-oriented Socket for # and control the IKE daemon. It takes a connected stream-oriented Socket for
@ -393,19 +364,65 @@ module Vici
# Non-String values that are not a Hash nor an Array get converted with .to_s # Non-String values that are not a Hash nor an Array get converted with .to_s
# during encoding. # during encoding.
class Connection class Connection
##
# Create a connection, optionally using the given socket
def initialize(socket = nil) def initialize(socket = nil)
if socket == nil socket = UNIXSocket.new("/var/run/charon.vici") if socket.nil?
socket = UNIXSocket.new("/var/run/charon.vici")
end
@transp = Transport.new(socket) @transp = Transport.new(socket)
end end
## ##
# List matching loaded connections. The provided closure is invoked # Get daemon version information
# for each matching connection. def version
def list_conns(match = nil, &block) call("version")
call_with_event("list-conns", Message.new(match), "list-conn", &block) end
##
# Get daemon statistics and information.
def stats
call("stats")
end
##
# Reload strongswan.conf settings.
def reload_settings
call("reload-settings")
end
##
# Initiate a connection. The provided closure is invoked for each log line.
def initiate(options, &block)
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)
call_with_event("terminate", Message.new(options), "control-log", &block)
end
##
# Initiate the rekeying of an SA.
def rekey(options)
call("rekey", Message.new(options))
end
##
# Redirect an IKE_SA.
def redirect(options)
call("redirect", Message.new(options))
end
##
# Install a shunt/route policy.
def install(policy)
call("install", Message.new(policy))
end
##
# Uninstall a shunt/route policy.
def uninstall(policy)
call("uninstall", Message.new(policy))
end end
## ##
@ -423,6 +440,19 @@ module Vici
&block) &block)
end 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
##
# Get the names of connections managed by vici.
def get_conns
call("get-conns")
end
## ##
# List matching loaded certificates. The provided closure is invoked # List matching loaded certificates. The provided closure is invoked
# for each matching certificate definition. # for each matching certificate definition.
@ -430,120 +460,138 @@ module Vici
call_with_event("list-certs", Message.new(match), "list-cert", &block) call_with_event("list-certs", Message.new(match), "list-cert", &block)
end end
##
# List matching loaded certification authorities. The provided closure is
# invoked for each matching certification authority definition.
def list_authorities(match = nil, &block)
call_with_event("list-authorities", Message.new(match), "list-authority",
&block)
end
##
# Get the names of certification authorities managed by vici.
def get_authorities
call("get-authorities")
end
## ##
# Load a connection into the daemon. # Load a connection into the daemon.
def load_conn(conn) def load_conn(conn)
check_success(@transp.request("load-conn", Message.new(conn))) call("load-conn", Message.new(conn))
end end
## ##
# Unload a connection from the daemon. # Unload a connection from the daemon.
def unload_conn(conn) def unload_conn(conn)
check_success(@transp.request("unload-conn", Message.new(conn))) call("unload-conn", Message.new(conn))
end
##
# Get the names of connections managed by vici.
def get_conns()
@transp.request("get-conns").root
end
##
# Flush credential cache.
def flush_certs(match = nil)
check_success(@transp.request("flush-certs", Message.new(match)))
end
##
# Clear all loaded credentials.
def clear_creds()
check_success(@transp.request("clear-creds"))
end end
## ##
# Load a certificate into the daemon. # Load a certificate into the daemon.
def load_cert(cert) def load_cert(cert)
check_success(@transp.request("load-cert", Message.new(cert))) call("load-cert", Message.new(cert))
end end
## ##
# Load a private key into the daemon. # Load a private key into the daemon.
def load_key(key) def load_key(key)
check_success(@transp.request("load-key", Message.new(key))) call("load-key", Message.new(key))
end
##
# Unload a private key from the daemon.
def unload_key(key)
call("unload-key", Message.new(key))
end
##
# Get the identifiers of private keys loaded via vici.
def get_keys
call("get-keys")
end
##
# Load a private key located on a token into the daemon.
def load_token(token)
call("load-token", Message.new(token))
end end
## ##
# Load a shared key into the daemon. # Load a shared key into the daemon.
def load_shared(shared) def load_shared(shared)
check_success(@transp.request("load-shared", Message.new(shared))) call("load-shared", Message.new(shared))
end end
## ##
# Load a virtual IP / attribute pool # Unload a shared key from the daemon.
def unload_shared(shared)
call("unload-shared", Message.new(shared))
end
##
# Get the unique identifiers of shared keys loaded via vici.
def get_shared
call("get-shared")
end
##
# Flush credential cache.
def flush_certs(match = nil)
call("flush-certs", Message.new(match))
end
##
# Clear all loaded credentials.
def clear_creds
call("clear-creds")
end
##
# Load a certification authority into the daemon.
def load_authority(authority)
call("load-authority", Message.new(authority))
end
##
# Unload a certification authority from the daemon.
def unload_authority(authority)
call("unload-authority", Message.new(authority))
end
##
# Load a virtual IP / attribute pool into the daemon.
def load_pool(pool) def load_pool(pool)
check_success(@transp.request("load-pool", Message.new(pool))) call("load-pool", Message.new(pool))
end end
## ##
# Unload a virtual IP / attribute pool # Unload a virtual IP / attribute pool from the daemon.
def unload_pool(pool) def unload_pool(pool)
check_success(@transp.request("unload-pool", Message.new(pool))) call("unload-pool", Message.new(pool))
end end
## ##
# Get the currently loaded pools. # Get the currently loaded pools.
def get_pools(options) def get_pools(options)
@transp.request("get-pools", Message.new(options)).root call("get-pools", Message.new(options))
end end
## ##
# Initiate a connection. The provided closure is invoked for each log line. # Get currently loaded algorithms and their implementation.
def initiate(options, &block) def get_algorithms
check_success(call_with_event("initiate", Message.new(options), call("get-algorithms")
"control-log", &block))
end end
## ##
# Terminate a connection. The provided closure is invoked for each log line. # Get global or connection-specific counters for IKE events.
def terminate(options, &block) def get_counters(options = nil)
check_success(call_with_event("terminate", Message.new(options), call("get-counters", Message.new(options))
"control-log", &block))
end end
## ##
# Redirect an IKE_SA. # Reset global or connection-specific IKE event counters.
def redirect(options) def reset_counters(options = nil)
check_success(@transp.request("redirect", Message.new(options))) call("reset-counters", Message.new(options))
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 end
## ##
@ -573,6 +621,13 @@ module Vici
end end
end end
##
# Issue a command request. Checks if the reply of a command indicates
# "success", otherwise raises a CommandExecError exception.
def call(command, request = nil)
check_success(@transp.request(command, request))
end
## ##
# Issue a command request, but register for a specific event while the # Issue a command request, but register for a specific event while the
# command is active. VICI uses this mechanism to stream potentially large # command is active. VICI uses this mechanism to stream potentially large
@ -580,7 +635,7 @@ module Vici
# event messages. # event messages.
def call_with_event(command, request, event, &block) def call_with_event(command, request, event, &block)
self.class.instance_eval do self.class.instance_eval do
define_method(:call_event) do |label, message| define_method(:call_event) do |_label, message|
block.call(message.root) block.call(message.root)
end end
end end
@ -590,7 +645,7 @@ module Vici
ensure ensure
@transp.unregister(event, method(:call_event)) @transp.unregister(event, method(:call_event))
end end
reply check_success(reply)
end end
## ##
@ -598,9 +653,10 @@ module Vici
# CommandExecError exception # CommandExecError exception
def check_success(reply) def check_success(reply)
root = reply.root root = reply.root
if root["success"] != "yes" if root.key?("success") && root["success"] != "yes"
raise CommandExecError, root["errmsg"] raise CommandExecError, root["errmsg"]
end end
root root
end end
end end

View File

@ -1,15 +1,15 @@
Gem::Specification.new do |s| Gem::Specification.new do |s|
s.name = "vici" s.name = "vici"
s.version = "@GEM_VERSION@" s.version = "@GEM_VERSION@"
s.authors = ["Martin Willi"] s.authors = ["strongSwan Project"]
s.email = ["martin@strongswan.org"] s.email = ["info@strongswan.org"]
s.description = %q{ s.description = %q{
The strongSwan VICI protocol allows external application to monitor, The strongSwan VICI protocol allows external application to monitor,
configure and control the IKE daemon charon. This ruby gem provides a configure and control the IKE daemon charon. This Ruby Gem provides a
native client side implementation of the VICI protocol, well suited to native client side implementation of the VICI protocol, well suited to
script automated tasks in a relaible way. script automated tasks in a relaible way.
} }
s.summary = "Native ruby interface for strongSwan VICI" s.summary = "Native Ruby interface for strongSwan VICI"
s.homepage = "https://wiki.strongswan.org/projects/strongswan/wiki/Vici" s.homepage = "https://wiki.strongswan.org/projects/strongswan/wiki/Vici"
s.license = "MIT" s.license = "MIT"
s.files = "lib/vici.rb" s.files = "lib/vici.rb"