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-2015 Teddy Hogeborn
7
# Copyright © 2009-2015 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)
66
dbus.OBJECT_MANAGER_IFACE
67
except AttributeError:
68
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
68
70
def isoformat_to_datetime(iso):
69
71
"Parse an ISO 8601 date string to a datetime.datetime()"
104
106
super(MandosClientPropertyCache, self).__init__(**kwargs)
106
def _property_changed(self, property, value):
107
"""Helper which takes positional arguments"""
108
return self.property_changed(property=property, value=value)
110
def property_changed(self, property=None, value=None):
111
"""This is called whenever we get a PropertyChanged signal
112
It updates the changed property in the "properties" dict.
108
def properties_changed(self, interface, properties, invalidated):
109
"""This is called whenever we get a PropertiesChanged signal
110
It updates the changed properties in the "properties" dict.
114
112
# Update properties dict with new value
115
self.properties[property] = value
113
if interface == client_interface:
114
self.properties.update(properties)
117
116
def delete(self):
118
117
self.property_changed_match.remove()
165
164
client_interface,
166
165
byte_arrays=True))
167
#self.logger('Created client {0}'
168
# .format(self.properties["Name"]))
166
self.logger('Created client {}'
167
.format(self.properties["Name"]), level=0)
170
169
def using_timer(self, flag):
171
170
"""Call this method with True or False when timer should be
183
182
def checker_completed(self, exitstatus, condition, command):
184
183
if exitstatus == 0:
184
self.logger('Checker for client {} (command "{}")'
185
' succeeded'.format(self.properties["Name"],
188
190
if os.WIFEXITED(condition):
189
self.logger('Checker for client {0} (command "{1}")'
190
' failed with exit code {2}'
191
self.logger('Checker for client {} (command "{}") failed'
191
193
.format(self.properties["Name"], command,
192
194
os.WEXITSTATUS(condition)))
193
195
elif os.WIFSIGNALED(condition):
194
self.logger('Checker for client {0} (command "{1}") was'
195
' killed by signal {2}'
196
self.logger('Checker for client {} (command "{}") was'
197
' killed by signal {}'
196
198
.format(self.properties["Name"], command,
197
199
os.WTERMSIG(condition)))
198
elif os.WCOREDUMP(condition):
199
self.logger('Checker for client {0} (command "{1}")'
201
.format(self.properties["Name"], command))
203
self.logger('Checker for client {0} completed'
205
.format(self.properties["Name"]))
208
202
def checker_started(self, command):
209
"""Server signals that a checker started. This could be useful
210
to log in the future. """
211
#self.logger('Client {0} started checker "{1}"'
212
# .format(self.properties["Name"],
203
"""Server signals that a checker started."""
204
self.logger('Client {} started checker "{}"'
205
.format(self.properties["Name"],
216
208
def got_secret(self):
217
self.logger('Client {0} received its secret'
209
self.logger('Client {} received its secret'
218
210
.format(self.properties["Name"]))
220
212
def need_approval(self, timeout, default):
222
message = 'Client {0} needs approval within {1} seconds'
214
message = 'Client {} needs approval within {} seconds'
224
message = 'Client {0} will get its secret in {1} seconds'
216
message = 'Client {} will get its secret in {} seconds'
225
217
self.logger(message.format(self.properties["Name"],
228
220
def rejected(self, reason):
229
self.logger('Client {0} was rejected; reason: {1}'
221
self.logger('Client {} was rejected; reason: {}'
230
222
.format(self.properties["Name"], reason))
232
224
def selectable(self):
277
269
timer = datetime.timedelta()
278
270
if self.properties["ApprovedByDefault"]:
279
message = "Approval in {0}. (d)eny?"
271
message = "Approval in {}. (d)eny?"
281
message = "Denial in {0}. (a)pprove?"
282
message = message.format(unicode(timer).rsplit(".", 1)[0])
273
message = "Denial in {}. (a)pprove?"
274
message = message.format(str(timer).rsplit(".", 1)[0])
283
275
self.using_timer(True)
284
276
elif self.properties["LastCheckerStatus"] != 0:
285
277
# When checker has failed, show timer until client expires
292
284
timer = max(expires - datetime.datetime.utcnow(),
293
285
datetime.timedelta())
294
286
message = ('A checker has failed! Time until client'
295
' gets disabled: {0}'
296
.format(unicode(timer).rsplit(".", 1)[0]))
288
.format(str(timer).rsplit(".", 1)[0]))
297
289
self.using_timer(True)
299
291
message = "enabled"
300
292
self.using_timer(False)
301
self._text = "{0}{1}".format(base, message)
293
self._text = "{}{}".format(base, message)
303
295
if not urwid.supports_unicode():
304
296
self._text = self._text.encode("ascii", "replace")
344
336
This overrides the method from urwid.FlowWidget"""
346
self.proxy.Enable(dbus_interface = client_interface,
338
self.proxy.Set(client_interface, "Enabled",
339
dbus.Boolean(True), ignore_reply = True,
340
dbus_interface = dbus.PROPERTIES_IFACE)
349
self.proxy.Disable(dbus_interface = client_interface,
342
self.proxy.Set(client_interface, "Enabled", False,
344
dbus_interface = dbus.PROPERTIES_IFACE)
352
346
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
353
347
dbus_interface = client_interface,
362
356
ignore_reply=True)
364
self.proxy.StartChecker(dbus_interface = client_interface,
358
self.proxy.Set(client_interface, "CheckerRunning",
359
dbus.Boolean(True), ignore_reply = True,
360
dbus_interface = dbus.PROPERTIES_IFACE)
367
self.proxy.StopChecker(dbus_interface = client_interface,
362
self.proxy.Set(client_interface, "CheckerRunning",
363
dbus.Boolean(False), ignore_reply = True,
364
dbus_interface = dbus.PROPERTIES_IFACE)
370
366
self.proxy.CheckedOK(dbus_interface = client_interface,
371
367
ignore_reply=True)
382
def property_changed(self, property=None, **kwargs):
383
"""Call self.update() if old value is not new value.
378
def properties_changed(self, interface, properties, invalidated):
379
"""Call self.update() if any properties changed.
384
380
This overrides the method from MandosClientPropertyCache"""
385
property_name = unicode(property)
386
old_value = self.properties.get(property_name)
387
super(MandosClientWidget, self).property_changed(
388
property=property, **kwargs)
389
if self.properties.get(property_name) != old_value:
381
old_values = { key: self.properties.get(key)
382
for key in properties.keys() }
383
super(MandosClientWidget, self).properties_changed(
384
interface, properties, invalidated)
385
if any(old_values[key] != self.properties.get(key)
386
for key in old_values):
406
403
"""This is the entire user interface - the whole screen
407
404
with boxes, lists of client widgets, etc.
409
def __init__(self, max_log_length=1000):
406
def __init__(self, max_log_length=1000, log_level=1):
410
407
DBusGMainLoop(set_as_default=True)
412
409
self.screen = urwid.curses_display.Screen()
416
413
"default", "default", None),
418
"default", "default", "bold"),
415
"bold", "default", "bold"),
419
416
("underline-blink",
420
"default", "default", "underline"),
417
"underline,blink", "default", "underline,blink"),
422
"default", "default", "standout"),
419
"standout", "default", "standout"),
423
420
("bold-underline-blink",
424
"default", "default", ("bold", "underline")),
421
"bold,underline,blink", "default", "bold,underline,blink"),
425
422
("bold-standout",
426
"default", "default", ("bold", "standout")),
423
"bold,standout", "default", "bold,standout"),
427
424
("underline-blink-standout",
428
"default", "default", ("underline", "standout")),
425
"underline,blink,standout", "default",
426
"underline,blink,standout"),
429
427
("bold-underline-blink-standout",
430
"default", "default", ("bold", "underline",
428
"bold,underline,blink,standout", "default",
429
"bold,underline,blink,standout"),
434
432
if urwid.supports_unicode():
468
468
self.main_loop = gobject.MainLoop()
470
470
def client_not_found(self, fingerprint, address):
471
self.log_message("Client with address {0} and fingerprint"
472
" {1} could not be found"
471
self.log_message("Client with address {} and fingerprint {}"
472
" could not be found"
473
473
.format(address, fingerprint))
475
475
def rebuild(self):
488
488
self.uilist.append(self.logbox)
489
489
self.topwidget = urwid.Pile(self.uilist)
491
def log_message(self, message):
491
def log_message(self, message, level=1):
492
492
"""Log message formatted with timestamp"""
493
if level < self.log_level:
493
495
timestamp = datetime.datetime.now().isoformat()
494
self.log_message_raw(timestamp + ": " + message)
496
self.log_message_raw("{}: {}".format(timestamp, message),
496
def log_message_raw(self, markup):
499
def log_message_raw(self, markup, level=1):
497
500
"""Add a log message to the log buffer."""
501
if level < self.log_level:
498
503
self.log.append(urwid.Text(markup, wrap=self.log_wrap))
499
504
if (self.max_log_length
500
505
and len(self.log) > self.max_log_length):
507
512
"""Toggle visibility of the log buffer."""
508
513
self.log_visible = not self.log_visible
510
#self.log_message("Log visibility changed to: "
511
# + unicode(self.log_visible))
515
self.log_message("Log visibility changed to: {}"
516
.format(self.log_visible), level=0)
513
518
def change_log_display(self):
514
519
"""Change type of log display.
519
524
self.log_wrap = "clip"
520
525
for textwidget in self.log:
521
526
textwidget.set_wrap_mode(self.log_wrap)
522
#self.log_message("Wrap mode: " + self.log_wrap)
527
self.log_message("Wrap mode: {}".format(self.log_wrap),
524
def find_and_remove_client(self, path, name):
530
def find_and_remove_client(self, path, interfaces):
525
531
"""Find a client by its object path and remove it.
527
This is connected to the ClientRemoved signal from the
533
This is connected to the InterfacesRemoved signal from the
528
534
Mandos server object."""
535
if client_interface not in interfaces:
536
# Not a Mandos client object; ignore
530
539
client = self.clients_dict[path]
533
self.log_message("Unknown client {0!r} ({1!r}) removed"
542
self.log_message("Unknown client {!r} removed"
538
def add_new_client(self, path):
547
def add_new_client(self, path, ifs_and_props):
548
"""Find a client by its object path and remove it.
550
This is connected to the InterfacesAdded signal from the
551
Mandos server object.
553
if client_interface not in ifs_and_props:
554
# Not a Mandos client object; ignore
539
556
client_proxy_object = self.bus.get_object(self.busname, path)
540
557
self.add_client(MandosClientWidget(server_proxy_object
541
558
=self.mandos_serv,
592
607
mandos_clients = dbus.Dictionary()
594
609
(self.mandos_serv
595
.connect_to_signal("ClientRemoved",
610
.connect_to_signal("InterfacesRemoved",
596
611
self.find_and_remove_client,
597
dbus_interface=server_interface,
613
= dbus.OBJECT_MANAGER_IFACE,
598
614
byte_arrays=True))
599
615
(self.mandos_serv
600
.connect_to_signal("ClientAdded",
616
.connect_to_signal("InterfacesAdded",
601
617
self.add_new_client,
602
dbus_interface=server_interface,
619
= dbus.OBJECT_MANAGER_IFACE,
603
620
byte_arrays=True))
604
621
(self.mandos_serv
605
622
.connect_to_signal("ClientNotFound",
606
623
self.client_not_found,
607
624
dbus_interface=server_interface,
608
625
byte_arrays=True))
609
for path, client in mandos_clients.iteritems():
626
for path, client in mandos_clients.items():
610
627
client_proxy_object = self.bus.get_object(self.busname,
612
629
self.add_client(MandosClientWidget(server_proxy_object