]>
git.ipfire.org Git - thirdparty/strongswan.git/blob - src/libcharon/plugins/vici/ruby/lib/vici.rb
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) 2014 Martin Willi
7 # Copyright (C) 2014 revosec AG
9 # Permission is hereby granted, free of charge, to any person obtaining a copy
10 # of this software and associated documentation files (the "Software"), to deal
11 # in the Software without restriction, including without limitation the rights
12 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 # copies of the Software, and to permit persons to whom the Software is
14 # furnished to do so, subject to the following conditions:
16 # The above copyright notice and this permission notice shall be included in
17 # all copies or substantial portions of the Software.
19 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
30 # Vici specific exception all others inherit from
31 class Error
< StandardError
35 # Error while parsing a vici message from the daemon
36 class ParseError
< Error
40 # Error while encoding a vici message from ruby data structures
41 class EncodeError
< Error
45 # Error while exchanging messages over the vici Transport layer
46 class TransportError
< Error
50 # Generic vici command execution error
51 class CommandError
< Error
55 # Error if an issued vici command is unknown by the daemon
56 class CommandUnknownError
< CommandError
60 # Error if a command failed to execute in the daemon
61 class CommandExecError
< CommandError
65 # Generic vici event handling error
66 class EventError
< Error
70 # Tried to register to / unregister from an unknown vici event
71 class EventUnknownError
< EventError
75 # Exception to raise from an event listening closure to stop listening
76 class StopEventListening
< Exception
81 # The Message class provides the low level encoding and decoding of vici
82 # protocol messages. Directly using this class is usually not required.
92 def initialize(data = "")
95 elsif data.is_a
?(Hash
)
103 # Get the raw byte encoding of an on-the-wire message
106 @encoded = encode(@root)
112 # Get the root element of the parsed ruby data structures
115 @root = parse(@encoded)
122 def encode_name(name
)
123 [name
.length
].pack("c") << name
126 def encode_value(value
)
127 if value
.class != String
130 [value
.length
].pack("n") << value
133 def encode_kv(encoding
, key
, value
)
134 encoding
<< KEY_VALUE
<< encode_name(key
) << encode_value(value
)
137 def encode_section(encoding
, key
, value
)
138 encoding
<< SECTION_START
<< encode_name(key
)
139 encoding
<< encode(value
) << SECTION_END
142 def encode_list(encoding
, key
, value
)
143 encoding
<< LIST_START
<< encode_name(key
)
145 encoding
<< LIST_ITEM
<< encode_value(item
)
152 node
.each
do |key
, value
|
154 when String
, Fixnum
, true, false
155 encoding
= encode_kv(encoding
, key
, value
)
158 encoding
= encode_section(encoding
, key
, value
)
159 elsif value
.is_a
?(Array
)
160 encoding
= encode_list(encoding
, key
, value
)
162 encoding
= encode_kv(encoding
, key
, value
)
169 def parse_name(encoding
)
170 len
= encoding
.unpack("c")[0]
171 name
= encoding
[1, len
]
172 return encoding
[(1 + len
)..-1], name
175 def parse_value(encoding
)
176 len
= encoding
.unpack("n")[0]
177 value
= encoding
[2, len
]
178 return encoding
[(2 + len
)..-1], value
184 while encoding
.length
!= 0 do
185 type
= encoding
.unpack("c")[0]
186 encoding
= encoding
[1..-1]
189 encoding
, name
= parse_name(encoding
)
190 stack
.push(stack
[-1][name
] = Hash
.new
)
192 if stack
.length() == 1
193 raise ParseError
, "unexpected section end"
197 encoding
, name
= parse_name(encoding
)
198 encoding
, value
= parse_value(encoding
)
199 stack
[-1][name
] = value
201 encoding
, name
= parse_name(encoding
)
205 raise ParseError
, "unexpected list item" if list
== nil
206 encoding
, value
= parse_value(encoding
)
207 stack
[-1][list
].push(value
)
209 raise ParseError
, "unexpected list end" if list
== nil
212 raise ParseError
, "invalid type: #{type}"
215 if stack
.length() > 1
216 raise ParseError
, "unexpected message end"
224 # The Transport class implements to low level segmentation of packets
225 # to the underlying transport stream. Directly using this class is usually
239 # Create a transport layer using a provided socket for communication.
240 def initialize(socket
)
246 # Receive data from socket, until len bytes read
249 while encoding
.length
< len
do
250 encoding
<< @socket.recv(len
- encoding
.length
)
256 # Send data to socket, until all bytes sent
257 def send_all(encoding
)
259 while len
< encoding
.length
do
260 len
+= @socket.send(encoding
[len
..-1], 0)
265 # Write a packet prefixed by its length over the transport socket. Type
266 # specifies the message, the optional label and message get appended.
267 def write(type
, label
, message
)
270 encoding
<< label
.length
<< label
273 encoding
<< message
.encoding
275 send_all([encoding
.length
+ 1, type
].pack("Nc") + encoding
)
279 # Read a packet from the transport socket. Returns the packet type, and
280 # if available in the packet a label and the contained message.
282 len
= recv_all(4).unpack("N")[0]
283 encoding
= recv_all(len
)
284 type
= encoding
.unpack("c")[0]
287 when CMD_REQUEST
, EVENT_REGISTER
, EVENT_UNREGISTER
, EVENT
288 label
= encoding
[2, encoding
[1].unpack("c")[0]]
289 len
+= label
.length
+ 1
290 when CMD_RESPONSE
, CMD_UNKNOWN
, EVENT_CONFIRM
, EVENT_UNKNOWN
293 raise TransportError
, "invalid message: #{type}"
295 if encoding
.length
== len
296 return type
, label
, Message
.new
298 return type
, label
, Message
.new(encoding
[len
..-1])
301 def dispatch_event(name
, message
)
302 @events[name
].each
do |handler
|
303 handler
.call(name
, message
)
307 def read_and_dispatch_event
308 type
, label
, message
= read
311 dispatch_event(label
, message
)
313 raise TransportError
, "unexpected message: #{type}"
317 def read_and_dispatch_events
319 type
, label
, message
= read
321 dispatch_event(label
, message
)
323 return type
, label
, message
329 # Send a command with a given name, and optionally a message. Returns
330 # the reply message on success.
331 def request(name
, message
= nil)
332 write(CMD_REQUEST
, name
, message
)
333 type
, label
, message
= read_and_dispatch_events
338 raise CommandUnknownError
, name
340 raise CommandError
, "invalid response for #{name}"
345 # Register a handler method for the given event name
346 def register(name
, handler
)
347 write(EVENT_REGISTER
, name
, nil)
348 type
, label
, message
= read_and_dispatch_events
351 if @events.has_key
?(name
)
352 @events[name
] += [handler
]
354 @events[name
] = [handler
];
357 raise EventUnknownError
, name
359 raise EventError
, "invalid response for #{name} register"
364 # Unregister a handler method for the given event name
365 def unregister(name
, handler
)
366 write(EVENT_UNREGISTER
, name
, nil)
367 type
, label
, message
= read_and_dispatch_events
370 @events[name
] -= [handler
]
372 raise EventUnknownError
, name
374 raise EventError
, "invalid response for #{name} unregister"
381 # The Connection class provides the high-level interface to monitor, configure
382 # and control the IKE daemon. It takes a connected stream-oriented Socket for
383 # the communication with the IKE daemon.
385 # This class takes and returns ruby objects for the exchanged message data.
386 # * Sections get encoded as Hash, containing other sections as Hash, or
387 # * Key/Values, where the values are Strings as Hash values
388 # * Lists get encoded as Arrays with String values
389 # Non-String values that are not a Hash nor an Array get converted with .to_s
393 def initialize(socket
)
394 @transp = Transport
.new(socket
)
398 # List matching loaded connections. The provided closure is invoked
399 # for each matching connection.
400 def list_conns(match
= nil, &block
)
401 call_with_event("list-conns", Message
.new(match
), "list-conn", &block
)
405 # List matching active SAs. The provided closure is invoked for each
407 def list_sas(match
= nil, &block
)
408 call_with_event("list-sas", Message
.new(match
), "list-sa", &block
)
412 # List matching installed policies. The provided closure is invoked
413 # for each matching policy.
414 def list_policies(match
, &block
)
415 call_with_event("list-policies", Message
.new(match
), "list-policy",
420 # List matching loaded certificates. The provided closure is invoked
421 # for each matching certificate definition.
422 def list_certs(match
= nil, &block
)
423 call_with_event("list-certs", Message
.new(match
), "list-cert", &block
)
427 # Load a connection into the daemon.
429 check_success(@transp.request("load-conn", Message
.new(conn
)))
433 # Unload a connection from the daemon.
434 def unload_conn(conn
)
435 check_success(@transp.request("unload-conn", Message
.new(conn
)))
439 # Get the names of connections managed by vici.
441 @transp.request("get-conns").root
445 # Clear all loaded credentials.
447 check_success(@transp.request("clear-creds"))
451 # Load a certificate into the daemon.
453 check_success(@transp.request("load-cert", Message
.new(cert
)))
457 # Load a private key into the daemon.
459 check_success(@transp.request("load-key", Message
.new(key
)))
463 # Load a shared key into the daemon.
464 def load_shared(shared
)
465 check_success(@transp.request("load-shared", Message
.new(shared
)))
469 # Load a virtual IP / attribute pool
471 check_success(@transp.request("load-pool", Message
.new(pool
)))
475 # Unload a virtual IP / attribute pool
476 def unload_pool(pool
)
477 check_success(@transp.request("unload-pool", Message
.new(pool
)))
481 # Get the currently loaded pools.
483 @transp.request("get-pools").root
487 # Initiate a connection. The provided closure is invoked for each log line.
488 def initiate(options
, &block
)
489 check_success(call_with_event("initiate", Message
.new(options
),
490 "control-log", &block
))
494 # Terminate a connection. The provided closure is invoked for each log line.
495 def terminate(options
, &block
)
496 check_success(call_with_event("terminate", Message
.new(options
),
497 "control-log", &block
))
501 # Install a shunt/route policy.
503 check_success(@transp.request("install", Message
.new(policy
)))
507 # Uninstall a shunt/route policy.
508 def uninstall(policy
)
509 check_success(@transp.request("uninstall", Message
.new(policy
)))
513 # Reload strongswan.conf settings.
515 check_success(@transp.request("reload-settings", nil))
519 # Get daemon statistics and information.
521 @transp.request("stats", nil).root
525 # Get daemon version information
527 @transp.request("version", nil).root
531 # Listen for a set of event messages. This call is blocking, and invokes
532 # the passed closure for each event received. The closure receives the
533 # event name and the event message as argument. To stop listening, the
534 # closure may raise a StopEventListening exception, the only catched
536 def listen_events(events
, &block
)
537 self.class.instance_eval
do
538 define_method(:listen_event) do |label
, message
|
539 block
.call(label
, message
.root
)
542 events
.each
do |event
|
543 @transp.register(event
, method(:listen_event))
547 @transp.read_and_dispatch_event
549 rescue StopEventListening
551 events
.each
do |event
|
552 @transp.unregister(event
, method(:listen_event))
558 # Issue a command request, but register for a specific event while the
559 # command is active. VICI uses this mechanism to stream potentially large
560 # data objects continuously. The provided closure is invoked for all
562 def call_with_event(command
, request
, event
, &block
)
563 self.class.instance_eval
do
564 define_method(:call_event) do |label
, message
|
565 block
.call(message
.root
)
568 @transp.register(event
, method(:call_event))
570 reply
= @transp.request(command
, request
)
572 @transp.unregister(event
, method(:call_event))
578 # Check if the reply of a command indicates "success", otherwise raise a
579 # CommandExecError exception
580 def check_success(reply
)
582 if root
["success"] != "yes"
583 raise CommandExecError
, root
["errmsg"]