]> git.ipfire.org Git - thirdparty/strongswan.git/blob - src/libcharon/plugins/vici/ruby/lib/vici.rb
0fd4b37bb341e34a3183f603ad7e8eef97bd14bd
[thirdparty/strongswan.git] / src / libcharon / plugins / vici / ruby / lib / vici.rb
1 ##
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.
5 #
6 # Copyright (C) 2019 Tobias Brunner
7 # HSR Hochschule fuer Technik Rapperswil
8 #
9 # Copyright (C) 2014 Martin Willi
10 # Copyright (C) 2014 revosec AG
11 #
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:
18 #
19 # The above copyright notice and this permission notice shall be included in
20 # all copies or substantial portions of the Software.
21 #
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
28 # THE SOFTWARE.
29
30 module Vici
31 ##
32 # Vici specific exception all others inherit from
33 class Error < StandardError
34 end
35
36 ##
37 # Error while parsing a vici message from the daemon
38 class ParseError < Error
39 end
40
41 ##
42 # Error while encoding a vici message from ruby data structures
43 class EncodeError < Error
44 end
45
46 ##
47 # Error while exchanging messages over the vici Transport layer
48 class TransportError < Error
49 end
50
51 ##
52 # Generic vici command execution error
53 class CommandError < Error
54 end
55
56 ##
57 # Error if an issued vici command is unknown by the daemon
58 class CommandUnknownError < CommandError
59 end
60
61 ##
62 # Error if a command failed to execute in the daemon
63 class CommandExecError < CommandError
64 end
65
66 ##
67 # Generic vici event handling error
68 class EventError < Error
69 end
70
71 ##
72 # Tried to register to / unregister from an unknown vici event
73 class EventUnknownError < EventError
74 end
75
76 ##
77 # Exception to raise from an event listening closure to stop listening
78 class StopEventListening < Exception
79 end
80
81
82 ##
83 # The Message class provides the low level encoding and decoding of vici
84 # protocol messages. Directly using this class is usually not required.
85 class Message
86
87 SECTION_START = 1
88 SECTION_END = 2
89 KEY_VALUE = 3
90 LIST_START = 4
91 LIST_ITEM = 5
92 LIST_END = 6
93
94 def initialize(data = "")
95 if data == nil
96 @root = Hash.new()
97 elsif data.is_a?(Hash)
98 @root = data
99 else
100 @encoded = data
101 end
102 end
103
104 ##
105 # Get the raw byte encoding of an on-the-wire message
106 def encoding
107 if @encoded == nil
108 @encoded = encode(@root)
109 end
110 @encoded
111 end
112
113 ##
114 # Get the root element of the parsed ruby data structures
115 def root
116 if @root == nil
117 @root = parse(@encoded)
118 end
119 @root
120 end
121
122 private
123
124 def encode_name(name)
125 [name.length].pack("c") << name
126 end
127
128 def encode_value(value)
129 if value.class != String
130 value = value.to_s
131 end
132 [value.length].pack("n") << value
133 end
134
135 def encode_kv(encoding, key, value)
136 encoding << KEY_VALUE << encode_name(key) << encode_value(value)
137 end
138
139 def encode_section(encoding, key, value)
140 encoding << SECTION_START << encode_name(key)
141 encoding << encode(value) << SECTION_END
142 end
143
144 def encode_list(encoding, key, value)
145 encoding << LIST_START << encode_name(key)
146 value.each do |item|
147 encoding << LIST_ITEM << encode_value(item)
148 end
149 encoding << LIST_END
150 end
151
152 def encode(node)
153 encoding = ""
154 node.each do |key, value|
155 case value.class
156 when String, Fixnum, true, false
157 encoding = encode_kv(encoding, key, value)
158 else
159 if value.is_a?(Hash)
160 encoding = encode_section(encoding, key, value)
161 elsif value.is_a?(Array)
162 encoding = encode_list(encoding, key, value)
163 else
164 encoding = encode_kv(encoding, key, value)
165 end
166 end
167 end
168 encoding
169 end
170
171 def parse_name(encoding)
172 len = encoding.unpack("c")[0]
173 name = encoding[1, len]
174 return encoding[(1 + len)..-1], name
175 end
176
177 def parse_value(encoding)
178 len = encoding.unpack("n")[0]
179 value = encoding[2, len]
180 return encoding[(2 + len)..-1], value
181 end
182
183 def parse(encoding)
184 stack = [Hash.new]
185 list = nil
186 while encoding.length != 0 do
187 type = encoding.unpack("c")[0]
188 encoding = encoding[1..-1]
189 case type
190 when SECTION_START
191 encoding, name = parse_name(encoding)
192 stack.push(stack[-1][name] = Hash.new)
193 when SECTION_END
194 if stack.length() == 1
195 raise ParseError, "unexpected section end"
196 end
197 stack.pop()
198 when KEY_VALUE
199 encoding, name = parse_name(encoding)
200 encoding, value = parse_value(encoding)
201 stack[-1][name] = value
202 when LIST_START
203 encoding, name = parse_name(encoding)
204 stack[-1][name] = []
205 list = name
206 when LIST_ITEM
207 raise ParseError, "unexpected list item" if list == nil
208 encoding, value = parse_value(encoding)
209 stack[-1][list].push(value)
210 when LIST_END
211 raise ParseError, "unexpected list end" if list == nil
212 list = nil
213 else
214 raise ParseError, "invalid type: #{type}"
215 end
216 end
217 if stack.length() > 1
218 raise ParseError, "unexpected message end"
219 end
220 stack[0]
221 end
222 end
223
224
225 ##
226 # The Transport class implements to low level segmentation of packets
227 # to the underlying transport stream. Directly using this class is usually
228 # not required.
229 class Transport
230
231 CMD_REQUEST = 0
232 CMD_RESPONSE = 1
233 CMD_UNKNOWN = 2
234 EVENT_REGISTER = 3
235 EVENT_UNREGISTER = 4
236 EVENT_CONFIRM = 5
237 EVENT_UNKNOWN = 6
238 EVENT = 7
239
240 ##
241 # Create a transport layer using a provided socket for communication.
242 def initialize(socket)
243 @socket = socket
244 @events = Hash.new
245 end
246
247 ##
248 # Receive data from socket, until len bytes read
249 def recv_all(len)
250 encoding = ""
251 while encoding.length < len do
252 data = @socket.recv(len - encoding.length)
253 if data.empty?
254 raise TransportError, "connection closed"
255 end
256 encoding << data
257 end
258 encoding
259 end
260
261 ##
262 # Send data to socket, until all bytes sent
263 def send_all(encoding)
264 len = 0
265 while len < encoding.length do
266 len += @socket.send(encoding[len..-1], 0)
267 end
268 end
269
270 ##
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)
274 encoding = ""
275 if label
276 encoding << label.length << label
277 end
278 if message
279 encoding << message.encoding
280 end
281 send_all([encoding.length + 1, type].pack("Nc") + encoding)
282 end
283
284 ##
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.
287 def read
288 len = recv_all(4).unpack("N")[0]
289 encoding = recv_all(len)
290 type = encoding.unpack("c")[0]
291 len = 1
292 case type
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
297 label = nil
298 else
299 raise TransportError, "invalid message: #{type}"
300 end
301 if encoding.length == len
302 return type, label, Message.new
303 end
304 return type, label, Message.new(encoding[len..-1])
305 end
306
307 def dispatch_event(name, message)
308 @events[name].each do |handler|
309 handler.call(name, message)
310 end
311 end
312
313 def read_and_dispatch_event
314 type, label, message = read
315 p
316 if type == EVENT
317 dispatch_event(label, message)
318 else
319 raise TransportError, "unexpected message: #{type}"
320 end
321 end
322
323 def read_and_dispatch_events
324 loop do
325 type, label, message = read
326 if type == EVENT
327 dispatch_event(label, message)
328 else
329 return type, label, message
330 end
331 end
332 end
333
334 ##
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
340 case type
341 when CMD_RESPONSE
342 return message
343 when CMD_UNKNOWN
344 raise CommandUnknownError, name
345 else
346 raise CommandError, "invalid response for #{name}"
347 end
348 end
349
350 ##
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
355 case type
356 when EVENT_CONFIRM
357 if @events.has_key?(name)
358 @events[name] += [handler]
359 else
360 @events[name] = [handler];
361 end
362 when EVENT_UNKNOWN
363 raise EventUnknownError, name
364 else
365 raise EventError, "invalid response for #{name} register"
366 end
367 end
368
369 ##
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
374 case type
375 when EVENT_CONFIRM
376 @events[name] -= [handler]
377 when EVENT_UNKNOWN
378 raise EventUnknownError, name
379 else
380 raise EventError, "invalid response for #{name} unregister"
381 end
382 end
383 end
384
385
386 ##
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.
390 #
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
396 # during encoding.
397 class Connection
398
399 def initialize(socket = nil)
400 if socket == nil
401 socket = UNIXSocket.new("/var/run/charon.vici")
402 end
403 @transp = Transport.new(socket)
404 end
405
406 ##
407 # Get daemon version information
408 def version
409 call("version")
410 end
411
412 ##
413 # Get daemon statistics and information.
414 def stats
415 call("stats")
416 end
417
418 ##
419 # Reload strongswan.conf settings.
420 def reload_settings
421 call("reload-settings")
422 end
423
424 ##
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)
428 end
429
430 ##
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)
434 end
435
436 ##
437 # Initiate the rekeying of an SA.
438 def rekey(options)
439 call("rekey", Message.new(options))
440 end
441
442 ##
443 # Redirect an IKE_SA.
444 def redirect(options)
445 call("redirect", Message.new(options))
446 end
447
448 ##
449 # Install a shunt/route policy.
450 def install(policy)
451 call("install", Message.new(policy))
452 end
453
454 ##
455 # Uninstall a shunt/route policy.
456 def uninstall(policy)
457 call("uninstall", Message.new(policy))
458 end
459
460 ##
461 # List matching active SAs. The provided closure is invoked for each
462 # matching SA.
463 def list_sas(match = nil, &block)
464 call_with_event("list-sas", Message.new(match), "list-sa", &block)
465 end
466
467 ##
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",
472 &block)
473 end
474
475 ##
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)
480 end
481
482 ##
483 # Get the names of connections managed by vici.
484 def get_conns()
485 call("get-conns")
486 end
487
488 ##
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)
493 end
494
495 ##
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",
500 &block)
501 end
502
503 ##
504 # Get the names of certification authorities managed by vici.
505 def get_authorities()
506 call("get-authorities")
507 end
508
509 ##
510 # Load a connection into the daemon.
511 def load_conn(conn)
512 call("load-conn", Message.new(conn))
513 end
514
515 ##
516 # Unload a connection from the daemon.
517 def unload_conn(conn)
518 call("unload-conn", Message.new(conn))
519 end
520
521 ##
522 # Load a certificate into the daemon.
523 def load_cert(cert)
524 call("load-cert", Message.new(cert))
525 end
526
527 ##
528 # Load a private key into the daemon.
529 def load_key(key)
530 call("load-key", Message.new(key))
531 end
532
533 ##
534 # Unload a private key from the daemon.
535 def unload_key(key)
536 call("unload-key", Message.new(key))
537 end
538
539 ##
540 # Get the identifiers of private keys loaded via vici.
541 def get_keys()
542 call("get-keys")
543 end
544
545 ##
546 # Load a private key located on a token into the daemon.
547 def load_token(token)
548 call("load-token", Message.new(token))
549 end
550
551 ##
552 # Load a shared key into the daemon.
553 def load_shared(shared)
554 call("load-shared", Message.new(shared))
555 end
556
557 ##
558 # Unload a shared key from the daemon.
559 def unload_shared(shared)
560 call("unload-shared", Message.new(shared))
561 end
562
563 ##
564 # Get the unique identifiers of shared keys loaded via vici.
565 def get_shared()
566 call("get-shared")
567 end
568
569 ##
570 # Flush credential cache.
571 def flush_certs(match = nil)
572 call("flush-certs", Message.new(match))
573 end
574
575 ##
576 # Clear all loaded credentials.
577 def clear_creds()
578 call("clear-creds")
579 end
580
581 ##
582 # Load a certification authority into the daemon.
583 def load_authority(authority)
584 call("load-authority", Message.new(authority))
585 end
586
587 ##
588 # Unload a certification authority from the daemon.
589 def unload_authority(authority)
590 call("unload-authority", Message.new(authority))
591 end
592
593 ##
594 # Load a virtual IP / attribute pool into the daemon.
595 def load_pool(pool)
596 call("load-pool", Message.new(pool))
597 end
598
599 ##
600 # Unload a virtual IP / attribute pool from the daemon.
601 def unload_pool(pool)
602 call("unload-pool", Message.new(pool))
603 end
604
605 ##
606 # Get the currently loaded pools.
607 def get_pools(options)
608 call("get-pools", Message.new(options))
609 end
610
611 ##
612 # Get currently loaded algorithms and their implementation.
613 def get_algorithms()
614 call("get-algorithms")
615 end
616
617 ##
618 # Get global or connection-specific counters for IKE events.
619 def get_counters(options = nil)
620 call("get-counters", Message.new(options))
621 end
622
623 ##
624 # Reset global or connection-specific IKE event counters.
625 def reset_counters(options = nil)
626 call("reset-counters", Message.new(options))
627 end
628
629 ##
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
634 # exception.
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)
639 end
640 end
641 events.each do |event|
642 @transp.register(event, method(:listen_event))
643 end
644 begin
645 loop do
646 @transp.read_and_dispatch_event
647 end
648 rescue StopEventListening
649 ensure
650 events.each do |event|
651 @transp.unregister(event, method(:listen_event))
652 end
653 end
654 end
655
656 ##
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))
661 end
662
663 ##
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
667 # event messages.
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)
672 end
673 end
674 @transp.register(event, method(:call_event))
675 begin
676 reply = @transp.request(command, request)
677 ensure
678 @transp.unregister(event, method(:call_event))
679 end
680 check_success(reply)
681 end
682
683 ##
684 # Check if the reply of a command indicates "success", otherwise raise a
685 # CommandExecError exception
686 def check_success(reply)
687 root = reply.root
688 if root.key?("success") && root["success"] != "yes"
689 raise CommandExecError, root["errmsg"]
690 end
691 root
692 end
693 end
694 end