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
53
60
domain = 'se.recompile'
54
61
server_interface = domain + '.Mandos'
55
62
client_interface = domain + '.Mandos.Client'
58
# Always run in monochrome mode
59
urwid.curses_display.curses.has_colors = lambda : False
61
# Urwid doesn't support blinking, but we want it. Since we have no
62
# use for underline on its own, we make underline also always blink.
63
urwid.curses_display.curses.A_UNDERLINE |= (
64
urwid.curses_display.curses.A_BLINK)
66
65
def isoformat_to_datetime(iso):
67
66
"Parse an ISO 8601 date string to a datetime.datetime()"
84
83
properties and calls a hook function when any of them are
87
def __init__(self, proxy_object=None, *args, **kwargs):
86
def __init__(self, proxy_object=None, properties=None, **kwargs):
88
87
self.proxy = proxy_object # Mandos Client proxy object
90
self.properties = dict()
88
self.properties = dict() if properties is None else properties
91
89
self.property_changed_match = (
92
90
self.proxy.connect_to_signal("PropertyChanged",
93
self.property_changed,
91
self._property_changed,
97
self.properties.update(
98
self.proxy.GetAll(client_interface,
99
dbus_interface = dbus.PROPERTIES_IFACE))
101
#XXX This breaks good super behaviour
102
# 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)
105
107
def property_changed(self, property=None, value=None):
106
108
"""This is called whenever we get a PropertyChanged signal
109
111
# Update properties dict with new value
110
112
self.properties[property] = value
112
def delete(self, *args, **kwargs):
113
115
self.property_changed_match.remove()
114
super(MandosClientPropertyCache, self).__init__(
118
118
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
122
122
def __init__(self, server_proxy_object=None, update_hook=None,
123
delete_hook=None, logger=None, *args, **kwargs):
123
delete_hook=None, logger=None, **kwargs):
124
124
# Called on update
125
125
self.update_hook = update_hook
126
126
# Called on delete
131
131
self.logger = logger
133
133
self._update_timer_callback_tag = None
134
self._update_timer_callback_lock = 0
136
135
# The widget shown normally
137
136
self._text_widget = urwid.Text("")
138
137
# The widget shown when we have focus
139
138
self._focus_text_widget = urwid.Text("")
140
super(MandosClientWidget, self).__init__(
141
update_hook=update_hook, delete_hook=delete_hook,
139
super(MandosClientWidget, self).__init__(**kwargs)
144
141
self.opened = False
146
last_checked_ok = isoformat_to_datetime(self.properties
149
if self.properties ["LastCheckerStatus"] != 0:
150
self.using_timer(True)
152
if self.need_approval:
153
self.using_timer(True)
155
143
self.match_objects = (
156
144
self.proxy.connect_to_signal("CheckerCompleted",
157
145
self.checker_completed,
174
162
client_interface,
175
163
byte_arrays=True))
176
#self.logger('Created client {0}'
177
# .format(self.properties["Name"]))
179
def property_changed(self, property=None, value=None):
180
super(self, MandosClientWidget).property_changed(property,
182
if property == "ApprovalPending":
183
using_timer(bool(value))
184
if property == "LastCheckerStatus":
185
using_timer(value != 0)
186
#self.logger('Checker for client {0} (command "{1}") was '
187
# ' successful'.format(self.properties["Name"],
164
self.logger('Created client {0}'
165
.format(self.properties["Name"]), level=0)
190
167
def using_timer(self, flag):
191
168
"""Call this method with True or False when timer should be
192
169
activated or deactivated.
194
old = self._update_timer_callback_lock
196
self._update_timer_callback_lock += 1
198
self._update_timer_callback_lock -= 1
199
if old == 0 and self._update_timer_callback_lock:
171
if flag and self._update_timer_callback_tag is None:
200
172
# Will update the shown timer value every second
201
173
self._update_timer_callback_tag = (gobject.timeout_add
203
175
self.update_timer))
204
elif old and self._update_timer_callback_lock == 0:
176
elif not (flag or self._update_timer_callback_tag is None):
205
177
gobject.source_remove(self._update_timer_callback_tag)
206
178
self._update_timer_callback_tag = None
208
180
def checker_completed(self, exitstatus, condition, command):
209
181
if exitstatus == 0:
182
self.logger('Checker for client {0} (command "{1}")'
183
' succeeded'.format(self.properties["Name"],
233
208
def checker_started(self, command):
234
"""Server signals that a checker started. This could be useful
235
to log in the future. """
236
#self.logger('Client {0} started checker "{1}"'
237
# .format(self.properties["Name"],
209
"""Server signals that a checker started."""
210
self.logger('Client {0} started checker "{1}"'
211
.format(self.properties["Name"],
241
214
def got_secret(self):
242
215
self.logger('Client {0} received its secret'
281
253
"bold-underline-blink":
282
254
"bold-underline-blink-standout",
285
257
# Rebuild focus and non-focus widgets using current properties
287
259
# Base part of a client. Name!
288
260
base = '{name}: '.format(name=self.properties["Name"])
289
261
if not self.properties["Enabled"]:
290
262
message = "DISABLED"
263
self.using_timer(False)
291
264
elif self.properties["ApprovalPending"]:
292
265
timeout = datetime.timedelta(milliseconds
293
266
= self.properties
295
268
last_approval_request = isoformat_to_datetime(
296
269
self.properties["LastApprovalRequest"])
297
270
if last_approval_request is not None:
298
timer = timeout - (datetime.datetime.utcnow()
299
- last_approval_request)
271
timer = max(timeout - (datetime.datetime.utcnow()
272
- last_approval_request),
273
datetime.timedelta())
301
275
timer = datetime.timedelta()
302
276
if self.properties["ApprovedByDefault"]:
303
277
message = "Approval in {0}. (d)eny?"
305
279
message = "Denial in {0}. (a)pprove?"
306
message = message.format(unicode(timer).rsplit(".", 1)[0])
280
message = message.format(str(timer).rsplit(".", 1)[0])
281
self.using_timer(True)
307
282
elif self.properties["LastCheckerStatus"] != 0:
308
283
# When checker has failed, show timer until client expires
309
284
expires = self.properties["Expires"]
313
288
expires = (datetime.datetime.strptime
314
289
(expires, '%Y-%m-%dT%H:%M:%S.%f'))
315
timer = expires - datetime.datetime.utcnow()
290
timer = max(expires - datetime.datetime.utcnow(),
291
datetime.timedelta())
316
292
message = ('A checker has failed! Time until client'
317
293
' gets disabled: {0}'
318
.format(unicode(timer).rsplit(".", 1)[0]))
294
.format(str(timer).rsplit(".", 1)[0]))
295
self.using_timer(True)
320
297
message = "enabled"
298
self.using_timer(False)
321
299
self._text = "{0}{1}".format(base, message)
323
301
if not urwid.supports_unicode():
324
302
self._text = self._text.encode("ascii", "replace")
325
303
textlist = [("normal", self._text)]
343
321
return True # Keep calling this
345
def delete(self, *args, **kwargs):
323
def delete(self, **kwargs):
346
324
if self._update_timer_callback_tag is not None:
347
325
gobject.source_remove(self._update_timer_callback_tag)
348
326
self._update_timer_callback_tag = None
351
329
self.match_objects = ()
352
330
if self.delete_hook is not None:
353
331
self.delete_hook(self)
354
return super(MandosClientWidget, self).delete(*args, **kwargs)
332
return super(MandosClientWidget, self).delete(**kwargs)
356
334
def render(self, maxcolrow, focus=False):
357
335
"""Render differently if we have focus.
402
def property_changed(self, property=None, value=None,
380
def property_changed(self, property=None, **kwargs):
404
381
"""Call self.update() if old value is not new value.
405
382
This overrides the method from MandosClientPropertyCache"""
406
property_name = unicode(property)
383
property_name = str(property)
407
384
old_value = self.properties.get(property_name)
408
385
super(MandosClientWidget, self).property_changed(
409
property=property, value=value, *args, **kwargs)
386
property=property, **kwargs)
410
387
if self.properties.get(property_name) != old_value:
416
393
"down" key presses, thus not allowing any containing widgets to
417
394
use them as an excuse to shift focus away from this widget.
419
def keypress(self, maxcolrow, key):
420
ret = super(ConstrainedListBox, self).keypress(maxcolrow, key)
396
def keypress(self, *args, **kwargs):
397
ret = super(ConstrainedListBox, self).keypress(*args, **kwargs)
421
398
if ret in ("up", "down"):
427
404
"""This is the entire user interface - the whole screen
428
405
with boxes, lists of client widgets, etc.
430
def __init__(self, max_log_length=1000):
407
def __init__(self, max_log_length=1000, log_level=1):
431
408
DBusGMainLoop(set_as_default=True)
433
410
self.screen = urwid.curses_display.Screen()
437
414
"default", "default", None),
439
"default", "default", "bold"),
416
"bold", "default", "bold"),
440
417
("underline-blink",
441
"default", "default", "underline"),
418
"underline,blink", "default", "underline,blink"),
443
"default", "default", "standout"),
420
"standout", "default", "standout"),
444
421
("bold-underline-blink",
445
"default", "default", ("bold", "underline")),
422
"bold,underline,blink", "default", "bold,underline,blink"),
446
423
("bold-standout",
447
"default", "default", ("bold", "standout")),
424
"bold,standout", "default", "bold,standout"),
448
425
("underline-blink-standout",
449
"default", "default", ("underline", "standout")),
426
"underline,blink,standout", "default",
427
"underline,blink,standout"),
450
428
("bold-underline-blink-standout",
451
"default", "default", ("bold", "underline",
429
"bold,underline,blink,standout", "default",
430
"bold,underline,blink,standout"),
455
433
if urwid.supports_unicode():
509
489
self.uilist.append(self.logbox)
510
490
self.topwidget = urwid.Pile(self.uilist)
512
def log_message(self, message):
492
def log_message(self, message, level=1):
493
"""Log message formatted with timestamp"""
494
if level < self.log_level:
513
496
timestamp = datetime.datetime.now().isoformat()
514
self.log_message_raw(timestamp + ": " + message)
497
self.log_message_raw("{0}: {1}".format(timestamp, message),
516
def log_message_raw(self, markup):
500
def log_message_raw(self, markup, level=1):
517
501
"""Add a log message to the log buffer."""
502
if level < self.log_level:
518
504
self.log.append(urwid.Text(markup, wrap=self.log_wrap))
519
505
if (self.max_log_length
520
506
and len(self.log) > self.max_log_length):
527
513
"""Toggle visibility of the log buffer."""
528
514
self.log_visible = not self.log_visible
530
#self.log_message("Log visibility changed to: "
531
# + unicode(self.log_visible))
516
self.log_message("Log visibility changed to: {0}"
517
.format(self.log_visible), level=0)
533
519
def change_log_display(self):
534
520
"""Change type of log display.
539
525
self.log_wrap = "clip"
540
526
for textwidget in self.log:
541
527
textwidget.set_wrap_mode(self.log_wrap)
542
#self.log_message("Wrap mode: " + self.log_wrap)
528
self.log_message("Wrap mode: {0}".format(self.log_wrap),
544
531
def find_and_remove_client(self, path, name):
545
532
"""Find a client by its object path and remove it.
606
588
mandos_clients = (self.mandos_serv
607
589
.GetAllClientsWithProperties())
590
if not mandos_clients:
591
self.log_message_raw(("bold", "Note: Server has no clients."))
608
592
except dbus.exceptions.DBusException:
593
self.log_message_raw(("bold", "Note: No Mandos server running."))
609
594
mandos_clients = dbus.Dictionary()
611
596
(self.mandos_serv
623
608
self.client_not_found,
624
609
dbus_interface=server_interface,
625
610
byte_arrays=True))
626
for path, client in mandos_clients.iteritems():
611
for path, client in mandos_clients.items():
627
612
client_proxy_object = self.bus.get_object(self.busname,
629
614
self.add_client(MandosClientWidget(server_proxy_object