4
4
# Mandos Monitor - Control and monitor the Mandos server
6
# Copyright © 2009-2012 Teddy Hogeborn
7
# Copyright © 2009-2012 Björn Påhlsson
6
# Copyright © 2009-2014 Teddy Hogeborn
7
# Copyright © 2009-2014 Björn Påhlsson
9
9
# This program is free software: you can redistribute it and/or modify
10
10
# it under the terms of the GNU General Public License as published by
55
60
domain = 'se.recompile'
56
61
server_interface = domain + '.Mandos'
57
62
client_interface = domain + '.Mandos.Client'
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)
68
65
def isoformat_to_datetime(iso):
69
66
"Parse an ISO 8601 date string to a datetime.datetime()"
86
83
properties and calls a hook function when any of them are
89
def __init__(self, proxy_object=None, *args, **kwargs):
86
def __init__(self, proxy_object=None, properties=None, **kwargs):
90
87
self.proxy = proxy_object # Mandos Client proxy object
92
self.properties = dict()
88
self.properties = dict() if properties is None else properties
93
89
self.property_changed_match = (
94
90
self.proxy.connect_to_signal("PropertyChanged",
95
self.property_changed,
91
self._property_changed,
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__(
95
if properties is None:
96
self.properties.update(
97
self.proxy.GetAll(client_interface,
99
= dbus.PROPERTIES_IFACE))
101
super(MandosClientPropertyCache, self).__init__(**kwargs)
103
def _property_changed(self, property, value):
104
"""Helper which takes positional arguments"""
105
return self.property_changed(property=property, value=value)
107
107
def property_changed(self, property=None, value=None):
108
108
"""This is called whenever we get a PropertyChanged signal
111
111
# Update properties dict with new value
112
112
self.properties[property] = value
114
def delete(self, *args, **kwargs):
115
115
self.property_changed_match.remove()
116
super(MandosClientPropertyCache, self).__init__(
120
118
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
124
122
def __init__(self, server_proxy_object=None, update_hook=None,
125
delete_hook=None, logger=None, *args, **kwargs):
123
delete_hook=None, logger=None, **kwargs):
126
124
# Called on update
127
125
self.update_hook = update_hook
128
126
# Called on delete
133
131
self.logger = logger
135
133
self._update_timer_callback_tag = None
136
self._update_timer_callback_lock = 0
138
135
# The widget shown normally
139
136
self._text_widget = urwid.Text("")
140
137
# The widget shown when we have focus
141
138
self._focus_text_widget = urwid.Text("")
142
super(MandosClientWidget, self).__init__(
143
update_hook=update_hook, delete_hook=delete_hook,
139
super(MandosClientWidget, self).__init__(**kwargs)
146
141
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)
157
143
self.match_objects = (
158
144
self.proxy.connect_to_signal("CheckerCompleted",
159
145
self.checker_completed,
178
164
#self.logger('Created client {0}'
179
165
# .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"],
192
167
def using_timer(self, flag):
193
168
"""Call this method with True or False when timer should be
194
169
activated or deactivated.
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
if flag and self._update_timer_callback_tag is None:
202
172
# Will update the shown timer value every second
203
173
self._update_timer_callback_tag = (gobject.timeout_add
205
175
self.update_timer))
206
elif old and self._update_timer_callback_lock == 0:
176
elif not (flag or self._update_timer_callback_tag is None):
207
177
gobject.source_remove(self._update_timer_callback_tag)
208
178
self._update_timer_callback_tag = None
283
252
"bold-underline-blink":
284
253
"bold-underline-blink-standout",
287
256
# Rebuild focus and non-focus widgets using current properties
289
258
# Base part of a client. Name!
290
259
base = '{name}: '.format(name=self.properties["Name"])
291
260
if not self.properties["Enabled"]:
292
261
message = "DISABLED"
262
self.using_timer(False)
293
263
elif self.properties["ApprovalPending"]:
294
264
timeout = datetime.timedelta(milliseconds
295
265
= self.properties
297
267
last_approval_request = isoformat_to_datetime(
298
268
self.properties["LastApprovalRequest"])
299
269
if last_approval_request is not None:
300
timer = timeout - (datetime.datetime.utcnow()
301
- last_approval_request)
270
timer = max(timeout - (datetime.datetime.utcnow()
271
- last_approval_request),
272
datetime.timedelta())
303
274
timer = datetime.timedelta()
304
275
if self.properties["ApprovedByDefault"]:
305
276
message = "Approval in {0}. (d)eny?"
307
278
message = "Denial in {0}. (a)pprove?"
308
message = message.format(unicode(timer).rsplit(".", 1)[0])
279
message = message.format(str(timer).rsplit(".", 1)[0])
280
self.using_timer(True)
309
281
elif self.properties["LastCheckerStatus"] != 0:
310
282
# When checker has failed, show timer until client expires
311
283
expires = self.properties["Expires"]
315
287
expires = (datetime.datetime.strptime
316
288
(expires, '%Y-%m-%dT%H:%M:%S.%f'))
317
timer = expires - datetime.datetime.utcnow()
289
timer = max(expires - datetime.datetime.utcnow(),
290
datetime.timedelta())
318
291
message = ('A checker has failed! Time until client'
319
292
' gets disabled: {0}'
320
.format(unicode(timer).rsplit(".", 1)[0]))
293
.format(str(timer).rsplit(".", 1)[0]))
294
self.using_timer(True)
322
296
message = "enabled"
297
self.using_timer(False)
323
298
self._text = "{0}{1}".format(base, message)
325
300
if not urwid.supports_unicode():
326
301
self._text = self._text.encode("ascii", "replace")
327
302
textlist = [("normal", self._text)]
345
320
return True # Keep calling this
347
def delete(self, *args, **kwargs):
322
def delete(self, **kwargs):
348
323
if self._update_timer_callback_tag is not None:
349
324
gobject.source_remove(self._update_timer_callback_tag)
350
325
self._update_timer_callback_tag = None
353
328
self.match_objects = ()
354
329
if self.delete_hook is not None:
355
330
self.delete_hook(self)
356
return super(MandosClientWidget, self).delete(*args, **kwargs)
331
return super(MandosClientWidget, self).delete(**kwargs)
358
333
def render(self, maxcolrow, focus=False):
359
334
"""Render differently if we have focus.
404
def property_changed(self, property=None, value=None,
379
def property_changed(self, property=None, **kwargs):
406
380
"""Call self.update() if old value is not new value.
407
381
This overrides the method from MandosClientPropertyCache"""
408
property_name = unicode(property)
382
property_name = str(property)
409
383
old_value = self.properties.get(property_name)
410
384
super(MandosClientWidget, self).property_changed(
411
property=property, value=value, *args, **kwargs)
385
property=property, **kwargs)
412
386
if self.properties.get(property_name) != old_value:
418
392
"down" key presses, thus not allowing any containing widgets to
419
393
use them as an excuse to shift focus away from this widget.
421
def keypress(self, maxcolrow, key):
422
ret = super(ConstrainedListBox, self).keypress(maxcolrow, key)
395
def keypress(self, *args, **kwargs):
396
ret = super(ConstrainedListBox, self).keypress(*args, **kwargs)
423
397
if ret in ("up", "down"):
439
413
"default", "default", None),
441
"default", "default", "bold"),
415
"bold", "default", "bold"),
442
416
("underline-blink",
443
"default", "default", "underline"),
417
"underline,blink", "default", "underline,blink"),
445
"default", "default", "standout"),
419
"standout", "default", "standout"),
446
420
("bold-underline-blink",
447
"default", "default", ("bold", "underline")),
421
"bold,underline,blink", "default", "bold,underline,blink"),
448
422
("bold-standout",
449
"default", "default", ("bold", "standout")),
423
"bold,standout", "default", "bold,standout"),
450
424
("underline-blink-standout",
451
"default", "default", ("underline", "standout")),
425
"underline,blink,standout", "default",
426
"underline,blink,standout"),
452
427
("bold-underline-blink-standout",
453
"default", "default", ("bold", "underline",
428
"bold,underline,blink,standout", "default",
429
"bold,underline,blink,standout"),
457
432
if urwid.supports_unicode():
608
579
mandos_clients = (self.mandos_serv
609
580
.GetAllClientsWithProperties())
581
if not mandos_clients:
582
self.log_message_raw(("bold", "Note: Server has no clients."))
610
583
except dbus.exceptions.DBusException:
584
self.log_message_raw(("bold", "Note: No Mandos server running."))
611
585
mandos_clients = dbus.Dictionary()
613
587
(self.mandos_serv
625
599
self.client_not_found,
626
600
dbus_interface=server_interface,
627
601
byte_arrays=True))
628
for path, client in mandos_clients.iteritems():
602
for path, client in mandos_clients.items():
629
603
client_proxy_object = self.bus.get_object(self.busname,
631
605
self.add_client(MandosClientWidget(server_proxy_object