]>
git.ipfire.org Git - thirdparty/strongswan.git/blob - src/libcharon/plugins/vici/ruby/lib/vici.rb
0fd4b37bb341e34a3183f603ad7e8eef97bd14bd
2 # The Vici module implements a native ruby client side library for the
3 # strongSwan VICI protocol. The Connection class provides a high-level
4 # interface to issue requests or listen for events.
6 # Copyright (C) 2019 Tobias Brunner
7 # HSR Hochschule fuer Technik Rapperswil
9 # Copyright (C) 2014 Martin Willi
10 # Copyright (C) 2014 revosec AG
12 # Permission is hereby granted, free of charge, to any person obtaining a copy
13 # of this software and associated documentation files (the "Software"), to deal
14 # in the Software without restriction, including without limitation the rights
15 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 # copies of the Software, and to permit persons to whom the Software is
17 # furnished to do so, subject to the following conditions:
19 # The above copyright notice and this permission notice shall be included in
20 # all copies or substantial portions of the Software.
22 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32 # Vici specific exception all others inherit from
33 class Error
< StandardError
37 # Error while parsing a vici message from the daemon
38 class ParseError
< Error
42 # Error while encoding a vici message from ruby data structures
43 class EncodeError
< Error
47 # Error while exchanging messages over the vici Transport layer
48 class TransportError
< Error
52 # Generic vici command execution error
53 class CommandError
< Error
57 # Error if an issued vici command is unknown by the daemon
58 class CommandUnknownError
< CommandError
62 # Error if a command failed to execute in the daemon
63 class CommandExecError
< CommandError
67 # Generic vici event handling error
68 class EventError
< Error
72 # Tried to register to / unregister from an unknown vici event
73 class EventUnknownError
< EventError
77 # Exception to raise from an event listening closure to stop listening
78 class StopEventListening
< Exception
83 # The Message class provides the low level encoding and decoding of vici
84 # protocol messages. Directly using this class is usually not required.
94 def initialize(data = "")
97 elsif data.is_a
?(Hash
)
105 # Get the raw byte encoding of an on-the-wire message
108 @encoded = encode(@root)
114 # Get the root element of the parsed ruby data structures
117 @root = parse(@encoded)
124 def encode_name(name
)
125 [name
.length
].pack("c") << name
128 def encode_value(value
)
129 if value
.class != String
132 [value
.length
].pack("n") << value
135 def encode_kv(encoding
, key
, value
)
136 encoding
<< KEY_VALUE
<< encode_name(key
) << encode_value(value
)
139 def encode_section(encoding
, key
, value
)
140 encoding
<< SECTION_START
<< encode_name(key
)
141 encoding
<< encode(value
) << SECTION_END
144 def encode_list(encoding
, key
, value
)
145 encoding
<< LIST_START
<< encode_name(key
)
147 encoding
<< LIST_ITEM
<< encode_value(item
)
154 node
.each
do |key
, value
|
156 when String
, Fixnum
, true, false
157 encoding
= encode_kv(encoding
, key
, value
)
160 encoding
= encode_section(encoding
, key
, value
)
161 elsif value
.is_a
?(Array
)
162 encoding
= encode_list(encoding
, key
, value
)
164 encoding
= encode_kv(encoding
, key
, value
)
171 def parse_name(encoding
)
172 len
= encoding
.unpack("c")[0]
173 name
= encoding
[1, len
]
174 return encoding
[(1 + len
)..-1], name
177 def parse_value(encoding
)
178 len
= encoding
.unpack("n")[0]
179 value
= encoding
[2, len
]
180 return encoding
[(2 + len
)..-1], value
186 while encoding
.length
!= 0 do
187 type
= encoding
.unpack("c")[0]
188 encoding
= encoding
[1..-1]
191 encoding
, name
= parse_name(encoding
)
192 stack
.push(stack
[-1][name
] = Hash
.new
)
194 if stack
.length() == 1
195 raise ParseError
, "unexpected section end"
199 encoding
, name
= parse_name(encoding
)
200 encoding
, value
= parse_value(encoding
)
201 stack
[-1][name
] = value
203 encoding
, name
= parse_name(encoding
)
207 raise ParseError
, "unexpected list item" if list
== nil
208 encoding
, value
= parse_value(encoding
)
209 stack
[-1][list
].push(value
)
211 raise ParseError
, "unexpected list end" if list
== nil
214 raise ParseError
, "invalid type: #{type}"
217 if stack
.length() > 1
218 raise ParseError
, "unexpected message end"
226 # The Transport class implements to low level segmentation of packets
227 # to the underlying transport stream. Directly using this class is usually
241 # Create a transport layer using a provided socket for communication.
242 def initialize(socket
)
248 # Receive data from socket, until len bytes read
251 while encoding
.length
< len
do
252 data = @socket.recv(len
- encoding
.length
)
254 raise TransportError
, "connection closed"
262 # Send data to socket, until all bytes sent
263 def send_all(encoding
)
265 while len
< encoding
.length
do
266 len
+= @socket.send(encoding
[len
..-1], 0)
271 # Write a packet prefixed by its length over the transport socket. Type
272 # specifies the message, the optional label and message get appended.
273 def write(type
, label
, message
)
276 encoding
<< label
.length
<< label
279 encoding
<< message
.encoding
281 send_all([encoding
.length
+ 1, type
].pack("Nc") + encoding
)
285 # Read a packet from the transport socket. Returns the packet type, and
286 # if available in the packet a label and the contained message.
288 len
= recv_all(4).unpack("N")[0]
289 encoding
= recv_all(len
)
290 type
= encoding
.unpack("c")[0]
293 when CMD_REQUEST
, EVENT_REGISTER
, EVENT_UNREGISTER
, EVENT
294 label
= encoding
[2, encoding
[1].unpack("c")[0]]
295 len
+= label
.length
+ 1
296 when CMD_RESPONSE
, CMD_UNKNOWN
, EVENT_CONFIRM
, EVENT_UNKNOWN
299 raise TransportError
, "invalid message: #{type}"
301 if encoding
.length
== len
302 return type
, label
, Message
.new
304 return type
, label
, Message
.new(encoding
[len
..-1])
307 def dispatch_event(name
, message
)
308 @events[name
].each
do |handler
|
309 handler
.call(name
, message
)
313 def read_and_dispatch_event
314 type
, label
, message
= read
317 dispatch_event(label
, message
)
319 raise TransportError
, "unexpected message: #{type}"
323 def read_and_dispatch_events
325 type
, label
, message
= read
327 dispatch_event(label
, message
)
329 return type
, label
, message
335 # Send a command with a given name, and optionally a message. Returns
336 # the reply message on success.
337 def request(name
, message
= nil)
338 write(CMD_REQUEST
, name
, message
)
339 type
, label
, message
= read_and_dispatch_events
344 raise CommandUnknownError
, name
346 raise CommandError
, "invalid response for #{name}"
351 # Register a handler method for the given event name
352 def register(name
, handler
)
353 write(EVENT_REGISTER
, name
, nil)
354 type
, label
, message
= read_and_dispatch_events
357 if @events.has_key
?(name
)
358 @events[name
] += [handler
]
360 @events[name
] = [handler
];
363 raise EventUnknownError
, name
365 raise EventError
, "invalid response for #{name} register"
370 # Unregister a handler method for the given event name
371 def unregister(name
, handler
)
372 write(EVENT_UNREGISTER
, name
, nil)
373 type
, label
, message
= read_and_dispatch_events
376 @events[name
] -= [handler
]
378 raise EventUnknownError
, name
380 raise EventError
, "invalid response for #{name} unregister"
387 # The Connection class provides the high-level interface to monitor, configure
388 # and control the IKE daemon. It takes a connected stream-oriented Socket for
389 # the communication with the IKE daemon.
391 # This class takes and returns ruby objects for the exchanged message data.
392 # * Sections get encoded as Hash, containing other sections as Hash, or
393 # * Key/Values, where the values are Strings as Hash values
394 # * Lists get encoded as Arrays with String values
395 # Non-String values that are not a Hash nor an Array get converted with .to_s
399 def initialize(socket
= nil)
401 socket
= UNIXSocket
.new("/var/run/charon.vici")
403 @transp = Transport
.new(socket
)
407 # Get daemon version information
413 # Get daemon statistics and information.
419 # Reload strongswan.conf settings.
421 call("reload-settings")
425 # Initiate a connection. The provided closure is invoked for each log line.
426 def initiate(options
, &block
)
427 call_with_event("initiate", Message
.new(options
), "control-log", &block
)
431 # Terminate a connection. The provided closure is invoked for each log line.
432 def terminate(options
, &block
)
433 call_with_event("terminate", Message
.new(options
), "control-log", &block
)
437 # Initiate the rekeying of an SA.
439 call("rekey", Message
.new(options
))
443 # Redirect an IKE_SA.
444 def redirect(options
)
445 call("redirect", Message
.new(options
))
449 # Install a shunt/route policy.
451 call("install", Message
.new(policy
))
455 # Uninstall a shunt/route policy.
456 def uninstall(policy
)
457 call("uninstall", Message
.new(policy
))
461 # List matching active SAs. The provided closure is invoked for each
463 def list_sas(match
= nil, &block
)
464 call_with_event("list-sas", Message
.new(match
), "list-sa", &block
)
468 # List matching installed policies. The provided closure is invoked
469 # for each matching policy.
470 def list_policies(match
, &block
)
471 call_with_event("list-policies", Message
.new(match
), "list-policy",
476 # List matching loaded connections. The provided closure is invoked
477 # for each matching connection.
478 def list_conns(match
= nil, &block
)
479 call_with_event("list-conns", Message
.new(match
), "list-conn", &block
)
483 # Get the names of connections managed by vici.
489 # List matching loaded certificates. The provided closure is invoked
490 # for each matching certificate definition.
491 def list_certs(match
= nil, &block
)
492 call_with_event("list-certs", Message
.new(match
), "list-cert", &block
)
496 # List matching loaded certification authorities. The provided closure is
497 # invoked for each matching certification authority definition.
498 def list_authorities(match
= nil, &block
)
499 call_with_event("list-authorities", Message
.new(match
), "list-authority",
504 # Get the names of certification authorities managed by vici.
505 def get_authorities()
506 call("get-authorities")
510 # Load a connection into the daemon.
512 call("load-conn", Message
.new(conn
))
516 # Unload a connection from the daemon.
517 def unload_conn(conn
)
518 call("unload-conn", Message
.new(conn
))
522 # Load a certificate into the daemon.
524 call("load-cert", Message
.new(cert
))
528 # Load a private key into the daemon.
530 call("load-key", Message
.new(key
))
534 # Unload a private key from the daemon.
536 call("unload-key", Message
.new(key
))
540 # Get the identifiers of private keys loaded via vici.
546 # Load a private key located on a token into the daemon.
547 def load_token(token
)
548 call("load-token", Message
.new(token
))
552 # Load a shared key into the daemon.
553 def load_shared(shared
)
554 call("load-shared", Message
.new(shared
))
558 # Unload a shared key from the daemon.
559 def unload_shared(shared
)
560 call("unload-shared", Message
.new(shared
))
564 # Get the unique identifiers of shared keys loaded via vici.
570 # Flush credential cache.
571 def flush_certs(match
= nil)
572 call("flush-certs", Message
.new(match
))
576 # Clear all loaded credentials.
582 # Load a certification authority into the daemon.
583 def load_authority(authority
)
584 call("load-authority", Message
.new(authority
))
588 # Unload a certification authority from the daemon.
589 def unload_authority(authority
)
590 call("unload-authority", Message
.new(authority
))
594 # Load a virtual IP / attribute pool into the daemon.
596 call("load-pool", Message
.new(pool
))
600 # Unload a virtual IP / attribute pool from the daemon.
601 def unload_pool(pool
)
602 call("unload-pool", Message
.new(pool
))
606 # Get the currently loaded pools.
607 def get_pools(options
)
608 call("get-pools", Message
.new(options
))
612 # Get currently loaded algorithms and their implementation.
614 call("get-algorithms")
618 # Get global or connection-specific counters for IKE events.
619 def get_counters(options
= nil)
620 call("get-counters", Message
.new(options
))
624 # Reset global or connection-specific IKE event counters.
625 def reset_counters(options
= nil)
626 call("reset-counters", Message
.new(options
))
630 # Listen for a set of event messages. This call is blocking, and invokes
631 # the passed closure for each event received. The closure receives the
632 # event name and the event message as argument. To stop listening, the
633 # closure may raise a StopEventListening exception, the only caught
635 def listen_events(events
, &block
)
636 self.class.instance_eval
do
637 define_method(:listen_event) do |label
, message
|
638 block
.call(label
, message
.root
)
641 events
.each
do |event
|
642 @transp.register(event
, method(:listen_event))
646 @transp.read_and_dispatch_event
648 rescue StopEventListening
650 events
.each
do |event
|
651 @transp.unregister(event
, method(:listen_event))
657 # Issue a command request. Checks if the reply of a command indicates
658 # "success", otherwise raises a CommandExecError exception.
659 def call(command
, request
= nil)
660 check_success(@transp.request(command
, request
))
664 # Issue a command request, but register for a specific event while the
665 # command is active. VICI uses this mechanism to stream potentially large
666 # data objects continuously. The provided closure is invoked for all
668 def call_with_event(command
, request
, event
, &block
)
669 self.class.instance_eval
do
670 define_method(:call_event) do |label
, message
|
671 block
.call(message
.root
)
674 @transp.register(event
, method(:call_event))
676 reply
= @transp.request(command
, request
)
678 @transp.unregister(event
, method(:call_event))
684 # Check if the reply of a command indicates "success", otherwise raise a
685 # CommandExecError exception
686 def check_success(reply
)
688 if root
.key
?("success") && root
["success"] != "yes"
689 raise CommandExecError
, root
["errmsg"]