66de8590a92b1f159814161e2feec13d55100ce6
[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 clear_creds(self):
170 """Clear credentials loaded over vici.
171
172 Clear all loaded certificate, private key and shared key credentials.
173 This affects only credentials loaded over vici, but additionally
174 flushes the credential cache.
175 """
176 self.handler.request("clear-creds")
177
178 def load_pool(self, pool):
179 """Load a virtual IP pool.
180
181 Load an in-memory virtual IP and configuration attribute pool.
182 Existing pools with the same name get updated, if possible.
183
184 :param pool: virtual IP and configuration attribute pool
185 :type pool: dict
186 """
187 return self.handler.request("load-pool", pool)
188
189 def unload_pool(self, pool_name):
190 """Unload a virtual IP pool.
191
192 Unload a previously loaded virtual IP and configuration attribute pool.
193 Unloading fails for pools with leases currently online.
194
195 :param pool_name: pool by name
196 :type pool_name: dict
197 """
198 self.handler.request("unload-pool", pool_name)
199
200 def get_pools(self):
201 """Retrieve loaded pools.
202
203 :return: loaded pools
204 :rtype: dict
205 """
206 return self.handler.request("get-pools")
207
208 def listen(self, event_types):
209 """Register and listen for the given events.
210
211 :param event_types: event types to register
212 :type event_types: list
213 :return: generator for streamed event responses as (event_type, dict)
214 :rtype: generator
215 """
216 return self.handler.listen(event_types)
217
218
219 class SessionHandler(object):
220 """Handles client command execution requests over vici."""
221
222 def __init__(self, transport):
223 self.transport = transport
224
225 def _communicate(self, packet):
226 """Send packet over transport and parse response.
227
228 :param packet: packet to send
229 :type packet: :py:class:`vici.protocol.Packet`
230 :return: parsed packet in a tuple with message type and payload
231 :rtype: :py:class:`collections.namedtuple`
232 """
233 self.transport.send(packet)
234 return Packet.parse(self.transport.receive())
235
236 def _register_unregister(self, event_type, register):
237 """Register or unregister for the given event.
238
239 :param event_type: event to register
240 :type event_type: str
241 :param register: whether to register or unregister
242 :type register: bool
243 """
244 if register:
245 packet = Packet.register_event(event_type)
246 else:
247 packet = Packet.unregister_event(event_type)
248 response = self._communicate(packet)
249 if response.response_type == Packet.EVENT_UNKNOWN:
250 raise EventUnknownException(
251 "Unknown event type '{event}'".format(event=event_type)
252 )
253 elif response.response_type != Packet.EVENT_CONFIRM:
254 raise SessionException(
255 "Unexpected response type {type}, "
256 "expected '{confirm}' (EVENT_CONFIRM)".format(
257 type=response.response_type,
258 confirm=Packet.EVENT_CONFIRM,
259 )
260 )
261
262 def request(self, command, message=None):
263 """Send request with an optional message.
264
265 :param command: command to send
266 :type command: str
267 :param message: message (optional)
268 :type message: str
269 :return: command result
270 :rtype: dict
271 """
272 if message is not None:
273 message = Message.serialize(message)
274 packet = Packet.request(command, message)
275 response = self._communicate(packet)
276
277 if response.response_type != Packet.CMD_RESPONSE:
278 raise SessionException(
279 "Unexpected response type {type}, "
280 "expected '{response}' (CMD_RESPONSE)".format(
281 type=response.response_type,
282 response=Packet.CMD_RESPONSE
283 )
284 )
285
286 command_response = Message.deserialize(response.payload)
287 if "success" in command_response:
288 if command_response["success"] != b"yes":
289 raise CommandException(
290 "Command failed: {errmsg}".format(
291 errmsg=command_response["errmsg"]
292 )
293 )
294
295 return command_response
296
297 def streamed_request(self, command, event_stream_type, message=None):
298 """Send command request and collect and return all emitted events.
299
300 :param command: command to send
301 :type command: str
302 :param event_stream_type: event type emitted on command execution
303 :type event_stream_type: str
304 :param message: message (optional)
305 :type message: str
306 :return: generator for streamed event responses as dict
307 :rtype: generator
308 """
309 if message is not None:
310 message = Message.serialize(message)
311
312 self._register_unregister(event_stream_type, True);
313
314 try:
315 packet = Packet.request(command, message)
316 self.transport.send(packet)
317 exited = False
318 while True:
319 response = Packet.parse(self.transport.receive())
320 if response.response_type == Packet.EVENT:
321 if not exited:
322 try:
323 yield Message.deserialize(response.payload)
324 except GeneratorExit:
325 exited = True
326 pass
327 else:
328 break
329
330 if response.response_type == Packet.CMD_RESPONSE:
331 command_response = Message.deserialize(response.payload)
332 else:
333 raise SessionException(
334 "Unexpected response type {type}, "
335 "expected '{response}' (CMD_RESPONSE)".format(
336 type=response.response_type,
337 response=Packet.CMD_RESPONSE
338 )
339 )
340
341 finally:
342 self._register_unregister(event_stream_type, False);
343
344 # evaluate command result, if any
345 if "success" in command_response:
346 if command_response["success"] != b"yes":
347 raise CommandException(
348 "Command failed: {errmsg}".format(
349 errmsg=command_response["errmsg"]
350 )
351 )
352
353 def listen(self, event_types):
354 """Register and listen for the given events.
355
356 :param event_types: event types to register
357 :type event_types: list
358 :return: generator for streamed event responses as (event_type, dict)
359 :rtype: generator
360 """
361 for event_type in event_types:
362 self._register_unregister(event_type, True)
363
364 try:
365 while True:
366 response = Packet.parse(self.transport.receive())
367 if response.response_type == Packet.EVENT:
368 try:
369 yield response.event_type, Message.deserialize(response.payload)
370 except GeneratorExit:
371 break
372
373 finally:
374 for event_type in event_types:
375 self._register_unregister(event_type, False)