Merge branch 'vici-python'
Introduce a Python Egg for the vici plugin, contributed by Björn Schuberg.
This commit is contained in:
@@ -8,6 +8,7 @@ before_install:
|
|||||||
- sudo apt-get update -qq
|
- sudo apt-get update -qq
|
||||||
- sudo apt-get install -qq bison flex gperf gettext
|
- sudo apt-get install -qq bison flex gperf gettext
|
||||||
- ./scripts/test.sh deps
|
- ./scripts/test.sh deps
|
||||||
|
- ./scripts/test.sh pydeps
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- ./autogen.sh
|
- ./autogen.sh
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ strongswan-5.3.0
|
|||||||
Windows 7 IKEv2 clients, which announces its services over the tunnel if the
|
Windows 7 IKEv2 clients, which announces its services over the tunnel if the
|
||||||
negotiated IPsec policy allows it.
|
negotiated IPsec policy allows it.
|
||||||
|
|
||||||
|
- For the vici plugin a Python Egg has been added to allow Python applications
|
||||||
|
to control or monitor the IKE daemon using the VICI interface, similar to the
|
||||||
|
existing ruby gem. The Python library has been contributed by Björn Schuberg.
|
||||||
|
|
||||||
- EAP server methods now can fulfill public key constraints, such as rightcert
|
- EAP server methods now can fulfill public key constraints, such as rightcert
|
||||||
or rightca. Additionally, public key and signature constraints can be
|
or rightca. Additionally, public key and signature constraints can be
|
||||||
specified for EAP methods in the rightauth keyword. Currently the EAP-TLS and
|
specified for EAP methods in the rightauth keyword. Currently the EAP-TLS and
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ ARG_WITH_SET([mpz_powm_sec], [yes], [use the more side-channel resistant
|
|||||||
ARG_WITH_SET([dev-headers], [no], [install strongSwan development headers to directory.])
|
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([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])
|
ARG_WITH_SET([rubygemdir], ["gem environment gemdir"], [path to install ruby gems to])
|
||||||
|
ARG_WITH_SET([pythoneggdir], ["main site-packages directory"], [path to install python eggs to to])
|
||||||
|
|
||||||
if test -n "$PKG_CONFIG"; then
|
if test -n "$PKG_CONFIG"; then
|
||||||
systemdsystemunitdir_default=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)
|
systemdsystemunitdir_default=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)
|
||||||
@@ -290,6 +291,7 @@ ARG_DISBL_SET([load-warning], [disable the charon plugin load option warning i
|
|||||||
ARG_ENABL_SET([mediation], [enable IKEv2 Mediation Extension.])
|
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([unwind-backtraces],[use libunwind to create backtraces for memory leaks and segfaults.])
|
||||||
ARG_ENABL_SET([ruby-gems], [enable installation of provided ruby gems.])
|
ARG_ENABL_SET([ruby-gems], [enable installation of provided ruby gems.])
|
||||||
|
ARG_ENABL_SET([python-eggs], [enable installation of provided python eggs.])
|
||||||
# compile options
|
# compile options
|
||||||
ARG_ENABL_SET([coverage], [enable lcov coverage report generation.])
|
ARG_ENABL_SET([coverage], [enable lcov coverage report generation.])
|
||||||
ARG_ENABL_SET([leak-detective], [enable malloc hooks to find memory leaks.])
|
ARG_ENABL_SET([leak-detective], [enable malloc hooks to find memory leaks.])
|
||||||
@@ -1186,6 +1188,19 @@ if test x$ruby_gems = xtrue; then
|
|||||||
AC_SUBST(RUBYGEMDIR, "$rubygemdir")
|
AC_SUBST(RUBYGEMDIR, "$rubygemdir")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if test x$python_eggs = xtrue; then
|
||||||
|
AC_PATH_PROG([EASY_INSTALL], [easy_install], [], [$PATH:/bin:/usr/bin:/usr/local/bin])
|
||||||
|
if test x$EASY_INSTALL = x; then
|
||||||
|
AC_MSG_ERROR(Python easy_install not found)
|
||||||
|
fi
|
||||||
|
if test "x$pythoneggdir" = "xmain site-packages directory"; then
|
||||||
|
AC_SUBST(PYTHONEGGINSTALLDIR, "")
|
||||||
|
else
|
||||||
|
AC_SUBST(PYTHONEGGINSTALLDIR, "--install-dir $pythoneggdir")
|
||||||
|
fi
|
||||||
|
AC_PATH_PROG([PY_TEST], [py.test], [], [$PATH:/bin:/usr/bin:/usr/local/bin])
|
||||||
|
fi
|
||||||
|
|
||||||
# ===============================================
|
# ===============================================
|
||||||
# collect plugin list for strongSwan components
|
# collect plugin list for strongSwan components
|
||||||
# ===============================================
|
# ===============================================
|
||||||
@@ -1552,6 +1567,8 @@ AM_CONDITIONAL(USE_SVC, test x$svc = xtrue)
|
|||||||
AM_CONDITIONAL(USE_SYSTEMD, test x$systemd = xtrue)
|
AM_CONDITIONAL(USE_SYSTEMD, test x$systemd = xtrue)
|
||||||
AM_CONDITIONAL(USE_LEGACY_SYSTEMD, test -n "$systemdsystemunitdir" -a "x$systemdsystemunitdir" != xno)
|
AM_CONDITIONAL(USE_LEGACY_SYSTEMD, test -n "$systemdsystemunitdir" -a "x$systemdsystemunitdir" != xno)
|
||||||
AM_CONDITIONAL(USE_RUBY_GEMS, test x$ruby_gems = xtrue)
|
AM_CONDITIONAL(USE_RUBY_GEMS, test x$ruby_gems = xtrue)
|
||||||
|
AM_CONDITIONAL(USE_PYTHON_EGGS, test x$python_eggs = xtrue)
|
||||||
|
AM_CONDITIONAL(USE_PY_TEST, test "x$PY_TEST" != x)
|
||||||
|
|
||||||
# ========================
|
# ========================
|
||||||
# set global definitions
|
# set global definitions
|
||||||
@@ -1758,6 +1775,7 @@ AC_CONFIG_FILES([
|
|||||||
src/libcharon/plugins/stroke/Makefile
|
src/libcharon/plugins/stroke/Makefile
|
||||||
src/libcharon/plugins/vici/Makefile
|
src/libcharon/plugins/vici/Makefile
|
||||||
src/libcharon/plugins/vici/ruby/Makefile
|
src/libcharon/plugins/vici/ruby/Makefile
|
||||||
|
src/libcharon/plugins/vici/python/Makefile
|
||||||
src/libcharon/plugins/updown/Makefile
|
src/libcharon/plugins/updown/Makefile
|
||||||
src/libcharon/plugins/dhcp/Makefile
|
src/libcharon/plugins/dhcp/Makefile
|
||||||
src/libcharon/plugins/load_tester/Makefile
|
src/libcharon/plugins/load_tester/Makefile
|
||||||
|
|||||||
+7
-1
@@ -52,7 +52,8 @@ all)
|
|||||||
DEPS="$DEPS libcurl4-gnutls-dev libsoup2.4-dev libunbound-dev libldns-dev
|
DEPS="$DEPS libcurl4-gnutls-dev libsoup2.4-dev libunbound-dev libldns-dev
|
||||||
libmysqlclient-dev libsqlite3-dev clearsilver-dev libfcgi-dev
|
libmysqlclient-dev libsqlite3-dev clearsilver-dev libfcgi-dev
|
||||||
libnm-glib-dev libnm-glib-vpn-dev libpcsclite-dev libpam0g-dev
|
libnm-glib-dev libnm-glib-vpn-dev libpcsclite-dev libpam0g-dev
|
||||||
binutils-dev libunwind7-dev libjson0-dev iptables-dev"
|
binutils-dev libunwind7-dev libjson0-dev iptables-dev python-pip"
|
||||||
|
PYDEPS="pytest"
|
||||||
;;
|
;;
|
||||||
win*)
|
win*)
|
||||||
CONFIG="--disable-defaults --enable-svc --enable-ikev2
|
CONFIG="--disable-defaults --enable-svc --enable-ikev2
|
||||||
@@ -95,6 +96,11 @@ if test "$1" = "deps"; then
|
|||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if test "$1" = "pydeps"; then
|
||||||
|
test -z "$PYDEPS" || sudo pip -q install $PYDEPS
|
||||||
|
exit $?
|
||||||
|
fi
|
||||||
|
|
||||||
CONFIG="$CONFIG
|
CONFIG="$CONFIG
|
||||||
--enable-silent-rules
|
--enable-silent-rules
|
||||||
--enable-test-vectors
|
--enable-test-vectors
|
||||||
|
|||||||
@@ -74,3 +74,7 @@ SUBDIRS =
|
|||||||
if USE_RUBY_GEMS
|
if USE_RUBY_GEMS
|
||||||
SUBDIRS += ruby
|
SUBDIRS += ruby
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if USE_PYTHON_EGGS
|
||||||
|
SUBDIRS += python
|
||||||
|
endif
|
||||||
|
|||||||
@@ -821,9 +821,9 @@ during encoding.
|
|||||||
|
|
||||||
## Connecting to the daemon ##
|
## Connecting to the daemon ##
|
||||||
|
|
||||||
To create a connection to the daemon, a socket must be passed to the
|
To create a connection to the daemon, a socket can be passed to the
|
||||||
_Connection_ constructor. There is no default, but on Unix systems usually
|
_Connection_ constructor. If none is passed, a default Unix socket at
|
||||||
a Unix socket over _/var/run/charon.vici_ is used:
|
_/var/run/charon.vici_ is used:
|
||||||
|
|
||||||
require "vici"
|
require "vici"
|
||||||
require "socket"
|
require "socket"
|
||||||
@@ -855,3 +855,73 @@ _list-conns_ command and implicitly the _list-conn_ event:
|
|||||||
|
|
||||||
For more details about the ruby gem refer to the comments in the gem source
|
For more details about the ruby gem refer to the comments in the gem source
|
||||||
code or the generated documentation.
|
code or the generated documentation.
|
||||||
|
|
||||||
|
# vici Python egg #
|
||||||
|
|
||||||
|
The _vici Python egg_ is a pure Python implementation of the VICI protocol to
|
||||||
|
implement client applications. It is provided in the _python_ subdirectory, and
|
||||||
|
gets built and installed if strongSwan has been _./configure_'d with
|
||||||
|
_--enable-vici_ and _--enable-python-eggs_.
|
||||||
|
|
||||||
|
The _vici_ module provides a _Session()_ constructor for a high level interface,
|
||||||
|
the underlying classes are usually not required to build Python applications
|
||||||
|
using VICI. The _Session_ class provides methods for the supported VICI
|
||||||
|
commands.
|
||||||
|
|
||||||
|
To represent the VICI message data tree, the library converts the binary
|
||||||
|
encoding to Python data types. The _Session_ class takes and returns Python
|
||||||
|
objects for the exchanged message data:
|
||||||
|
* Sections get encoded as OrderedDict, containing other sections, or
|
||||||
|
* Key/Values, where the values are strings as dictionary values
|
||||||
|
* Lists get encoded as Python Lists with string values
|
||||||
|
Values that do not conform to Python dict or list get converted to strings using
|
||||||
|
str().
|
||||||
|
|
||||||
|
## Connecting to the daemon ##
|
||||||
|
|
||||||
|
To create a connection to the daemon, a socket can be passed to the _Session_
|
||||||
|
constructor. If none is passed, a default Unix socket at _/var/run/charon.vici_
|
||||||
|
is used:
|
||||||
|
|
||||||
|
import vici
|
||||||
|
import socket
|
||||||
|
|
||||||
|
s = socket.socket(socket.AF_UNIX)
|
||||||
|
s.connect("/var/run/charon.vici")
|
||||||
|
v = vici.Session(s)
|
||||||
|
|
||||||
|
## A simple client request ##
|
||||||
|
|
||||||
|
An example to print the daemon version information is as simple as:
|
||||||
|
|
||||||
|
ver = v.version()
|
||||||
|
|
||||||
|
print "{daemon} {version} ({sysname}, {release}, {machine})".format(**ver)
|
||||||
|
|
||||||
|
## A request with response iteration ##
|
||||||
|
|
||||||
|
The _Session_ class returns an iterable Python generator for streamed events to
|
||||||
|
continuously stream objects to the caller. The following example lists all
|
||||||
|
loaded connections using the _list-conns_ command and implicitly the _list-conn_
|
||||||
|
event:
|
||||||
|
|
||||||
|
for conn in v.list_conns():
|
||||||
|
for key in conn:
|
||||||
|
print key
|
||||||
|
|
||||||
|
Please note that if the returned generator is not iterated completely, it must
|
||||||
|
be closed using _close()_. This is implicitly done when breaking from a loop,
|
||||||
|
but an explicit call may be required when directly iterating the generator with
|
||||||
|
_next()_.
|
||||||
|
|
||||||
|
## Sorting in dictionaries ##
|
||||||
|
|
||||||
|
In VICI, in some message trees the order of objects in dictionary matters. In
|
||||||
|
contrast to ruby Hashes, Python dictionaries do not preserve order of added
|
||||||
|
objects. It is therefore recommended to use OrderedDicts instead of the default
|
||||||
|
dictionaries. Objects returned by the library use OrderedDicts.
|
||||||
|
|
||||||
|
## API documentation ##
|
||||||
|
|
||||||
|
For more details about the Python egg refer to the comments in the Python source
|
||||||
|
code.
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
*.pyc
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
vici.egg-info
|
||||||
|
setup.py
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2015 Björn Schuberg
|
||||||
|
|
||||||
|
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.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
include LICENSE
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
EXTRA_DIST = LICENSE MANIFEST.in \
|
||||||
|
setup.py.in \
|
||||||
|
vici/test/__init__.py \
|
||||||
|
vici/test/test_protocol.py \
|
||||||
|
vici/__init__.py \
|
||||||
|
vici/compat.py \
|
||||||
|
vici/exception.py \
|
||||||
|
vici/protocol.py \
|
||||||
|
vici/session.py
|
||||||
|
|
||||||
|
setup.py: $(srcdir)/setup.py.in
|
||||||
|
$(AM_V_GEN) sed \
|
||||||
|
-e "s:@EGG_VERSION@:$(PACKAGE_VERSION):" \
|
||||||
|
$(srcdir)/setup.py.in > $@
|
||||||
|
|
||||||
|
all-local: dist/vici-$(PACKAGE_VERSION)-py$(PYTHON_VERSION).egg
|
||||||
|
|
||||||
|
dist/vici-$(PACKAGE_VERSION)-py$(PYTHON_VERSION).egg: $(EXTRA_DIST) setup.py
|
||||||
|
(cd $(srcdir); $(PYTHON) setup.py bdist_egg \
|
||||||
|
-b $(shell readlink -f $(builddir))/build \
|
||||||
|
-d $(shell readlink -f $(builddir))/dist)
|
||||||
|
|
||||||
|
clean-local: setup.py
|
||||||
|
$(PYTHON) setup.py clean -a
|
||||||
|
rm -rf vici.egg-info dist setup.py
|
||||||
|
|
||||||
|
install-exec-local: dist/vici-$(PACKAGE_VERSION)-py$(PYTHON_VERSION).egg
|
||||||
|
$(EASY_INSTALL) $(PYTHONEGGINSTALLDIR) \
|
||||||
|
dist/vici-$(PACKAGE_VERSION)-py$(PYTHON_VERSION).egg
|
||||||
|
|
||||||
|
if USE_PY_TEST
|
||||||
|
TESTS = $(PY_TEST)
|
||||||
|
endif
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
|
||||||
|
long_description = (
|
||||||
|
"The strongSwan VICI protocol allows external application to monitor, "
|
||||||
|
"configure and control the IKE daemon charon. This python package provides "
|
||||||
|
"a native client side implementation of the VICI protocol, well suited to "
|
||||||
|
"script automated tasks in a reliable way."
|
||||||
|
)
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="vici",
|
||||||
|
version="@EGG_VERSION@",
|
||||||
|
description="Native python interface for strongSwan VICI",
|
||||||
|
author="Bjorn Schuberg",
|
||||||
|
url="https://wiki.strongswan.org/projects/strongswan/wiki/Vici",
|
||||||
|
license="MIT",
|
||||||
|
packages=["vici"],
|
||||||
|
long_description=long_description,
|
||||||
|
include_package_data=True,
|
||||||
|
classifiers=(
|
||||||
|
"Development Status :: 3 - Alpha",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"Intended Audience :: System Administrators",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Natural Language :: English",
|
||||||
|
"Programming Language :: Python :: 2.7",
|
||||||
|
"Programming Language :: Python :: 3.2",
|
||||||
|
"Programming Language :: Python :: 3.3",
|
||||||
|
"Programming Language :: Python :: 3.4",
|
||||||
|
"Topic :: Security",
|
||||||
|
"Topic :: Software Development :: Libraries",
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
from .session import Session
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Help functions for compatibility between python version 2 and 3
|
||||||
|
|
||||||
|
|
||||||
|
# From http://legacy.python.org/dev/peps/pep-0469
|
||||||
|
try:
|
||||||
|
dict.iteritems
|
||||||
|
except AttributeError:
|
||||||
|
# python 3
|
||||||
|
def iteritems(d):
|
||||||
|
return iter(d.items())
|
||||||
|
else:
|
||||||
|
# python 2
|
||||||
|
def iteritems(d):
|
||||||
|
return d.iteritems()
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
"""Exception types that may be thrown by this library."""
|
||||||
|
|
||||||
|
class DeserializationException(Exception):
|
||||||
|
"""Encountered an unexpected byte sequence or missing element type."""
|
||||||
|
|
||||||
|
class SessionException(Exception):
|
||||||
|
"""Session request exception."""
|
||||||
|
|
||||||
|
class CommandException(Exception):
|
||||||
|
"""Command result exception."""
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
import io
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from .compat import iteritems
|
||||||
|
from .exception import DeserializationException
|
||||||
|
|
||||||
|
|
||||||
|
class Transport(object):
|
||||||
|
HEADER_LENGTH = 4
|
||||||
|
MAX_SEGMENT = 512 * 1024
|
||||||
|
|
||||||
|
def __init__(self, sock):
|
||||||
|
self.socket = sock
|
||||||
|
|
||||||
|
def send(self, packet):
|
||||||
|
self.socket.sendall(struct.pack("!I", len(packet)) + packet)
|
||||||
|
|
||||||
|
def receive(self):
|
||||||
|
raw_length = self.socket.recv(self.HEADER_LENGTH)
|
||||||
|
length, = struct.unpack("!I", raw_length)
|
||||||
|
payload = self.socket.recv(length)
|
||||||
|
return payload
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.socket.shutdown(socket.SHUT_RDWR)
|
||||||
|
self.socket.close()
|
||||||
|
|
||||||
|
|
||||||
|
class Packet(object):
|
||||||
|
CMD_REQUEST = 0 # Named request message
|
||||||
|
CMD_RESPONSE = 1 # Unnamed response message for a request
|
||||||
|
CMD_UNKNOWN = 2 # Unnamed response if requested command is unknown
|
||||||
|
EVENT_REGISTER = 3 # Named event registration request
|
||||||
|
EVENT_UNREGISTER = 4 # Named event de-registration request
|
||||||
|
EVENT_CONFIRM = 5 # Unnamed confirmation for event (de-)registration
|
||||||
|
EVENT_UNKNOWN = 6 # Unnamed response if event (de-)registration failed
|
||||||
|
EVENT = 7 # Named event message
|
||||||
|
|
||||||
|
ParsedPacket = namedtuple(
|
||||||
|
"ParsedPacket",
|
||||||
|
["response_type", "payload"]
|
||||||
|
)
|
||||||
|
|
||||||
|
ParsedEventPacket = namedtuple(
|
||||||
|
"ParsedEventPacket",
|
||||||
|
["response_type", "event_type", "payload"]
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _named_request(cls, request_type, request, message=None):
|
||||||
|
request = request.encode()
|
||||||
|
payload = struct.pack("!BB", request_type, len(request)) + request
|
||||||
|
if message is not None:
|
||||||
|
return payload + message
|
||||||
|
else:
|
||||||
|
return payload
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def request(cls, command, message=None):
|
||||||
|
return cls._named_request(cls.CMD_REQUEST, command, message)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register_event(cls, event_type):
|
||||||
|
return cls._named_request(cls.EVENT_REGISTER, event_type)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def unregister_event(cls, event_type):
|
||||||
|
return cls._named_request(cls.EVENT_UNREGISTER, event_type)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse(cls, packet):
|
||||||
|
stream = FiniteStream(packet)
|
||||||
|
response_type, = struct.unpack("!B", stream.read(1))
|
||||||
|
|
||||||
|
if response_type == cls.EVENT:
|
||||||
|
length, = struct.unpack("!B", stream.read(1))
|
||||||
|
event_type = stream.read(length)
|
||||||
|
return cls.ParsedEventPacket(response_type, event_type, stream)
|
||||||
|
else:
|
||||||
|
return cls.ParsedPacket(response_type, stream)
|
||||||
|
|
||||||
|
|
||||||
|
class Message(object):
|
||||||
|
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 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
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def serialize(cls, message):
|
||||||
|
def encode_named_type(marker, name):
|
||||||
|
name = name.encode()
|
||||||
|
return struct.pack("!BB", marker, len(name)) + name
|
||||||
|
|
||||||
|
def encode_blob(value):
|
||||||
|
if not isinstance(value, bytes):
|
||||||
|
value = str(value).encode()
|
||||||
|
return struct.pack("!H", len(value)) + value
|
||||||
|
|
||||||
|
def serialize_list(lst):
|
||||||
|
segment = bytes()
|
||||||
|
for item in lst:
|
||||||
|
segment += struct.pack("!B", cls.LIST_ITEM) + encode_blob(item)
|
||||||
|
return segment
|
||||||
|
|
||||||
|
def serialize_dict(d):
|
||||||
|
segment = bytes()
|
||||||
|
for key, value in iteritems(d):
|
||||||
|
if isinstance(value, dict):
|
||||||
|
segment += (
|
||||||
|
encode_named_type(cls.SECTION_START, key)
|
||||||
|
+ serialize_dict(value)
|
||||||
|
+ struct.pack("!B", cls.SECTION_END)
|
||||||
|
)
|
||||||
|
elif isinstance(value, list):
|
||||||
|
segment += (
|
||||||
|
encode_named_type(cls.LIST_START, key)
|
||||||
|
+ serialize_list(value)
|
||||||
|
+ struct.pack("!B", cls.LIST_END)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
segment += (
|
||||||
|
encode_named_type(cls.KEY_VALUE, key)
|
||||||
|
+ encode_blob(value)
|
||||||
|
)
|
||||||
|
return segment
|
||||||
|
|
||||||
|
return serialize_dict(message)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def deserialize(cls, stream):
|
||||||
|
def decode_named_type(stream):
|
||||||
|
length, = struct.unpack("!B", stream.read(1))
|
||||||
|
return stream.read(length).decode()
|
||||||
|
|
||||||
|
def decode_blob(stream):
|
||||||
|
length, = struct.unpack("!H", stream.read(2))
|
||||||
|
return stream.read(length)
|
||||||
|
|
||||||
|
def decode_list_item(stream):
|
||||||
|
marker, = struct.unpack("!B", stream.read(1))
|
||||||
|
while marker == cls.LIST_ITEM:
|
||||||
|
yield decode_blob(stream)
|
||||||
|
marker, = struct.unpack("!B", stream.read(1))
|
||||||
|
|
||||||
|
if marker != cls.LIST_END:
|
||||||
|
raise DeserializationException(
|
||||||
|
"Expected end of list at {pos}".format(pos=stream.tell())
|
||||||
|
)
|
||||||
|
|
||||||
|
section = OrderedDict()
|
||||||
|
section_stack = []
|
||||||
|
while stream.has_more():
|
||||||
|
element_type, = struct.unpack("!B", stream.read(1))
|
||||||
|
if element_type == cls.SECTION_START:
|
||||||
|
section_name = decode_named_type(stream)
|
||||||
|
new_section = OrderedDict()
|
||||||
|
section[section_name] = new_section
|
||||||
|
section_stack.append(section)
|
||||||
|
section = new_section
|
||||||
|
|
||||||
|
elif element_type == cls.LIST_START:
|
||||||
|
list_name = decode_named_type(stream)
|
||||||
|
section[list_name] = [item for item in decode_list_item(stream)]
|
||||||
|
|
||||||
|
elif element_type == cls.KEY_VALUE:
|
||||||
|
key = decode_named_type(stream)
|
||||||
|
section[key] = decode_blob(stream)
|
||||||
|
|
||||||
|
elif element_type == cls.SECTION_END:
|
||||||
|
if len(section_stack):
|
||||||
|
section = section_stack.pop()
|
||||||
|
else:
|
||||||
|
raise DeserializationException(
|
||||||
|
"Unexpected end of section at {pos}".format(
|
||||||
|
pos=stream.tell()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(section_stack):
|
||||||
|
raise DeserializationException("Expected end of section")
|
||||||
|
return section
|
||||||
|
|
||||||
|
|
||||||
|
class FiniteStream(io.BytesIO):
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.getvalue())
|
||||||
|
|
||||||
|
def has_more(self):
|
||||||
|
return self.tell() < len(self)
|
||||||
@@ -0,0 +1,327 @@
|
|||||||
|
import collections
|
||||||
|
import socket
|
||||||
|
|
||||||
|
from .exception import SessionException, CommandException
|
||||||
|
from .protocol import Transport, Packet, Message
|
||||||
|
|
||||||
|
|
||||||
|
class Session(object):
|
||||||
|
def __init__(self, sock=None):
|
||||||
|
if sock is None:
|
||||||
|
sock = socket.socket(socket.AF_UNIX)
|
||||||
|
sock.connect("/var/run/charon.vici")
|
||||||
|
self.handler = SessionHandler(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 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 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):
|
||||||
|
"""Retrieve loaded pools.
|
||||||
|
|
||||||
|
:return: loaded pools
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
return self.handler.request("get-pools")
|
||||||
|
|
||||||
|
|
||||||
|
class SessionHandler(object):
|
||||||
|
"""Handles client command execution requests over vici."""
|
||||||
|
|
||||||
|
def __init__(self, transport):
|
||||||
|
self.transport = transport
|
||||||
|
|
||||||
|
def _communicate(self, packet):
|
||||||
|
"""Send packet over transport and parse response.
|
||||||
|
|
||||||
|
:param packet: packet to send
|
||||||
|
:type packet: :py:class:`vici.protocol.Packet`
|
||||||
|
:return: parsed packet in a tuple with message type and payload
|
||||||
|
:rtype: :py:class:`collections.namedtuple`
|
||||||
|
"""
|
||||||
|
self.transport.send(packet)
|
||||||
|
return Packet.parse(self.transport.receive())
|
||||||
|
|
||||||
|
def request(self, command, message=None):
|
||||||
|
"""Send request with an optional message.
|
||||||
|
|
||||||
|
:param command: command to send
|
||||||
|
:type command: str
|
||||||
|
:param message: message (optional)
|
||||||
|
:type message: str
|
||||||
|
:return: command result
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
if message is not None:
|
||||||
|
message = Message.serialize(message)
|
||||||
|
packet = Packet.request(command, message)
|
||||||
|
response = self._communicate(packet)
|
||||||
|
|
||||||
|
if response.response_type != Packet.CMD_RESPONSE:
|
||||||
|
raise SessionException(
|
||||||
|
"Unexpected response type {type}, "
|
||||||
|
"expected '{response}' (CMD_RESPONSE)".format(
|
||||||
|
type=response.response_type,
|
||||||
|
response=Packet.CMD_RESPONSE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
command_response = Message.deserialize(response.payload)
|
||||||
|
if "success" in command_response:
|
||||||
|
if command_response["success"] != b"yes":
|
||||||
|
raise CommandException(
|
||||||
|
"Command failed: {errmsg}".format(
|
||||||
|
errmsg=command_response["errmsg"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return command_response
|
||||||
|
|
||||||
|
def streamed_request(self, command, event_stream_type, message=None):
|
||||||
|
"""Send command request and collect and return all emitted events.
|
||||||
|
|
||||||
|
:param command: command to send
|
||||||
|
:type command: str
|
||||||
|
:param event_stream_type: event type emitted on command execution
|
||||||
|
:type event_stream_type: str
|
||||||
|
:param message: message (optional)
|
||||||
|
:type message: str
|
||||||
|
:return: generator for streamed event responses as dict
|
||||||
|
:rtype: generator
|
||||||
|
"""
|
||||||
|
if message is not None:
|
||||||
|
message = Message.serialize(message)
|
||||||
|
|
||||||
|
# subscribe to event stream
|
||||||
|
packet = Packet.register_event(event_stream_type)
|
||||||
|
response = self._communicate(packet)
|
||||||
|
|
||||||
|
if response.response_type != Packet.EVENT_CONFIRM:
|
||||||
|
raise SessionException(
|
||||||
|
"Unexpected response type {type}, "
|
||||||
|
"expected '{confirm}' (EVENT_CONFIRM)".format(
|
||||||
|
type=response.response_type,
|
||||||
|
confirm=Packet.EVENT_CONFIRM,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# issue command, and read any event messages
|
||||||
|
packet = Packet.request(command, message)
|
||||||
|
self.transport.send(packet)
|
||||||
|
exited = False
|
||||||
|
while True:
|
||||||
|
response = Packet.parse(self.transport.receive())
|
||||||
|
if response.response_type == Packet.EVENT:
|
||||||
|
if not exited:
|
||||||
|
try:
|
||||||
|
yield Message.deserialize(response.payload)
|
||||||
|
except GeneratorExit:
|
||||||
|
exited = True
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
if response.response_type == Packet.CMD_RESPONSE:
|
||||||
|
command_response = Message.deserialize(response.payload)
|
||||||
|
else:
|
||||||
|
raise SessionException(
|
||||||
|
"Unexpected response type {type}, "
|
||||||
|
"expected '{response}' (CMD_RESPONSE)".format(
|
||||||
|
type=response.response_type,
|
||||||
|
response=Packet.CMD_RESPONSE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# unsubscribe from event stream
|
||||||
|
packet = Packet.unregister_event(event_stream_type)
|
||||||
|
response = self._communicate(packet)
|
||||||
|
if response.response_type != Packet.EVENT_CONFIRM:
|
||||||
|
raise SessionException(
|
||||||
|
"Unexpected response type {type}, "
|
||||||
|
"expected '{confirm}' (EVENT_CONFIRM)".format(
|
||||||
|
type=response.response_type,
|
||||||
|
confirm=Packet.EVENT_CONFIRM,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# evaluate command result, if any
|
||||||
|
if "success" in command_response:
|
||||||
|
if command_response["success"] != b"yes":
|
||||||
|
raise CommandException(
|
||||||
|
"Command failed: {errmsg}".format(
|
||||||
|
errmsg=command_response["errmsg"]
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from ..protocol import Packet, Message, FiniteStream
|
||||||
|
from ..exception import DeserializationException
|
||||||
|
|
||||||
|
|
||||||
|
class TestPacket(object):
|
||||||
|
# test data definitions for outgoing packet types
|
||||||
|
cmd_request = b"\x00\x0c" b"command_type"
|
||||||
|
cmd_request_msg = b"\x00\x07" b"command" b"payload"
|
||||||
|
event_register = b"\x03\x0a" b"event_type"
|
||||||
|
event_unregister = b"\x04\x0a" b"event_type"
|
||||||
|
|
||||||
|
# test data definitions for incoming packet types
|
||||||
|
cmd_response = b"\x01" b"reply"
|
||||||
|
cmd_unknown = b"\x02"
|
||||||
|
event_confirm = b"\x05"
|
||||||
|
event_unknown = b"\x06"
|
||||||
|
event = b"\x07\x03" b"log" b"message"
|
||||||
|
|
||||||
|
def test_request(self):
|
||||||
|
assert Packet.request("command_type") == self.cmd_request
|
||||||
|
assert Packet.request("command", b"payload") == self.cmd_request_msg
|
||||||
|
|
||||||
|
def test_register_event(self):
|
||||||
|
assert Packet.register_event("event_type") == self.event_register
|
||||||
|
|
||||||
|
def test_unregister_event(self):
|
||||||
|
assert Packet.unregister_event("event_type") == self.event_unregister
|
||||||
|
|
||||||
|
def test_parse(self):
|
||||||
|
parsed_cmd_response = Packet.parse(self.cmd_response)
|
||||||
|
assert parsed_cmd_response.response_type == Packet.CMD_RESPONSE
|
||||||
|
assert parsed_cmd_response.payload.getvalue() == self.cmd_response
|
||||||
|
|
||||||
|
parsed_cmd_unknown = Packet.parse(self.cmd_unknown)
|
||||||
|
assert parsed_cmd_unknown.response_type == Packet.CMD_UNKNOWN
|
||||||
|
assert parsed_cmd_unknown.payload.getvalue() == self.cmd_unknown
|
||||||
|
|
||||||
|
parsed_event_confirm = Packet.parse(self.event_confirm)
|
||||||
|
assert parsed_event_confirm.response_type == Packet.EVENT_CONFIRM
|
||||||
|
assert parsed_event_confirm.payload.getvalue() == self.event_confirm
|
||||||
|
|
||||||
|
parsed_event_unknown = Packet.parse(self.event_unknown)
|
||||||
|
assert parsed_event_unknown.response_type == Packet.EVENT_UNKNOWN
|
||||||
|
assert parsed_event_unknown.payload.getvalue() == self.event_unknown
|
||||||
|
|
||||||
|
parsed_event = Packet.parse(self.event)
|
||||||
|
assert parsed_event.response_type == Packet.EVENT
|
||||||
|
assert parsed_event.payload.getvalue() == self.event
|
||||||
|
|
||||||
|
|
||||||
|
class TestMessage(object):
|
||||||
|
"""Message (de)serialization test."""
|
||||||
|
|
||||||
|
# data definitions for test of de(serialization)
|
||||||
|
# serialized messages holding a section
|
||||||
|
ser_sec_unclosed = b"\x01\x08unclosed"
|
||||||
|
ser_sec_single = b"\x01\x07section\x02"
|
||||||
|
ser_sec_nested = b"\x01\x05outer\x01\x0asubsection\x02\x02"
|
||||||
|
|
||||||
|
# serialized messages holding a list
|
||||||
|
ser_list_invalid = b"\x04\x07invalid\x05\x00\x02e1\x02\x03sec\x06"
|
||||||
|
ser_list_0_item = b"\x04\x05empty\x06"
|
||||||
|
ser_list_1_item = b"\x04\x01l\x05\x00\x02e1\x06"
|
||||||
|
ser_list_2_item = b"\x04\x01l\x05\x00\x02e1\x05\x00\x02e2\x06"
|
||||||
|
|
||||||
|
# serialized messages with key value pairs
|
||||||
|
ser_kv_pair = b"\x03\x03key\x00\x05value"
|
||||||
|
ser_kv_zero = b"\x03\x0azerolength\x00\x00"
|
||||||
|
|
||||||
|
# deserialized messages holding a section
|
||||||
|
des_sec_single = { "section": {} }
|
||||||
|
des_sec_nested = { "outer": { "subsection": {} } }
|
||||||
|
|
||||||
|
# deserialized messages holding a list
|
||||||
|
des_list_0_item = { "empty": [] }
|
||||||
|
des_list_1_item = { "l": [ b"e1" ] }
|
||||||
|
des_list_2_item = { "l": [ b"e1", b"e2" ] }
|
||||||
|
|
||||||
|
# deserialized messages with key value pairs
|
||||||
|
des_kv_pair = { "key": b"value" }
|
||||||
|
des_kv_zero = { "zerolength": b"" }
|
||||||
|
|
||||||
|
def test_section_serialization(self):
|
||||||
|
assert Message.serialize(self.des_sec_single) == self.ser_sec_single
|
||||||
|
assert Message.serialize(self.des_sec_nested) == self.ser_sec_nested
|
||||||
|
|
||||||
|
def test_list_serialization(self):
|
||||||
|
assert Message.serialize(self.des_list_0_item) == self.ser_list_0_item
|
||||||
|
assert Message.serialize(self.des_list_1_item) == self.ser_list_1_item
|
||||||
|
assert Message.serialize(self.des_list_2_item) == self.ser_list_2_item
|
||||||
|
|
||||||
|
def test_key_serialization(self):
|
||||||
|
assert Message.serialize(self.des_kv_pair) == self.ser_kv_pair
|
||||||
|
assert Message.serialize(self.des_kv_zero) == self.ser_kv_zero
|
||||||
|
|
||||||
|
def test_section_deserialization(self):
|
||||||
|
single = Message.deserialize(FiniteStream(self.ser_sec_single))
|
||||||
|
nested = Message.deserialize(FiniteStream(self.ser_sec_nested))
|
||||||
|
|
||||||
|
assert single == self.des_sec_single
|
||||||
|
assert nested == self.des_sec_nested
|
||||||
|
|
||||||
|
with pytest.raises(DeserializationException):
|
||||||
|
Message.deserialize(FiniteStream(self.ser_sec_unclosed))
|
||||||
|
|
||||||
|
def test_list_deserialization(self):
|
||||||
|
l0 = Message.deserialize(FiniteStream(self.ser_list_0_item))
|
||||||
|
l1 = Message.deserialize(FiniteStream(self.ser_list_1_item))
|
||||||
|
l2 = Message.deserialize(FiniteStream(self.ser_list_2_item))
|
||||||
|
|
||||||
|
assert l0 == self.des_list_0_item
|
||||||
|
assert l1 == self.des_list_1_item
|
||||||
|
assert l2 == self.des_list_2_item
|
||||||
|
|
||||||
|
with pytest.raises(DeserializationException):
|
||||||
|
Message.deserialize(FiniteStream(self.ser_list_invalid))
|
||||||
|
|
||||||
|
def test_key_deserialization(self):
|
||||||
|
pair = Message.deserialize(FiniteStream(self.ser_kv_pair))
|
||||||
|
zerolength = Message.deserialize(FiniteStream(self.ser_kv_zero))
|
||||||
|
|
||||||
|
assert pair == self.des_kv_pair
|
||||||
|
assert zerolength == self.des_kv_zero
|
||||||
|
|
||||||
|
def test_roundtrip(self):
|
||||||
|
message = {
|
||||||
|
"key1": "value1",
|
||||||
|
"section1": {
|
||||||
|
"sub-section": {
|
||||||
|
"key2": b"value2",
|
||||||
|
},
|
||||||
|
"list1": [ "item1", "item2" ],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
serialized_message = FiniteStream(Message.serialize(message))
|
||||||
|
deserialized_message = Message.deserialize(serialized_message)
|
||||||
|
|
||||||
|
# ensure that list items and key values remain as undecoded bytes
|
||||||
|
deserialized_section = deserialized_message["section1"]
|
||||||
|
assert deserialized_message["key1"] == b"value1"
|
||||||
|
assert deserialized_section["sub-section"]["key2"] == b"value2"
|
||||||
|
assert deserialized_section["list1"] == [ b"item1", b"item2" ]
|
||||||
@@ -5,7 +5,7 @@ vici.gemspec: $(srcdir)/vici.gemspec.in
|
|||||||
-e "s:@GEM_VERSION@:$(PACKAGE_VERSION):" \
|
-e "s:@GEM_VERSION@:$(PACKAGE_VERSION):" \
|
||||||
$(srcdir)/vici.gemspec.in > $@
|
$(srcdir)/vici.gemspec.in > $@
|
||||||
|
|
||||||
vici-$(PACKAGE_VERSION).gem: vici.gemspec
|
vici-$(PACKAGE_VERSION).gem: vici.gemspec $(EXTRA_DIST)
|
||||||
(cd $(srcdir); $(GEM) build $(abs_builddir)/vici.gemspec)
|
(cd $(srcdir); $(GEM) build $(abs_builddir)/vici.gemspec)
|
||||||
[ "$(srcdir)" = "$(builddir)" ] || \
|
[ "$(srcdir)" = "$(builddir)" ] || \
|
||||||
mv $(srcdir)/vici-$(PACKAGE_VERSION).gem $(builddir)
|
mv $(srcdir)/vici-$(PACKAGE_VERSION).gem $(builddir)
|
||||||
|
|||||||
@@ -390,7 +390,10 @@ module Vici
|
|||||||
# during encoding.
|
# during encoding.
|
||||||
class Connection
|
class Connection
|
||||||
|
|
||||||
def initialize(socket)
|
def initialize(socket = nil)
|
||||||
|
if socket == nil
|
||||||
|
socket = UNIXSocket.new("/var/run/charon.vici")
|
||||||
|
end
|
||||||
@transp = Transport.new(socket)
|
@transp = Transport.new(socket)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,8 @@ METHOD(vici_builder_t, add, void,
|
|||||||
|
|
||||||
if (value.len > 0xffff)
|
if (value.len > 0xffff)
|
||||||
{
|
{
|
||||||
|
DBG1(DBG_ENC, "vici value exceeds size limit (%zu > %u)",
|
||||||
|
value.len, 0xffff);
|
||||||
this->error++;
|
this->error++;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -125,22 +127,56 @@ METHOD(vici_builder_t, add, void,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
METHOD(vici_builder_t, vadd_kv, void,
|
/**
|
||||||
private_vici_builder_t *this, char *key, char *fmt, va_list args)
|
* Add a list item or a key/value, if key given
|
||||||
|
*/
|
||||||
|
static void vadd_kv_or_li(private_vici_builder_t *this, char *key,
|
||||||
|
char *fmt, va_list args)
|
||||||
{
|
{
|
||||||
char buf[2048];
|
u_char buf[512];
|
||||||
|
chunk_t value;
|
||||||
ssize_t len;
|
ssize_t len;
|
||||||
|
va_list copy;
|
||||||
|
|
||||||
len = vsnprintf(buf, sizeof(buf), fmt, args);
|
va_copy(copy, args);
|
||||||
if (len < 0 || len >= sizeof(buf))
|
len = vsnprintf(buf, sizeof(buf), fmt, copy);
|
||||||
|
va_end(copy);
|
||||||
|
if (len >= sizeof(buf))
|
||||||
{
|
{
|
||||||
DBG1(DBG_ENC, "vici builder format buffer exceeds limit");
|
value = chunk_alloc(len + 1);
|
||||||
|
len = vsnprintf(value.ptr, value.len, fmt, args);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
value = chunk_create(buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len < 0)
|
||||||
|
{
|
||||||
|
DBG1(DBG_ENC, "vici builder format print failed");
|
||||||
this->error++;
|
this->error++;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
add(this, VICI_KEY_VALUE, key, chunk_create(buf, len));
|
if (key)
|
||||||
|
{
|
||||||
|
add(this, VICI_KEY_VALUE, key, value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
add(this, VICI_LIST_ITEM, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (value.ptr != buf)
|
||||||
|
{
|
||||||
|
free(value.ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
METHOD(vici_builder_t, vadd_kv, void,
|
||||||
|
private_vici_builder_t *this, char *key, char *fmt, va_list args)
|
||||||
|
{
|
||||||
|
vadd_kv_or_li(this, key, fmt, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
METHOD(vici_builder_t, add_kv, void,
|
METHOD(vici_builder_t, add_kv, void,
|
||||||
@@ -153,23 +189,10 @@ METHOD(vici_builder_t, add_kv, void,
|
|||||||
va_end(args);
|
va_end(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
METHOD(vici_builder_t, vadd_li, void,
|
METHOD(vici_builder_t, vadd_li, void,
|
||||||
private_vici_builder_t *this, char *fmt, va_list args)
|
private_vici_builder_t *this, char *fmt, va_list args)
|
||||||
{
|
{
|
||||||
char buf[2048];
|
vadd_kv_or_li(this, NULL, fmt, args);
|
||||||
ssize_t len;
|
|
||||||
|
|
||||||
len = vsnprintf(buf, sizeof(buf), fmt, args);
|
|
||||||
if (len < 0 || len >= sizeof(buf))
|
|
||||||
{
|
|
||||||
DBG1(DBG_ENC, "vici builder format buffer exceeds limit");
|
|
||||||
this->error++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
add(this, VICI_LIST_ITEM, chunk_create(buf, len));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
METHOD(vici_builder_t, add_li, void,
|
METHOD(vici_builder_t, add_li, void,
|
||||||
|
|||||||
@@ -508,11 +508,14 @@ static void build_auth_cfgs(peer_cfg_t *peer_cfg, bool local, vici_builder_t *b)
|
|||||||
certificate_t *cert;
|
certificate_t *cert;
|
||||||
char *str;
|
char *str;
|
||||||
} v;
|
} v;
|
||||||
|
char buf[32];
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
enumerator = peer_cfg->create_auth_cfg_enumerator(peer_cfg, local);
|
enumerator = peer_cfg->create_auth_cfg_enumerator(peer_cfg, local);
|
||||||
while (enumerator->enumerate(enumerator, &auth))
|
while (enumerator->enumerate(enumerator, &auth))
|
||||||
{
|
{
|
||||||
b->begin_section(b, local ? "local" : "remote");
|
snprintf(buf, sizeof(buf), "%s-%d", local ? "local" : "remote", ++i);
|
||||||
|
b->begin_section(b, buf);
|
||||||
|
|
||||||
rules = auth->create_enumerator(auth);
|
rules = auth->create_enumerator(auth);
|
||||||
while (rules->enumerate(rules, &rule, &v))
|
while (rules->enumerate(rules, &rule, &v))
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ CALLBACK(conn_sn, int,
|
|||||||
{
|
{
|
||||||
return vici_parse_cb(res, children_sn, NULL, NULL, NULL);
|
return vici_parse_cb(res, children_sn, NULL, NULL, NULL);
|
||||||
}
|
}
|
||||||
if (streq(name, "local") || streq(name, "remote"))
|
if (strpfx(name, "local") || strpfx(name, "remote"))
|
||||||
{
|
{
|
||||||
hashtable_t *auth;
|
hashtable_t *auth;
|
||||||
|
|
||||||
@@ -112,7 +112,8 @@ CALLBACK(conn_sn, int,
|
|||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
{
|
{
|
||||||
printf(" %s %s authentication:\n",
|
printf(" %s %s authentication:\n",
|
||||||
name, auth->get(auth, "class") ?: "unspecified");
|
strpfx(name, "local") ? "local" : "remote",
|
||||||
|
auth->get(auth, "class") ?: "unspecified");
|
||||||
if (auth->get(auth, "id"))
|
if (auth->get(auth, "id"))
|
||||||
{
|
{
|
||||||
printf(" id: %s\n", auth->get(auth, "id"));
|
printf(" id: %s\n", auth->get(auth, "id"));
|
||||||
|
|||||||
Reference in New Issue
Block a user