Annotation of embedaddon/strongswan/src/libcharon/plugins/vici/ruby/lib/vici.rb, revision 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>