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>