1383fa778365a215b615b9d6fab37188bf1ea20f
[strongswan.git] / src / libcharon / plugins / vici / python / vici / session.py
1 import collections
2 import socket
3
4 from .exception import SessionException, CommandException, EventUnknownException
5 from .protocol import Transport, Packet, Message
6
7
8 class Session(object):
9 def __init__(self, sock=None):
10 if sock is None:
11 sock = socket.socket(socket.AF_UNIX)
12 sock.connect("/var/run/charon.vici")
13 self.handler = SessionHandler(Transport(sock))
14
15 def version(self):
16 """Retrieve daemon and system specific version information.
17
18 :return: daemon and system specific version information
19 :rtype: dict
20 """
21 return self.handler.request("version")
22
23 def stats(self):
24 """Retrieve IKE daemon statistics and load information.
25
26 :return: IKE daemon statistics and load information
27 :rtype: dict
28 """
29 return self.handler.request("stats")
30
31 def reload_settings(self):
32 """Reload strongswan.conf settings and any plugins supporting reload.
33 """
34 self.handler.request("reload-settings")
35
36 def initiate(self, sa):
37 """Initiate an SA.
38
39 :param sa: the SA to initiate
40 :type sa: dict
41 :return: generator for logs emitted as dict
42 :rtype: generator
43 """
44 return self.handler.streamed_request("initiate", "control-log", sa)
45
46 def terminate(self, sa):
47 """Terminate an SA.
48
49 :param sa: the SA to terminate
50 :type sa: dict
51 :return: generator for logs emitted as dict
52 :rtype: generator
53 """
54 return self.handler.streamed_request("terminate", "control-log", sa)
55
56 def redirect(self, sa):
57 """Redirect an IKE_SA.
58
59 :param sa: the SA to redirect
60 :type sa: dict
61 """
62 self.handler.request("redirect", sa)
63
64 def install(self, policy):
65 """Install a trap, drop or bypass policy defined by a CHILD_SA config.
66
67 :param policy: policy to install
68 :type policy: dict
69 """
70 self.handler.request("install", policy)
71
72 def uninstall(self, policy):
73 """Uninstall a trap, drop or bypass policy defined by a CHILD_SA config.
74
75 :param policy: policy to uninstall
76 :type policy: dict
77 """
78 self.handler.request("uninstall", policy)
79
80 def list_sas(self, filters=None):
81 """Retrieve active IKE_SAs and associated CHILD_SAs.
82
83 :param filters: retrieve only matching IKE_SAs (optional)
84 :type filters: dict
85 :return: generator for active IKE_SAs and associated CHILD_SAs as dict
86 :rtype: generator
87 """
88 return self.handler.streamed_request("list-sas", "list-sa", filters)
89
90 def list_policies(self, filters=None):
91 """Retrieve installed trap, drop and bypass policies.
92
93 :param filters: retrieve only matching policies (optional)
94 :type filters: dict
95 :return: generator for installed trap, drop and bypass policies as dict
96 :rtype: generator
97 """
98 return self.handler.streamed_request("list-policies", "list-policy",
99 filters)
100
101 def list_conns(self, filters=None):
102 """Retrieve loaded connections.
103
104 :param filters: retrieve only matching configuration names (optional)
105 :type filters: dict
106 :return: generator for loaded connections as dict
107 :rtype: generator
108 """
109 return self.handler.streamed_request("list-conns", "list-conn",
110 filters)
111
112 def get_conns(self):
113 """Retrieve connection names loaded exclusively over vici.
114
115 :return: connection names
116 :rtype: dict
117 """
118 return self.handler.request("get-conns")
119
120 def list_certs(self, filters=None):
121 """Retrieve loaded certificates.
122
123 :param filters: retrieve only matching certificates (optional)
124 :type filters: dict
125 :return: generator for loaded certificates as dict
126 :rtype: generator
127 """
128 return self.handler.streamed_request("list-certs", "list-cert", filters)
129
130 def load_conn(self, connection):
131 """Load a connection definition into the daemon.
132
133 :param connection: connection definition
134 :type connection: dict
135 """
136 self.handler.request("load-conn", connection)
137
138 def unload_conn(self, name):
139 """Unload a connection definition.
140
141 :param name: connection definition name
142 :type name: dict
143 """
144 self.handler.request("unload-conn", name)
145
146 def load_cert(self, certificate):
147 """Load a certificate into the daemon.
148
149 :param certificate: PEM or DER encoded certificate
150 :type certificate: dict
151 """
152 self.handler.request("load-cert", certificate)
153
154 def load_key(self, private_key):
155 """Load a private key into the daemon.
156
157 :param private_key: PEM or DER encoded key
158 """
159 self.handler.request("load-key", private_key)
160
161 def load_shared(self, secret):
162 """Load a shared IKE PSK, EAP or XAuth secret into the daemon.
163
164 :param secret: shared IKE PSK, EAP or XAuth secret
165 :type secret: dict
166 """
167 self.handler.request("load-shared", secret)
168
169 def flush_certs(self, filter=None):
170 """Flush the volatile certificate cache.
171
172 Flush the certificate stored temporarily in the cache. The filter
173 allows to flush only a certain type of certificates, e.g. CRLs.
174
175 :param filter: flush only certificates of a given type (optional)
176 :type filter: dict
177 """
178 self.handler.request("flush-certs", filter)
179
180 def clear_creds(self):
181 """Clear credentials loaded over vici.
182
183 Clear all loaded certificate, private key and shared key credentials.
184 This affects only credentials loaded over vici, but additionally
185 flushes the credential cache.
186 """
187 self.handler.request("clear-creds")
188
189 def load_pool(self, pool):
190 """Load a virtual IP pool.
191
192 Load an in-memory virtual IP and configuration attribute pool.
193 Existing pools with the same name get updated, if possible.
194
195 :param pool: virtual IP and configuration attribute pool
196 :type pool: dict
197 """
198 return self.handler.request("load-pool", pool)
199
200 def unload_pool(self, pool_name):
201 """Unload a virtual IP pool.
202
203 Unload a previously loaded virtual IP and configuration attribute pool.
204 Unloading fails for pools with leases currently online.
205
206 :param pool_name: pool by name
207 :type pool_name: dict
208 """
209 self.handler.request("unload-pool", pool_name)
210
211 def get_pools(self, options):
212 """Retrieve loaded pools.
213
214 :param options: filter by name and/or retrieve leases (optional)
215 :type options: dict
216 :return: loaded pools
217 :rtype: dict
218 """
219 return self.handler.request("get-pools", options)
220
221 def listen(self, event_types):
222 """Register and listen for the given events.
223
224 :param event_types: event types to register
225 :type event_types: list
226 :return: generator for streamed event responses as (event_type, dict)
227 :rtype: generator
228 """
229 return self.handler.listen(event_types)
230
231
232 class SessionHandler(object):
233 """Handles client command execution requests over vici."""
234
235 def __init__(self, transport):
236 self.transport = transport
237
238 def _communicate(self, packet):
239 """Send packet over transport and parse response.
240
241 :param packet: packet to send
242 :type packet: :py:class:`vici.protocol.Packet`
243 :return: parsed packet in a tuple with message type and payload
244 :rtype: :py:class:`collections.namedtuple`
245 """
246 self.transport.send(packet)
247 return Packet.parse(self.transport.receive())
248
249 def _register_unregister(self, event_type, register):
250 """Register or unregister for the given event.
251
252 :param event_type: event to register
253 :type event_type: str
254 :param register: whether to register or unregister
255 :type register: bool
256 """
257 if register:
258 packet = Packet.register_event(event_type)
259 else:
260 packet = Packet.unregister_event(event_type)
261 response = self._communicate(packet)
262 if response.response_type == Packet.EVENT_UNKNOWN:
263 raise EventUnknownException(
264 "Unknown event type '{event}'".format(event=event_type)
265 )
266 elif response.response_type != Packet.EVENT_CONFIRM:
267 raise SessionException(
268 "Unexpected response type {type}, "
269 "expected '{confirm}' (EVENT_CONFIRM)".format(
270 type=response.response_type,
271 confirm=Packet.EVENT_CONFIRM,
272 )
273 )
274
275 def request(self, command, message=None):
276 """Send request with an optional message.
277
278 :param command: command to send
279 :type command: str
280 :param message: message (optional)
281 :type message: str
282 :return: command result
283 :rtype: dict
284 """
285 if message is not None:
286 message = Message.serialize(message)
287 packet = Packet.request(command, message)
288 response = self._communicate(packet)
289
290 if response.response_type != Packet.CMD_RESPONSE:
291 raise SessionException(
292 "Unexpected response type {type}, "
293 "expected '{response}' (CMD_RESPONSE)".format(
294 type=response.response_type,
295 response=Packet.CMD_RESPONSE
296 )
297 )
298
299 command_response = Message.deserialize(response.payload)
300 if "success" in command_response:
301 if command_response["success"] != b"yes":
302 raise CommandException(
303 "Command failed: {errmsg}".format(
304 errmsg=command_response["errmsg"]
305 )
306 )
307
308 return command_response
309
310 def streamed_request(self, command, event_stream_type, message=None):
311 """Send command request and collect and return all emitted events.
312
313 :param command: command to send
314 :type command: str
315 :param event_stream_type: event type emitted on command execution
316 :type event_stream_type: str
317 :param message: message (optional)
318 :type message: str
319 :return: generator for streamed event responses as dict
320 :rtype: generator
321 """
322 if message is not None:
323 message = Message.serialize(message)
324
325 self._register_unregister(event_stream_type, True);
326
327 try:
328 packet = Packet.request(command, message)
329 self.transport.send(packet)
330 exited = False
331 while True:
332 response = Packet.parse(self.transport.receive())
333 if response.response_type == Packet.EVENT:
334 if not exited:
335 try:
336 yield Message.deserialize(response.payload)
337 except GeneratorExit:
338 exited = True
339 pass
340 else:
341 break
342
343 if response.response_type == Packet.CMD_RESPONSE:
344 command_response = Message.deserialize(response.payload)
345 else:
346 raise SessionException(
347 "Unexpected response type {type}, "
348 "expected '{response}' (CMD_RESPONSE)".format(
349 type=response.response_type,
350 response=Packet.CMD_RESPONSE
351 )
352 )
353
354 finally:
355 self._register_unregister(event_stream_type, False);
356
357 # evaluate command result, if any
358 if "success" in command_response:
359 if command_response["success"] != b"yes":
360 raise CommandException(
361 "Command failed: {errmsg}".format(
362 errmsg=command_response["errmsg"]
363 )
364 )
365
366 def listen(self, event_types):
367 """Register and listen for the given events.
368
369 :param event_types: event types to register
370 :type event_types: list
371 :return: generator for streamed event responses as (event_type, dict)
372 :rtype: generator
373 """
374 for event_type in event_types:
375 self._register_unregister(event_type, True)
376
377 try:
378 while True:
379 response = Packet.parse(self.transport.receive())
380 if response.response_type == Packet.EVENT:
381 try:
382 yield response.event_type, Message.deserialize(response.payload)
383 except GeneratorExit:
384 break
385
386 finally:
387 for event_type in event_types:
388 self._register_unregister(event_type, False)