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