Annotation of embedaddon/strongswan/src/libcharon/plugins/vici/ruby/lib/vici.rb, revision 1.1.1.1
1.1 misho 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: # The Message class provides the low level encoding and decoding of vici
83: # protocol messages. Directly using this class is usually not required.
84: class Message
85: SECTION_START = 1
86: SECTION_END = 2
87: KEY_VALUE = 3
88: LIST_START = 4
89: LIST_ITEM = 5
90: LIST_END = 6
91:
92: def initialize(data = "")
93: if data.nil?
94: @root = {}
95: elsif data.is_a?(Hash)
96: @root = data
97: else
98: @encoded = data
99: end
100: end
101:
102: ##
103: # Get the raw byte encoding of an on-the-wire message
104: def encoding
105: @encoded = encode(@root) if @encoded.nil?
106: @encoded
107: end
108:
109: ##
110: # Get the root element of the parsed ruby data structures
111: def root
112: @root = parse(@encoded) if @root.nil?
113: @root
114: end
115:
116: private
117:
118: def encode_name(name)
119: [name.length].pack("c") << name
120: end
121:
122: def encode_value(value)
123: value = value.to_s if value.class != String
124: [value.length].pack("n") << value
125: end
126:
127: def encode_kv(encoding, key, value)
128: encoding << KEY_VALUE << encode_name(key) << encode_value(value)
129: end
130:
131: def encode_section(encoding, key, value)
132: encoding << SECTION_START << encode_name(key)
133: encoding << encode(value) << SECTION_END
134: end
135:
136: def encode_list(encoding, key, value)
137: encoding << LIST_START << encode_name(key)
138: value.each do |item|
139: encoding << LIST_ITEM << encode_value(item)
140: end
141: encoding << LIST_END
142: end
143:
144: def encode(node)
145: encoding = ""
146: node.each do |key, value|
147: encoding = if value.is_a?(Hash)
148: encode_section(encoding, key, value)
149: elsif value.is_a?(Array)
150: encode_list(encoding, key, value)
151: else
152: encode_kv(encoding, key, value)
153: end
154: end
155: encoding
156: end
157:
158: def parse_name(encoding)
159: len = encoding.unpack("c")[0]
160: name = encoding[1, len]
161: [encoding[(1 + len)..-1], name]
162: end
163:
164: def parse_value(encoding)
165: len = encoding.unpack("n")[0]
166: value = encoding[2, len]
167: [encoding[(2 + len)..-1], value]
168: end
169:
170: def parse(encoding)
171: stack = [{}]
172: list = nil
173: until encoding.empty?
174: type = encoding.unpack("c")[0]
175: encoding = encoding[1..-1]
176: case type
177: when SECTION_START
178: encoding, name = parse_name(encoding)
179: stack.push(stack[-1][name] = {})
180: when SECTION_END
181: raise ParseError, "unexpected section end" if stack.length == 1
182: stack.pop
183: when KEY_VALUE
184: encoding, name = parse_name(encoding)
185: encoding, value = parse_value(encoding)
186: stack[-1][name] = value
187: when LIST_START
188: encoding, name = parse_name(encoding)
189: stack[-1][name] = []
190: list = name
191: when LIST_ITEM
192: raise ParseError, "unexpected list item" if list.nil?
193: encoding, value = parse_value(encoding)
194: stack[-1][list].push(value)
195: when LIST_END
196: raise ParseError, "unexpected list end" if list.nil?
197: list = nil
198: else
199: raise ParseError, "invalid type: #{type}"
200: end
201: end
202: raise ParseError, "unexpected message end" if stack.length > 1
203: stack[0]
204: end
205: end
206:
207: ##
208: # The Transport class implements to low level segmentation of packets
209: # to the underlying transport stream. Directly using this class is usually
210: # not required.
211: class Transport
212: CMD_REQUEST = 0
213: CMD_RESPONSE = 1
214: CMD_UNKNOWN = 2
215: EVENT_REGISTER = 3
216: EVENT_UNREGISTER = 4
217: EVENT_CONFIRM = 5
218: EVENT_UNKNOWN = 6
219: EVENT = 7
220:
221: ##
222: # Create a transport layer using a provided socket for communication.
223: def initialize(socket)
224: @socket = socket
225: @events = {}
226: end
227:
228: ##
229: # Receive data from socket, until len bytes read
230: def recv_all(len)
231: encoding = ""
232: while encoding.length < len
233: data = @socket.recv(len - encoding.length)
234: raise TransportError, "connection closed" if data.empty?
235: encoding << data
236: end
237: encoding
238: end
239:
240: ##
241: # Send data to socket, until all bytes sent
242: def send_all(encoding)
243: len = 0
244: len += @socket.send(encoding[len..-1], 0) while len < encoding.length
245: end
246:
247: ##
248: # Write a packet prefixed by its length over the transport socket. Type
249: # specifies the message, the optional label and message get appended.
250: def write(type, label, message)
251: encoding = ""
252: encoding << label.length << label if label
253: encoding << message.encoding if message
254: send_all([encoding.length + 1, type].pack("Nc") + encoding)
255: end
256:
257: ##
258: # Read a packet from the transport socket. Returns the packet type, and
259: # if available in the packet a label and the contained message.
260: def read
261: len = recv_all(4).unpack("N")[0]
262: encoding = recv_all(len)
263: type = encoding.unpack("c")[0]
264: len = 1
265: case type
266: when CMD_REQUEST, EVENT_REGISTER, EVENT_UNREGISTER, EVENT
267: label = encoding[2, encoding[1].unpack("c")[0]]
268: len += label.length + 1
269: when CMD_RESPONSE, CMD_UNKNOWN, EVENT_CONFIRM, EVENT_UNKNOWN
270: label = nil
271: else
272: raise TransportError, "invalid message: #{type}"
273: end
274: message = if encoding.length == len
275: Message.new
276: else
277: Message.new(encoding[len..-1])
278: end
279: [type, label, message]
280: end
281:
282: def dispatch_event(name, message)
283: @events[name].each do |handler|
284: handler.call(name, message)
285: end
286: end
287:
288: def read_and_dispatch_event
289: type, label, message = read
290: raise TransportError, "unexpected message: #{type}" if type != EVENT
291:
292: dispatch_event(label, message)
293: end
294:
295: def read_and_dispatch_events
296: loop do
297: type, label, message = read
298: return type, label, message if type != EVENT
299:
300: dispatch_event(label, message)
301: end
302: end
303:
304: ##
305: # Send a command with a given name, and optionally a message. Returns
306: # the reply message on success.
307: def request(name, message = nil)
308: write(CMD_REQUEST, name, message)
309: type, _label, message = read_and_dispatch_events
310: case type
311: when CMD_RESPONSE
312: return message
313: when CMD_UNKNOWN
314: raise CommandUnknownError, name
315: else
316: raise CommandError, "invalid response for #{name}"
317: end
318: end
319:
320: ##
321: # Register a handler method for the given event name
322: def register(name, handler)
323: write(EVENT_REGISTER, name, nil)
324: type, _label, _message = read_and_dispatch_events
325: case type
326: when EVENT_CONFIRM
327: if @events.key?(name)
328: @events[name] += [handler]
329: else
330: @events[name] = [handler]
331: end
332: when EVENT_UNKNOWN
333: raise EventUnknownError, name
334: else
335: raise EventError, "invalid response for #{name} register"
336: end
337: end
338:
339: ##
340: # Unregister a handler method for the given event name
341: def unregister(name, handler)
342: write(EVENT_UNREGISTER, name, nil)
343: type, _label, _message = read_and_dispatch_events
344: case type
345: when EVENT_CONFIRM
346: @events[name] -= [handler]
347: when EVENT_UNKNOWN
348: raise EventUnknownError, name
349: else
350: raise EventError, "invalid response for #{name} unregister"
351: end
352: end
353: end
354:
355: ##
356: # The Connection class provides the high-level interface to monitor, configure
357: # and control the IKE daemon. It takes a connected stream-oriented Socket for
358: # the communication with the IKE daemon.
359: #
360: # This class takes and returns ruby objects for the exchanged message data.
361: # * Sections get encoded as Hash, containing other sections as Hash, or
362: # * Key/Values, where the values are Strings as Hash values
363: # * Lists get encoded as Arrays with String values
364: # Non-String values that are not a Hash nor an Array get converted with .to_s
365: # during encoding.
366: class Connection
367: ##
368: # Create a connection, optionally using the given socket
369: def initialize(socket = nil)
370: socket = UNIXSocket.new("/var/run/charon.vici") if socket.nil?
371: @transp = Transport.new(socket)
372: end
373:
374: ##
375: # Get daemon version information
376: def version
377: call("version")
378: end
379:
380: ##
381: # Get daemon statistics and information.
382: def stats
383: call("stats")
384: end
385:
386: ##
387: # Reload strongswan.conf settings.
388: def reload_settings
389: call("reload-settings")
390: end
391:
392: ##
393: # Initiate a connection. The provided closure is invoked for each log line.
394: def initiate(options, &block)
395: call_with_event("initiate", Message.new(options), "control-log", &block)
396: end
397:
398: ##
399: # Terminate a connection. The provided closure is invoked for each log line.
400: def terminate(options, &block)
401: call_with_event("terminate", Message.new(options), "control-log", &block)
402: end
403:
404: ##
405: # Initiate the rekeying of an SA.
406: def rekey(options)
407: call("rekey", Message.new(options))
408: end
409:
410: ##
411: # Redirect an IKE_SA.
412: def redirect(options)
413: call("redirect", Message.new(options))
414: end
415:
416: ##
417: # Install a shunt/route policy.
418: def install(policy)
419: call("install", Message.new(policy))
420: end
421:
422: ##
423: # Uninstall a shunt/route policy.
424: def uninstall(policy)
425: call("uninstall", Message.new(policy))
426: end
427:
428: ##
429: # List matching active SAs. The provided closure is invoked for each
430: # matching SA.
431: def list_sas(match = nil, &block)
432: call_with_event("list-sas", Message.new(match), "list-sa", &block)
433: end
434:
435: ##
436: # List matching installed policies. The provided closure is invoked
437: # for each matching policy.
438: def list_policies(match, &block)
439: call_with_event("list-policies", Message.new(match), "list-policy",
440: &block)
441: end
442:
443: ##
444: # List matching loaded connections. The provided closure is invoked
445: # for each matching connection.
446: def list_conns(match = nil, &block)
447: call_with_event("list-conns", Message.new(match), "list-conn", &block)
448: end
449:
450: ##
451: # Get the names of connections managed by vici.
452: def get_conns
453: call("get-conns")
454: end
455:
456: ##
457: # List matching loaded certificates. The provided closure is invoked
458: # for each matching certificate definition.
459: def list_certs(match = nil, &block)
460: call_with_event("list-certs", Message.new(match), "list-cert", &block)
461: end
462:
463: ##
464: # List matching loaded certification authorities. The provided closure is
465: # invoked for each matching certification authority definition.
466: def list_authorities(match = nil, &block)
467: call_with_event("list-authorities", Message.new(match), "list-authority",
468: &block)
469: end
470:
471: ##
472: # Get the names of certification authorities managed by vici.
473: def get_authorities
474: call("get-authorities")
475: end
476:
477: ##
478: # Load a connection into the daemon.
479: def load_conn(conn)
480: call("load-conn", Message.new(conn))
481: end
482:
483: ##
484: # Unload a connection from the daemon.
485: def unload_conn(conn)
486: call("unload-conn", Message.new(conn))
487: end
488:
489: ##
490: # Load a certificate into the daemon.
491: def load_cert(cert)
492: call("load-cert", Message.new(cert))
493: end
494:
495: ##
496: # Load a private key into the daemon.
497: def load_key(key)
498: call("load-key", Message.new(key))
499: end
500:
501: ##
502: # Unload a private key from the daemon.
503: def unload_key(key)
504: call("unload-key", Message.new(key))
505: end
506:
507: ##
508: # Get the identifiers of private keys loaded via vici.
509: def get_keys
510: call("get-keys")
511: end
512:
513: ##
514: # Load a private key located on a token into the daemon.
515: def load_token(token)
516: call("load-token", Message.new(token))
517: end
518:
519: ##
520: # Load a shared key into the daemon.
521: def load_shared(shared)
522: call("load-shared", Message.new(shared))
523: end
524:
525: ##
526: # Unload a shared key from the daemon.
527: def unload_shared(shared)
528: call("unload-shared", Message.new(shared))
529: end
530:
531: ##
532: # Get the unique identifiers of shared keys loaded via vici.
533: def get_shared
534: call("get-shared")
535: end
536:
537: ##
538: # Flush credential cache.
539: def flush_certs(match = nil)
540: call("flush-certs", Message.new(match))
541: end
542:
543: ##
544: # Clear all loaded credentials.
545: def clear_creds
546: call("clear-creds")
547: end
548:
549: ##
550: # Load a certification authority into the daemon.
551: def load_authority(authority)
552: call("load-authority", Message.new(authority))
553: end
554:
555: ##
556: # Unload a certification authority from the daemon.
557: def unload_authority(authority)
558: call("unload-authority", Message.new(authority))
559: end
560:
561: ##
562: # Load a virtual IP / attribute pool into the daemon.
563: def load_pool(pool)
564: call("load-pool", Message.new(pool))
565: end
566:
567: ##
568: # Unload a virtual IP / attribute pool from the daemon.
569: def unload_pool(pool)
570: call("unload-pool", Message.new(pool))
571: end
572:
573: ##
574: # Get the currently loaded pools.
575: def get_pools(options)
576: call("get-pools", Message.new(options))
577: end
578:
579: ##
580: # Get currently loaded algorithms and their implementation.
581: def get_algorithms
582: call("get-algorithms")
583: end
584:
585: ##
586: # Get global or connection-specific counters for IKE events.
587: def get_counters(options = nil)
588: call("get-counters", Message.new(options))
589: end
590:
591: ##
592: # Reset global or connection-specific IKE event counters.
593: def reset_counters(options = nil)
594: call("reset-counters", Message.new(options))
595: end
596:
597: ##
598: # Listen for a set of event messages. This call is blocking, and invokes
599: # the passed closure for each event received. The closure receives the
600: # event name and the event message as argument. To stop listening, the
601: # closure may raise a StopEventListening exception, the only caught
602: # exception.
603: def listen_events(events, &block)
604: self.class.instance_eval do
605: define_method(:listen_event) do |label, message|
606: block.call(label, message.root)
607: end
608: end
609: events.each do |event|
610: @transp.register(event, method(:listen_event))
611: end
612: begin
613: loop do
614: @transp.read_and_dispatch_event
615: end
616: rescue StopEventListening
617: ensure
618: events.each do |event|
619: @transp.unregister(event, method(:listen_event))
620: end
621: end
622: end
623:
624: ##
625: # Issue a command request. Checks if the reply of a command indicates
626: # "success", otherwise raises a CommandExecError exception.
627: def call(command, request = nil)
628: check_success(@transp.request(command, request))
629: end
630:
631: ##
632: # Issue a command request, but register for a specific event while the
633: # command is active. VICI uses this mechanism to stream potentially large
634: # data objects continuously. The provided closure is invoked for all
635: # event messages.
636: def call_with_event(command, request, event, &block)
637: self.class.instance_eval do
638: define_method(:call_event) do |_label, message|
639: block.call(message.root)
640: end
641: end
642: @transp.register(event, method(:call_event))
643: begin
644: reply = @transp.request(command, request)
645: ensure
646: @transp.unregister(event, method(:call_event))
647: end
648: check_success(reply)
649: end
650:
651: ##
652: # Check if the reply of a command indicates "success", otherwise raise a
653: # CommandExecError exception
654: def check_success(reply)
655: root = reply.root
656: if root.key?("success") && root["success"] != "yes"
657: raise CommandExecError, root["errmsg"]
658: end
659:
660: root
661: end
662: end
663: end
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>