57
55
domain = 'se.recompile'
58
56
server_interface = domain + '.Mandos'
59
57
client_interface = domain + '.Mandos.Client'
63
dbus.OBJECT_MANAGER_IFACE
64
except AttributeError:
65
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
60
# Always run in monochrome mode
61
urwid.curses_display.curses.has_colors = lambda : False
63
# Urwid doesn't support blinking, but we want it. Since we have no
64
# use for underline on its own, we make underline also always blink.
65
urwid.curses_display.curses.A_UNDERLINE |= (
66
urwid.curses_display.curses.A_BLINK)
67
68
def isoformat_to_datetime(iso):
68
69
"Parse an ISO 8601 date string to a datetime.datetime()"
85
86
properties and calls a hook function when any of them are
88
def __init__(self, proxy_object=None, properties=None, **kwargs):
89
def __init__(self, proxy_object=None, *args, **kwargs):
89
90
self.proxy = proxy_object # Mandos Client proxy object
90
self.properties = dict() if properties is None else properties
92
self.properties = dict()
91
93
self.property_changed_match = (
92
self.proxy.connect_to_signal("PropertiesChanged",
93
self.properties_changed,
94
dbus.PROPERTIES_IFACE,
94
self.proxy.connect_to_signal("PropertyChanged",
95
self.property_changed,
97
if properties is None:
98
self.properties.update(
99
self.proxy.GetAll(client_interface,
101
= dbus.PROPERTIES_IFACE))
103
super(MandosClientPropertyCache, self).__init__(**kwargs)
99
self.properties.update(
100
self.proxy.GetAll(client_interface,
101
dbus_interface = dbus.PROPERTIES_IFACE))
103
#XXX This breaks good super behaviour
104
# super(MandosClientPropertyCache, self).__init__(
105
def properties_changed(self, interface, properties, invalidated):
106
"""This is called whenever we get a PropertiesChanged signal
107
It updates the changed properties in the "properties" dict.
107
def property_changed(self, property=None, value=None):
108
"""This is called whenever we get a PropertyChanged signal
109
It updates the changed property in the "properties" dict.
109
111
# Update properties dict with new value
110
if interface == client_interface:
111
self.properties.update(properties)
112
self.properties[property] = value
114
def delete(self, *args, **kwargs):
114
115
self.property_changed_match.remove()
116
super(MandosClientPropertyCache, self).__init__(
117
120
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
130
133
self.logger = logger
132
135
self._update_timer_callback_tag = None
136
self._update_timer_callback_lock = 0
134
138
# The widget shown normally
135
139
self._text_widget = urwid.Text("")
136
140
# The widget shown when we have focus
137
141
self._focus_text_widget = urwid.Text("")
138
super(MandosClientWidget, self).__init__(**kwargs)
142
super(MandosClientWidget, self).__init__(
143
update_hook=update_hook, delete_hook=delete_hook,
140
146
self.opened = False
148
last_checked_ok = isoformat_to_datetime(self.properties
151
if self.properties ["LastCheckerStatus"] != 0:
152
self.using_timer(True)
154
if self.need_approval:
155
self.using_timer(True)
142
157
self.match_objects = (
143
158
self.proxy.connect_to_signal("CheckerCompleted",
144
159
self.checker_completed,
161
176
client_interface,
162
177
byte_arrays=True))
163
self.logger('Created client {}'
164
.format(self.properties["Name"]), level=0)
178
#self.logger('Created client {0}'
179
# .format(self.properties["Name"]))
181
def property_changed(self, property=None, value=None):
182
super(self, MandosClientWidget).property_changed(property,
184
if property == "ApprovalPending":
185
using_timer(bool(value))
186
if property == "LastCheckerStatus":
187
using_timer(value != 0)
188
#self.logger('Checker for client {0} (command "{1}") was '
189
# ' successful'.format(self.properties["Name"],
166
192
def using_timer(self, flag):
167
193
"""Call this method with True or False when timer should be
168
194
activated or deactivated.
170
if flag and self._update_timer_callback_tag is None:
196
old = self._update_timer_callback_lock
198
self._update_timer_callback_lock += 1
200
self._update_timer_callback_lock -= 1
201
if old == 0 and self._update_timer_callback_lock:
171
202
# Will update the shown timer value every second
172
self._update_timer_callback_tag = (GLib.timeout_add
203
self._update_timer_callback_tag = (gobject.timeout_add
174
205
self.update_timer))
175
elif not (flag or self._update_timer_callback_tag is None):
176
GLib.source_remove(self._update_timer_callback_tag)
206
elif old and self._update_timer_callback_lock == 0:
207
gobject.source_remove(self._update_timer_callback_tag)
177
208
self._update_timer_callback_tag = None
179
210
def checker_completed(self, exitstatus, condition, command):
180
211
if exitstatus == 0:
181
self.logger('Checker for client {} (command "{}")'
182
' succeeded'.format(self.properties["Name"],
187
215
if os.WIFEXITED(condition):
188
self.logger('Checker for client {} (command "{}") failed'
216
self.logger('Checker for client {0} (command "{1}")'
217
' failed with exit code {2}'
190
218
.format(self.properties["Name"], command,
191
219
os.WEXITSTATUS(condition)))
192
220
elif os.WIFSIGNALED(condition):
193
self.logger('Checker for client {} (command "{}") was'
194
' killed by signal {}'
221
self.logger('Checker for client {0} (command "{1}") was'
222
' killed by signal {2}'
195
223
.format(self.properties["Name"], command,
196
224
os.WTERMSIG(condition)))
225
elif os.WCOREDUMP(condition):
226
self.logger('Checker for client {0} (command "{1}")'
228
.format(self.properties["Name"], command))
230
self.logger('Checker for client {0} completed'
232
.format(self.properties["Name"]))
199
235
def checker_started(self, command):
200
"""Server signals that a checker started."""
201
self.logger('Client {} started checker "{}"'
202
.format(self.properties["Name"],
236
"""Server signals that a checker started. This could be useful
237
to log in the future. """
238
#self.logger('Client {0} started checker "{1}"'
239
# .format(self.properties["Name"],
205
243
def got_secret(self):
206
self.logger('Client {} received its secret'
244
self.logger('Client {0} received its secret'
207
245
.format(self.properties["Name"]))
209
247
def need_approval(self, timeout, default):
211
message = 'Client {} needs approval within {} seconds'
249
message = 'Client {0} needs approval within {1} seconds'
213
message = 'Client {} will get its secret in {} seconds'
251
message = 'Client {0} will get its secret in {1} seconds'
214
252
self.logger(message.format(self.properties["Name"],
254
self.using_timer(True)
217
256
def rejected(self, reason):
218
self.logger('Client {} was rejected; reason: {}'
257
self.logger('Client {0} was rejected; reason: {1}'
219
258
.format(self.properties["Name"], reason))
221
260
def selectable(self):
259
297
last_approval_request = isoformat_to_datetime(
260
298
self.properties["LastApprovalRequest"])
261
299
if last_approval_request is not None:
262
timer = max(timeout - (datetime.datetime.utcnow()
263
- last_approval_request),
264
datetime.timedelta())
300
timer = timeout - (datetime.datetime.utcnow()
301
- last_approval_request)
266
303
timer = datetime.timedelta()
267
304
if self.properties["ApprovedByDefault"]:
268
message = "Approval in {}. (d)eny?"
305
message = "Approval in {0}. (d)eny?"
270
message = "Denial in {}. (a)pprove?"
271
message = message.format(str(timer).rsplit(".", 1)[0])
272
self.using_timer(True)
307
message = "Denial in {0}. (a)pprove?"
308
message = message.format(unicode(timer).rsplit(".", 1)[0])
273
309
elif self.properties["LastCheckerStatus"] != 0:
274
310
# When checker has failed, show timer until client expires
275
311
expires = self.properties["Expires"]
279
315
expires = (datetime.datetime.strptime
280
316
(expires, '%Y-%m-%dT%H:%M:%S.%f'))
281
timer = max(expires - datetime.datetime.utcnow(),
282
datetime.timedelta())
317
timer = expires - datetime.datetime.utcnow()
283
318
message = ('A checker has failed! Time until client'
285
.format(str(timer).rsplit(".", 1)[0]))
286
self.using_timer(True)
319
' gets disabled: {0}'
320
.format(unicode(timer).rsplit(".", 1)[0]))
288
322
message = "enabled"
289
self.using_timer(False)
290
self._text = "{}{}".format(base, message)
323
self._text = "{0}{1}".format(base, message)
292
325
if not urwid.supports_unicode():
293
326
self._text = self._text.encode("ascii", "replace")
294
327
textlist = [("normal", self._text)]
306
339
self.update_hook()
308
341
def update_timer(self):
309
"""called by GLib. Will indefinitely loop until
310
GLib.source_remove() on tag is called
342
"""called by gobject. Will indefinitely loop until
343
gobject.source_remove() on tag is called"""
313
345
return True # Keep calling this
315
def delete(self, **kwargs):
347
def delete(self, *args, **kwargs):
316
348
if self._update_timer_callback_tag is not None:
317
GLib.source_remove(self._update_timer_callback_tag)
349
gobject.source_remove(self._update_timer_callback_tag)
318
350
self._update_timer_callback_tag = None
319
351
for match in self.match_objects:
321
353
self.match_objects = ()
322
354
if self.delete_hook is not None:
323
355
self.delete_hook(self)
324
return super(MandosClientWidget, self).delete(**kwargs)
356
return super(MandosClientWidget, self).delete(*args, **kwargs)
326
358
def render(self, maxcolrow, focus=False):
327
359
"""Render differently if we have focus.
334
366
This overrides the method from urwid.FlowWidget"""
336
self.proxy.Set(client_interface, "Enabled",
337
dbus.Boolean(True), ignore_reply = True,
338
dbus_interface = dbus.PROPERTIES_IFACE)
368
self.proxy.Enable(dbus_interface = client_interface,
340
self.proxy.Set(client_interface, "Enabled", False,
342
dbus_interface = dbus.PROPERTIES_IFACE)
371
self.proxy.Disable(dbus_interface = client_interface,
344
374
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
345
375
dbus_interface = client_interface,
354
384
ignore_reply=True)
356
self.proxy.Set(client_interface, "CheckerRunning",
357
dbus.Boolean(True), ignore_reply = True,
358
dbus_interface = dbus.PROPERTIES_IFACE)
386
self.proxy.StartChecker(dbus_interface = client_interface,
360
self.proxy.Set(client_interface, "CheckerRunning",
361
dbus.Boolean(False), ignore_reply = True,
362
dbus_interface = dbus.PROPERTIES_IFACE)
389
self.proxy.StopChecker(dbus_interface = client_interface,
364
392
self.proxy.CheckedOK(dbus_interface = client_interface,
365
393
ignore_reply=True)
376
def properties_changed(self, interface, properties, invalidated):
377
"""Call self.update() if any properties changed.
404
def property_changed(self, property=None, value=None,
406
"""Call self.update() if old value is not new value.
378
407
This overrides the method from MandosClientPropertyCache"""
379
old_values = { key: self.properties.get(key)
380
for key in properties.keys() }
381
super(MandosClientWidget, self).properties_changed(
382
interface, properties, invalidated)
383
if any(old_values[key] != self.properties.get(key)
384
for key in old_values):
408
property_name = unicode(property)
409
old_value = self.properties.get(property_name)
410
super(MandosClientWidget, self).property_changed(
411
property=property, value=value, *args, **kwargs)
412
if self.properties.get(property_name) != old_value:
411
439
"default", "default", None),
413
"bold", "default", "bold"),
441
"default", "default", "bold"),
414
442
("underline-blink",
415
"underline,blink", "default", "underline,blink"),
443
"default", "default", "underline"),
417
"standout", "default", "standout"),
445
"default", "default", "standout"),
418
446
("bold-underline-blink",
419
"bold,underline,blink", "default", "bold,underline,blink"),
447
"default", "default", ("bold", "underline")),
420
448
("bold-standout",
421
"bold,standout", "default", "bold,standout"),
449
"default", "default", ("bold", "standout")),
422
450
("underline-blink-standout",
423
"underline,blink,standout", "default",
424
"underline,blink,standout"),
451
"default", "default", ("underline", "standout")),
425
452
("bold-underline-blink-standout",
426
"bold,underline,blink,standout", "default",
427
"bold,underline,blink,standout"),
453
"default", "default", ("bold", "underline",
430
457
if urwid.supports_unicode():
486
511
self.uilist.append(self.logbox)
487
512
self.topwidget = urwid.Pile(self.uilist)
489
def log_message(self, message, level=1):
490
"""Log message formatted with timestamp"""
491
if level < self.log_level:
514
def log_message(self, message):
493
515
timestamp = datetime.datetime.now().isoformat()
494
self.log_message_raw("{}: {}".format(timestamp, message),
516
self.log_message_raw(timestamp + ": " + message)
497
def log_message_raw(self, markup, level=1):
518
def log_message_raw(self, markup):
498
519
"""Add a log message to the log buffer."""
499
if level < self.log_level:
501
520
self.log.append(urwid.Text(markup, wrap=self.log_wrap))
502
521
if (self.max_log_length
503
522
and len(self.log) > self.max_log_length):
522
541
self.log_wrap = "clip"
523
542
for textwidget in self.log:
524
543
textwidget.set_wrap_mode(self.log_wrap)
525
self.log_message("Wrap mode: {}".format(self.log_wrap),
544
#self.log_message("Wrap mode: " + self.log_wrap)
528
def find_and_remove_client(self, path, interfaces):
546
def find_and_remove_client(self, path, name):
529
547
"""Find a client by its object path and remove it.
531
This is connected to the InterfacesRemoved signal from the
549
This is connected to the ClientRemoved signal from the
532
550
Mandos server object."""
533
if client_interface not in interfaces:
534
# Not a Mandos client object; ignore
537
552
client = self.clients_dict[path]
540
self.log_message("Unknown client {!r} removed"
555
self.log_message("Unknown client {0!r} ({1!r}) removed"
545
def add_new_client(self, path, ifs_and_props):
546
"""Find a client by its object path and remove it.
548
This is connected to the InterfacesAdded signal from the
549
Mandos server object.
551
if client_interface not in ifs_and_props:
552
# Not a Mandos client object; ignore
560
def add_new_client(self, path):
554
561
client_proxy_object = self.bus.get_object(self.busname, path)
555
562
self.add_client(MandosClientWidget(server_proxy_object
556
563
=self.mandos_serv,
599
608
mandos_clients = (self.mandos_serv
600
609
.GetAllClientsWithProperties())
601
if not mandos_clients:
602
self.log_message_raw(("bold", "Note: Server has no clients."))
603
610
except dbus.exceptions.DBusException:
604
self.log_message_raw(("bold", "Note: No Mandos server running."))
605
611
mandos_clients = dbus.Dictionary()
607
613
(self.mandos_serv
608
.connect_to_signal("InterfacesRemoved",
614
.connect_to_signal("ClientRemoved",
609
615
self.find_and_remove_client,
611
= dbus.OBJECT_MANAGER_IFACE,
616
dbus_interface=server_interface,
612
617
byte_arrays=True))
613
618
(self.mandos_serv
614
.connect_to_signal("InterfacesAdded",
619
.connect_to_signal("ClientAdded",
615
620
self.add_new_client,
617
= dbus.OBJECT_MANAGER_IFACE,
621
dbus_interface=server_interface,
618
622
byte_arrays=True))
619
623
(self.mandos_serv
620
624
.connect_to_signal("ClientNotFound",
621
625
self.client_not_found,
622
626
dbus_interface=server_interface,
623
627
byte_arrays=True))
624
for path, client in mandos_clients.items():
628
for path, client in mandos_clients.iteritems():
625
629
client_proxy_object = self.bus.get_object(self.busname,
627
631
self.add_client(MandosClientWidget(server_proxy_object