vici 5.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. data/lib/vici.rb +595 -0
  2. metadata +50 -0
@@ -0,0 +1,595 @@
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) 2014 Martin Willi
7
+ # Copyright (C) 2014 revosec AG
8
+ #
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:
15
+ #
16
+ # The above copyright notice and this permission notice shall be included in
17
+ # all copies or substantial portions of the Software.
18
+ #
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
25
+ # THE SOFTWARE.
26
+
27
+ module Vici
28
+
29
+ ##
30
+ # Vici specific exception all others inherit from
31
+ class Error < StandardError
32
+ end
33
+
34
+ ##
35
+ # Error while parsing a vici message from the daemon
36
+ class ParseError < Error
37
+ end
38
+
39
+ ##
40
+ # Error while encoding a vici message from ruby data structures
41
+ class EncodeError < Error
42
+ end
43
+
44
+ ##
45
+ # Error while exchanging messages over the vici Transport layer
46
+ class TransportError < Error
47
+ end
48
+
49
+ ##
50
+ # Generic vici command execution error
51
+ class CommandError < Error
52
+ end
53
+
54
+ ##
55
+ # Error if an issued vici command is unknown by the daemon
56
+ class CommandUnknownError < CommandError
57
+ end
58
+
59
+ ##
60
+ # Error if a command failed to execute in the daemon
61
+ class CommandExecError < CommandError
62
+ end
63
+
64
+ ##
65
+ # Generic vici event handling error
66
+ class EventError < Error
67
+ end
68
+
69
+ ##
70
+ # Tried to register to / unregister from an unknown vici event
71
+ class EventUnknownError < EventError
72
+ end
73
+
74
+ ##
75
+ # Exception to raise from an event listening closure to stop listening
76
+ class StopEventListening < Exception
77
+ end
78
+
79
+
80
+ ##
81
+ # The Message class provides the low level encoding and decoding of vici
82
+ # protocol messages. Directly using this class is usually not required.
83
+ class Message
84
+
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 = Hash.new()
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
+ if @encoded == nil
106
+ @encoded = encode(@root)
107
+ end
108
+ @encoded
109
+ end
110
+
111
+ ##
112
+ # Get the root element of the parsed ruby data structures
113
+ def root
114
+ if @root == nil
115
+ @root = parse(@encoded)
116
+ end
117
+ @root
118
+ end
119
+
120
+ private
121
+
122
+ def encode_name(name)
123
+ [name.length].pack("c") << name
124
+ end
125
+
126
+ def encode_value(value)
127
+ if value.class != String
128
+ value = value.to_s
129
+ end
130
+ [value.length].pack("n") << value
131
+ end
132
+
133
+ def encode_kv(encoding, key, value)
134
+ encoding << KEY_VALUE << encode_name(key) << encode_value(value)
135
+ end
136
+
137
+ def encode_section(encoding, key, value)
138
+ encoding << SECTION_START << encode_name(key)
139
+ encoding << encode(value) << SECTION_END
140
+ end
141
+
142
+ def encode_list(encoding, key, value)
143
+ encoding << LIST_START << encode_name(key)
144
+ value.each do |item|
145
+ encoding << LIST_ITEM << encode_value(item)
146
+ end
147
+ encoding << LIST_END
148
+ end
149
+
150
+ def encode(node)
151
+ encoding = ""
152
+ node.each do |key, value|
153
+ case value.class
154
+ when String, Fixnum, true, false
155
+ encoding = encode_kv(encoding, key, value)
156
+ else
157
+ if value.is_a?(Hash)
158
+ encoding = encode_section(encoding, key, value)
159
+ elsif value.is_a?(Array)
160
+ encoding = encode_list(encoding, key, value)
161
+ else
162
+ encoding = encode_kv(encoding, key, value)
163
+ end
164
+ end
165
+ end
166
+ encoding
167
+ end
168
+
169
+ def parse_name(encoding)
170
+ len = encoding.unpack("c")[0]
171
+ name = encoding[1, len]
172
+ return encoding[(1 + len)..-1], name
173
+ end
174
+
175
+ def parse_value(encoding)
176
+ len = encoding.unpack("n")[0]
177
+ value = encoding[2, len]
178
+ return encoding[(2 + len)..-1], value
179
+ end
180
+
181
+ def parse(encoding)
182
+ stack = [Hash.new]
183
+ list = nil
184
+ while encoding.length != 0 do
185
+ type = encoding.unpack("c")[0]
186
+ encoding = encoding[1..-1]
187
+ case type
188
+ when SECTION_START
189
+ encoding, name = parse_name(encoding)
190
+ stack.push(stack[-1][name] = Hash.new)
191
+ when SECTION_END
192
+ if stack.length() == 1
193
+ raise ParseError, "unexpected section end"
194
+ end
195
+ stack.pop()
196
+ when KEY_VALUE
197
+ encoding, name = parse_name(encoding)
198
+ encoding, value = parse_value(encoding)
199
+ stack[-1][name] = value
200
+ when LIST_START
201
+ encoding, name = parse_name(encoding)
202
+ stack[-1][name] = []
203
+ list = name
204
+ when LIST_ITEM
205
+ raise ParseError, "unexpected list item" if list == nil
206
+ encoding, value = parse_value(encoding)
207
+ stack[-1][list].push(value)
208
+ when LIST_END
209
+ raise ParseError, "unexpected list end" if list == nil
210
+ list = nil
211
+ else
212
+ raise ParseError, "invalid type: #{type}"
213
+ end
214
+ end
215
+ if stack.length() > 1
216
+ raise ParseError, "unexpected message end"
217
+ end
218
+ stack[0]
219
+ end
220
+ end
221
+
222
+
223
+ ##
224
+ # The Transport class implements to low level segmentation of packets
225
+ # to the underlying transport stream. Directly using this class is usually
226
+ # not required.
227
+ class Transport
228
+
229
+ CMD_REQUEST = 0
230
+ CMD_RESPONSE = 1
231
+ CMD_UNKNOWN = 2
232
+ EVENT_REGISTER = 3
233
+ EVENT_UNREGISTER = 4
234
+ EVENT_CONFIRM = 5
235
+ EVENT_UNKNOWN = 6
236
+ EVENT = 7
237
+
238
+ ##
239
+ # Create a transport layer using a provided socket for communication.
240
+ def initialize(socket)
241
+ @socket = socket
242
+ @events = Hash.new
243
+ end
244
+
245
+ ##
246
+ # Receive data from socket, until len bytes read
247
+ def recv_all(len)
248
+ encoding = ""
249
+ while encoding.length < len do
250
+ data = @socket.recv(len - encoding.length)
251
+ if data.empty?
252
+ raise TransportError, "connection closed"
253
+ end
254
+ encoding << data
255
+ end
256
+ encoding
257
+ end
258
+
259
+ ##
260
+ # Send data to socket, until all bytes sent
261
+ def send_all(encoding)
262
+ len = 0
263
+ while len < encoding.length do
264
+ len += @socket.send(encoding[len..-1], 0)
265
+ end
266
+ end
267
+
268
+ ##
269
+ # Write a packet prefixed by its length over the transport socket. Type
270
+ # specifies the message, the optional label and message get appended.
271
+ def write(type, label, message)
272
+ encoding = ""
273
+ if label
274
+ encoding << label.length << label
275
+ end
276
+ if message
277
+ encoding << message.encoding
278
+ end
279
+ send_all([encoding.length + 1, type].pack("Nc") + encoding)
280
+ end
281
+
282
+ ##
283
+ # Read a packet from the transport socket. Returns the packet type, and
284
+ # if available in the packet a label and the contained message.
285
+ def read
286
+ len = recv_all(4).unpack("N")[0]
287
+ encoding = recv_all(len)
288
+ type = encoding.unpack("c")[0]
289
+ len = 1
290
+ case type
291
+ when CMD_REQUEST, EVENT_REGISTER, EVENT_UNREGISTER, EVENT
292
+ label = encoding[2, encoding[1].unpack("c")[0]]
293
+ len += label.length + 1
294
+ when CMD_RESPONSE, CMD_UNKNOWN, EVENT_CONFIRM, EVENT_UNKNOWN
295
+ label = nil
296
+ else
297
+ raise TransportError, "invalid message: #{type}"
298
+ end
299
+ if encoding.length == len
300
+ return type, label, Message.new
301
+ end
302
+ return type, label, Message.new(encoding[len..-1])
303
+ end
304
+
305
+ def dispatch_event(name, message)
306
+ @events[name].each do |handler|
307
+ handler.call(name, message)
308
+ end
309
+ end
310
+
311
+ def read_and_dispatch_event
312
+ type, label, message = read
313
+ p
314
+ if type == EVENT
315
+ dispatch_event(label, message)
316
+ else
317
+ raise TransportError, "unexpected message: #{type}"
318
+ end
319
+ end
320
+
321
+ def read_and_dispatch_events
322
+ loop do
323
+ type, label, message = read
324
+ if type == EVENT
325
+ dispatch_event(label, message)
326
+ else
327
+ return type, label, message
328
+ end
329
+ end
330
+ end
331
+
332
+ ##
333
+ # Send a command with a given name, and optionally a message. Returns
334
+ # the reply message on success.
335
+ def request(name, message = nil)
336
+ write(CMD_REQUEST, name, message)
337
+ type, label, message = read_and_dispatch_events
338
+ case type
339
+ when CMD_RESPONSE
340
+ return message
341
+ when CMD_UNKNOWN
342
+ raise CommandUnknownError, name
343
+ else
344
+ raise CommandError, "invalid response for #{name}"
345
+ end
346
+ end
347
+
348
+ ##
349
+ # Register a handler method for the given event name
350
+ def register(name, handler)
351
+ write(EVENT_REGISTER, name, nil)
352
+ type, label, message = read_and_dispatch_events
353
+ case type
354
+ when EVENT_CONFIRM
355
+ if @events.has_key?(name)
356
+ @events[name] += [handler]
357
+ else
358
+ @events[name] = [handler];
359
+ end
360
+ when EVENT_UNKNOWN
361
+ raise EventUnknownError, name
362
+ else
363
+ raise EventError, "invalid response for #{name} register"
364
+ end
365
+ end
366
+
367
+ ##
368
+ # Unregister a handler method for the given event name
369
+ def unregister(name, handler)
370
+ write(EVENT_UNREGISTER, name, nil)
371
+ type, label, message = read_and_dispatch_events
372
+ case type
373
+ when EVENT_CONFIRM
374
+ @events[name] -= [handler]
375
+ when EVENT_UNKNOWN
376
+ raise EventUnknownError, name
377
+ else
378
+ raise EventError, "invalid response for #{name} unregister"
379
+ end
380
+ end
381
+ end
382
+
383
+
384
+ ##
385
+ # The Connection class provides the high-level interface to monitor, configure
386
+ # and control the IKE daemon. It takes a connected stream-oriented Socket for
387
+ # the communication with the IKE daemon.
388
+ #
389
+ # This class takes and returns ruby objects for the exchanged message data.
390
+ # * Sections get encoded as Hash, containing other sections as Hash, or
391
+ # * Key/Values, where the values are Strings as Hash values
392
+ # * Lists get encoded as Arrays with String values
393
+ # Non-String values that are not a Hash nor an Array get converted with .to_s
394
+ # during encoding.
395
+ class Connection
396
+
397
+ def initialize(socket = nil)
398
+ if socket == nil
399
+ socket = UNIXSocket.new("/var/run/charon.vici")
400
+ end
401
+ @transp = Transport.new(socket)
402
+ end
403
+
404
+ ##
405
+ # List matching loaded connections. The provided closure is invoked
406
+ # for each matching connection.
407
+ def list_conns(match = nil, &block)
408
+ call_with_event("list-conns", Message.new(match), "list-conn", &block)
409
+ end
410
+
411
+ ##
412
+ # List matching active SAs. The provided closure is invoked for each
413
+ # matching SA.
414
+ def list_sas(match = nil, &block)
415
+ call_with_event("list-sas", Message.new(match), "list-sa", &block)
416
+ end
417
+
418
+ ##
419
+ # List matching installed policies. The provided closure is invoked
420
+ # for each matching policy.
421
+ def list_policies(match, &block)
422
+ call_with_event("list-policies", Message.new(match), "list-policy",
423
+ &block)
424
+ end
425
+
426
+ ##
427
+ # List matching loaded certificates. The provided closure is invoked
428
+ # for each matching certificate definition.
429
+ def list_certs(match = nil, &block)
430
+ call_with_event("list-certs", Message.new(match), "list-cert", &block)
431
+ end
432
+
433
+ ##
434
+ # Load a connection into the daemon.
435
+ def load_conn(conn)
436
+ check_success(@transp.request("load-conn", Message.new(conn)))
437
+ end
438
+
439
+ ##
440
+ # Unload a connection from the daemon.
441
+ def unload_conn(conn)
442
+ check_success(@transp.request("unload-conn", Message.new(conn)))
443
+ end
444
+
445
+ ##
446
+ # Get the names of connections managed by vici.
447
+ def get_conns()
448
+ @transp.request("get-conns").root
449
+ end
450
+
451
+ ##
452
+ # Clear all loaded credentials.
453
+ def clear_creds()
454
+ check_success(@transp.request("clear-creds"))
455
+ end
456
+
457
+ ##
458
+ # Load a certificate into the daemon.
459
+ def load_cert(cert)
460
+ check_success(@transp.request("load-cert", Message.new(cert)))
461
+ end
462
+
463
+ ##
464
+ # Load a private key into the daemon.
465
+ def load_key(key)
466
+ check_success(@transp.request("load-key", Message.new(key)))
467
+ end
468
+
469
+ ##
470
+ # Load a shared key into the daemon.
471
+ def load_shared(shared)
472
+ check_success(@transp.request("load-shared", Message.new(shared)))
473
+ end
474
+
475
+ ##
476
+ # Load a virtual IP / attribute pool
477
+ def load_pool(pool)
478
+ check_success(@transp.request("load-pool", Message.new(pool)))
479
+ end
480
+
481
+ ##
482
+ # Unload a virtual IP / attribute pool
483
+ def unload_pool(pool)
484
+ check_success(@transp.request("unload-pool", Message.new(pool)))
485
+ end
486
+
487
+ ##
488
+ # Get the currently loaded pools.
489
+ def get_pools()
490
+ @transp.request("get-pools").root
491
+ end
492
+
493
+ ##
494
+ # Initiate a connection. The provided closure is invoked for each log line.
495
+ def initiate(options, &block)
496
+ check_success(call_with_event("initiate", Message.new(options),
497
+ "control-log", &block))
498
+ end
499
+
500
+ ##
501
+ # Terminate a connection. The provided closure is invoked for each log line.
502
+ def terminate(options, &block)
503
+ check_success(call_with_event("terminate", Message.new(options),
504
+ "control-log", &block))
505
+ end
506
+
507
+ ##
508
+ # Install a shunt/route policy.
509
+ def install(policy)
510
+ check_success(@transp.request("install", Message.new(policy)))
511
+ end
512
+
513
+ ##
514
+ # Uninstall a shunt/route policy.
515
+ def uninstall(policy)
516
+ check_success(@transp.request("uninstall", Message.new(policy)))
517
+ end
518
+
519
+ ##
520
+ # Reload strongswan.conf settings.
521
+ def reload_settings
522
+ check_success(@transp.request("reload-settings", nil))
523
+ end
524
+
525
+ ##
526
+ # Get daemon statistics and information.
527
+ def stats
528
+ @transp.request("stats", nil).root
529
+ end
530
+
531
+ ##
532
+ # Get daemon version information
533
+ def version
534
+ @transp.request("version", nil).root
535
+ end
536
+
537
+ ##
538
+ # Listen for a set of event messages. This call is blocking, and invokes
539
+ # the passed closure for each event received. The closure receives the
540
+ # event name and the event message as argument. To stop listening, the
541
+ # closure may raise a StopEventListening exception, the only catched
542
+ # exception.
543
+ def listen_events(events, &block)
544
+ self.class.instance_eval do
545
+ define_method(:listen_event) do |label, message|
546
+ block.call(label, message.root)
547
+ end
548
+ end
549
+ events.each do |event|
550
+ @transp.register(event, method(:listen_event))
551
+ end
552
+ begin
553
+ loop do
554
+ @transp.read_and_dispatch_event
555
+ end
556
+ rescue StopEventListening
557
+ ensure
558
+ events.each do |event|
559
+ @transp.unregister(event, method(:listen_event))
560
+ end
561
+ end
562
+ end
563
+
564
+ ##
565
+ # Issue a command request, but register for a specific event while the
566
+ # command is active. VICI uses this mechanism to stream potentially large
567
+ # data objects continuously. The provided closure is invoked for all
568
+ # event messages.
569
+ def call_with_event(command, request, event, &block)
570
+ self.class.instance_eval do
571
+ define_method(:call_event) do |label, message|
572
+ block.call(message.root)
573
+ end
574
+ end
575
+ @transp.register(event, method(:call_event))
576
+ begin
577
+ reply = @transp.request(command, request)
578
+ ensure
579
+ @transp.unregister(event, method(:call_event))
580
+ end
581
+ reply
582
+ end
583
+
584
+ ##
585
+ # Check if the reply of a command indicates "success", otherwise raise a
586
+ # CommandExecError exception
587
+ def check_success(reply)
588
+ root = reply.root
589
+ if root["success"] != "yes"
590
+ raise CommandExecError, root["errmsg"]
591
+ end
592
+ root
593
+ end
594
+ end
595
+ end
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vici
3
+ version: !ruby/object:Gem::Version
4
+ version: 5.3.5
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Martin Willi
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-01-22 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: ! "\n The strongSwan VICI protocol allows external application to
15
+ monitor,\n configure and control the IKE daemon charon. This ruby gem provides
16
+ a\n native client side implementation of the VICI protocol, well suited to\n
17
+ \ script automated tasks in a relaible way.\n "
18
+ email:
19
+ - martin@strongswan.org
20
+ executables: []
21
+ extensions: []
22
+ extra_rdoc_files: []
23
+ files:
24
+ - lib/vici.rb
25
+ homepage: https://wiki.strongswan.org/projects/strongswan/wiki/Vici
26
+ licenses:
27
+ - MIT
28
+ post_install_message:
29
+ rdoc_options: []
30
+ require_paths:
31
+ - lib
32
+ required_ruby_version: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ requirements: []
45
+ rubyforge_project:
46
+ rubygems_version: 1.8.23
47
+ signing_key:
48
+ specification_version: 3
49
+ summary: Native ruby interface for strongSwan VICI
50
+ test_files: []