172252065c3e8c0ca18f56c274256e0d56fb3227
[strongswan.git] / src / libcharon / plugins / vici / python / vici / session.py
1 import collections
2 import socket
3
4 from .exception import SessionException, CommandException
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: logs emitted by command
42 :rtype: list
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: logs emitted by command
52 :rtype: list
53 """
54 return self.handler.streamed_request("terminate", "control-log", sa)
55
56 def install(self, policy):
57 """Install a trap, drop or bypass policy defined by a CHILD_SA config.
58
59 :param policy: policy to install
60 :type policy: dict
61 """
62 self.handler.request("install", policy)
63
64 def uninstall(self, policy):
65 """Uninstall a trap, drop or bypass policy defined by a CHILD_SA config.
66
67 :param policy: policy to uninstall
68 :type policy: dict
69 """
70 self.handler.request("uninstall", policy)
71
72 def list_sas(self, filters=None):
73 """Retrieve active IKE_SAs and associated CHILD_SAs.
74
75 :param filters: retrieve only matching IKE_SAs (optional)
76 :type filters: dict
77 :return: list of active IKE_SAs and associated CHILD_SAs
78 :rtype: list
79 """
80 return self.handler.streamed_request("list-sas", "list-sa", filters)
81
82 def list_policies(self, filters=None):
83 """Retrieve installed trap, drop and bypass policies.
84
85 :param filters: retrieve only matching policies (optional)
86 :type filters: dict
87 :return: list of installed trap, drop and bypass policies
88 :rtype: list
89 """
90 return self.handler.streamed_request("list-policies", "list-policy",
91 filters)
92
93 def list_conns(self, filters=None):
94 """Retrieve loaded connections.
95
96 :param filters: retrieve only matching configuration names (optional)
97 :type filters: dict
98 :return: list of connections
99 :rtype: list
100 """
101 return self.handler.streamed_request("list-conns", "list-conn",
102 filters)
103
104 def get_conns(self):
105 """Retrieve connection names loaded exclusively over vici.
106
107 :return: connection names
108 :rtype: dict
109 """
110 return self.handler.request("get-conns")
111
112 def list_certs(self, filters=None):
113 """Retrieve loaded certificates.
114
115 :param filters: retrieve only matching certificates (optional)
116 :type filters: dict
117 :return: list of installed trap, drop and bypass policies
118 :rtype: list
119 """
120 return self.handler.streamed_request("list-certs", "list-cert", filters)
121
122 def load_conn(self, connection):
123 """Load a connection definition into the daemon.
124
125 :param connection: connection definition
126 :type connection: dict
127 """
128 self.handler.request("load-conn", connection)
129
130 def unload_conn(self, name):
131 """Unload a connection definition.
132
133 :param name: connection definition name
134 :type name: dict
135 """
136 self.handler.request("unload-conn", name)
137
138 def load_cert(self, certificate):
139 """Load a certificate into the daemon.
140
141 :param certificate: PEM or DER encoded certificate
142 :type certificate: dict
143 """
144 self.handler.request("load-cert", certificate)
145
146 def load_key(self, private_key):
147 """Load a private key into the daemon.
148
149 :param private_key: PEM or DER encoded key
150 """
151 self.handler.request("load-key", private_key)
152
153 def load_shared(self, secret):
154 """Load a shared IKE PSK, EAP or XAuth secret into the daemon.
155
156 :param secret: shared IKE PSK, EAP or XAuth secret
157 :type secret: dict
158 """
159 self.handler.request("load-shared", secret)
160
161 def clear_creds(self):
162 """Clear credentials loaded over vici.
163
164 Clear all loaded certificate, private key and shared key credentials.
165 This affects only credentials loaded over vici, but additionally
166 flushes the credential cache.
167 """
168 self.handler.request("clear-creds")
169
170 def load_pool(self, pool):
171 """Load a virtual IP pool.
172
173 Load an in-memory virtual IP and configuration attribute pool.
174 Existing pools with the same name get updated, if possible.
175
176 :param pool: virtual IP and configuration attribute pool
177 :type pool: dict
178 """
179 return self.handler.request("load-pool", pool)
180
181 def unload_pool(self, pool_name):
182 """Unload a virtual IP pool.
183
184 Unload a previously loaded virtual IP and configuration attribute pool.
185 Unloading fails for pools with leases currently online.
186
187 :param pool_name: pool by name
188 :type pool_name: dict
189 """
190 self.handler.request("unload-pool", pool_name)
191
192 def get_pools(self):
193 """Retrieve loaded pools.
194
195 :return: loaded pools
196 :rtype: dict
197 """
198 return self.handler.request("get-pools")
199
200
201 class SessionHandler(object):
202 """Handles client command execution requests over vici."""
203
204 def __init__(self, transport):
205 self.transport = transport
206 self.log_events = collections.deque()
207
208 def _communicate(self, packet):
209 """Send packet over transport and parse response.
210
211 :param packet: packet to send
212 :type packet: :py:class:`vici.protocol.Packet`
213 :return: parsed packet in a tuple with message type and payload
214 :rtype: :py:class:`collections.namedtuple`
215 """
216 self.transport.send(packet)
217 return self._read()
218
219 def request(self, command, message=None):
220 """Send request with an optional message.
221
222 :param command: command to send
223 :type command: str
224 :param message: message (optional)
225 :type message: str
226 :return: command result
227 :rtype: dict
228 """
229 if message is not None:
230 message = Message.serialize(message)
231 packet = Packet.request(command, message)
232 response = self._communicate(packet)
233
234 if response.response_type != Packet.CMD_RESPONSE:
235 raise SessionException(
236 "Unexpected response type {type}, "
237 "expected '{response}' (CMD_RESPONSE)".format(
238 type=response.response_type,
239 response=Packet.CMD_RESPONSE
240 )
241 )
242
243 command_response = Message.deserialize(response.payload)
244 if "success" in command_response:
245 if command_response["success"] != "yes":
246 raise CommandException(
247 "Command failed: {errmsg}".format(
248 errmsg=command_response["errmsg"]
249 )
250 )
251
252 return command_response
253
254 def streamed_request(self, command, event_stream_type, message=None):
255 """Send command request and collect and return all emitted events.
256
257 :param command: command to send
258 :type command: str
259 :param event_stream_type: event type emitted on command execution
260 :type event_stream_type: str
261 :param message: message (optional)
262 :type message: str
263 :return: a pair of the command result and a list of emitted events
264 :rtype: tuple
265 """
266 result = []
267
268 if message is not None:
269 message = Message.serialize(message)
270
271 # subscribe to event stream
272 packet = Packet.register_event(event_stream_type)
273 response = self._communicate(packet)
274
275 if response.response_type != Packet.EVENT_CONFIRM:
276 raise SessionException(
277 "Unexpected response type {type}, "
278 "expected '{confirm}' (EVENT_CONFIRM)".format(
279 type=response.response_type,
280 confirm=Packet.EVENT_CONFIRM,
281 )
282 )
283
284 # issue command, and read any event messages
285 packet = Packet.request(command, message)
286 self.transport.send(packet)
287 response = self._read()
288 while response.response_type == Packet.EVENT:
289 result.append(Message.deserialize(response.payload))
290 response = self._read()
291
292 if response.response_type == Packet.CMD_RESPONSE:
293 Message.deserialize(response.payload)
294 else:
295 raise SessionException(
296 "Unexpected response type {type}, "
297 "expected '{response}' (CMD_RESPONSE)".format(
298 type=response.response_type,
299 response=Packet.CMD_RESPONSE
300 )
301 )
302
303 # unsubscribe from event stream
304 packet = Packet.unregister_event(event_stream_type)
305 response = self._communicate(packet)
306 if response.response_type != Packet.EVENT_CONFIRM:
307 raise SessionException(
308 "Unexpected response type {type}, "
309 "expected '{confirm}' (EVENT_CONFIRM)".format(
310 type=response.response_type,
311 confirm=Packet.EVENT_CONFIRM,
312 )
313 )
314
315 return result
316
317
318 def _read(self):
319 """Get next packet from transport.
320
321 :return: parsed packet in a tuple with message type and payload
322 :rtype: :py:class:`collections.namedtuple`
323 """
324 raw_response = self.transport.receive()
325 response = Packet.parse(raw_response)
326
327 # FIXME
328 if response.response_type == Packet.EVENT and response.event_type == "log":
329 # queue up any debug log messages, and get next
330 self.log_events.append(response)
331 # do something?
332 self._read()
333 else:
334 return response