4
4
# Mandos Monitor - Control and monitor the Mandos server
6
# Copyright © 2009-2013 Teddy Hogeborn
7
# Copyright © 2009-2013 Björn Påhlsson
6
# Copyright © 2009-2016 Teddy Hogeborn
7
# Copyright © 2009-2016 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
61
60
domain = 'se.recompile'
62
61
server_interface = domain + '.Mandos'
63
62
client_interface = domain + '.Mandos.Client'
66
dbus.OBJECT_MANAGER_IFACE
67
except AttributeError:
68
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
66
70
def isoformat_to_datetime(iso):
67
71
"Parse an ISO 8601 date string to a datetime.datetime()"
88
92
self.proxy = proxy_object # Mandos Client proxy object
89
93
self.properties = dict() if properties is None else properties
90
94
self.property_changed_match = (
91
self.proxy.connect_to_signal("PropertyChanged",
92
self._property_changed,
95
self.proxy.connect_to_signal("PropertiesChanged",
96
self.properties_changed,
97
dbus.PROPERTIES_IFACE,
96
100
if properties is None:
102
106
super(MandosClientPropertyCache, self).__init__(**kwargs)
104
def _property_changed(self, property, value):
105
"""Helper which takes positional arguments"""
106
return self.property_changed(property=property, value=value)
108
def property_changed(self, property=None, value=None):
109
"""This is called whenever we get a PropertyChanged signal
110
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.
112
112
# Update properties dict with new value
113
self.properties[property] = value
113
if interface == client_interface:
114
self.properties.update(properties)
115
116
def delete(self):
116
117
self.property_changed_match.remove()
163
164
client_interface,
164
165
byte_arrays=True))
165
#self.logger('Created client {0}'
166
# .format(self.properties["Name"]))
166
self.logger('Created client {}'
167
.format(self.properties["Name"]), level=0)
168
169
def using_timer(self, flag):
169
170
"""Call this method with True or False when timer should be
181
182
def checker_completed(self, exitstatus, condition, command):
182
183
if exitstatus == 0:
184
self.logger('Checker for client {} (command "{}")'
185
' succeeded'.format(self.properties["Name"],
186
190
if os.WIFEXITED(condition):
187
self.logger('Checker for client {0} (command "{1}")'
188
' failed with exit code {2}'
191
self.logger('Checker for client {} (command "{}") failed'
189
193
.format(self.properties["Name"], command,
190
194
os.WEXITSTATUS(condition)))
191
195
elif os.WIFSIGNALED(condition):
192
self.logger('Checker for client {0} (command "{1}") was'
193
' killed by signal {2}'
196
self.logger('Checker for client {} (command "{}") was'
197
' killed by signal {}'
194
198
.format(self.properties["Name"], command,
195
199
os.WTERMSIG(condition)))
196
elif os.WCOREDUMP(condition):
197
self.logger('Checker for client {0} (command "{1}")'
199
.format(self.properties["Name"], command))
201
self.logger('Checker for client {0} completed'
203
.format(self.properties["Name"]))
206
202
def checker_started(self, command):
207
"""Server signals that a checker started. This could be useful
208
to log in the future. """
209
#self.logger('Client {0} started checker "{1}"'
210
# .format(self.properties["Name"],
203
"""Server signals that a checker started."""
204
self.logger('Client {} started checker "{}"'
205
.format(self.properties["Name"],
214
208
def got_secret(self):
215
self.logger('Client {0} received its secret'
209
self.logger('Client {} received its secret'
216
210
.format(self.properties["Name"]))
218
212
def need_approval(self, timeout, default):
220
message = 'Client {0} needs approval within {1} seconds'
214
message = 'Client {} needs approval within {} seconds'
222
message = 'Client {0} will get its secret in {1} seconds'
216
message = 'Client {} will get its secret in {} seconds'
223
217
self.logger(message.format(self.properties["Name"],
226
220
def rejected(self, reason):
227
self.logger('Client {0} was rejected; reason: {1}'
221
self.logger('Client {} was rejected; reason: {}'
228
222
.format(self.properties["Name"], reason))
230
224
def selectable(self):
275
269
timer = datetime.timedelta()
276
270
if self.properties["ApprovedByDefault"]:
277
message = "Approval in {0}. (d)eny?"
271
message = "Approval in {}. (d)eny?"
279
message = "Denial in {0}. (a)pprove?"
273
message = "Denial in {}. (a)pprove?"
280
274
message = message.format(str(timer).rsplit(".", 1)[0])
281
275
self.using_timer(True)
282
276
elif self.properties["LastCheckerStatus"] != 0:
290
284
timer = max(expires - datetime.datetime.utcnow(),
291
285
datetime.timedelta())
292
286
message = ('A checker has failed! Time until client'
293
' gets disabled: {0}'
294
288
.format(str(timer).rsplit(".", 1)[0]))
295
289
self.using_timer(True)
297
291
message = "enabled"
298
292
self.using_timer(False)
299
self._text = "{0}{1}".format(base, message)
293
self._text = "{}{}".format(base, message)
301
295
if not urwid.supports_unicode():
302
296
self._text = self._text.encode("ascii", "replace")
342
336
This overrides the method from urwid.FlowWidget"""
344
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)
347
self.proxy.Disable(dbus_interface = client_interface,
342
self.proxy.Set(client_interface, "Enabled", False,
344
dbus_interface = dbus.PROPERTIES_IFACE)
350
346
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
351
347
dbus_interface = client_interface,
360
356
ignore_reply=True)
362
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)
365
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)
368
366
self.proxy.CheckedOK(dbus_interface = client_interface,
369
367
ignore_reply=True)
380
def property_changed(self, property=None, **kwargs):
381
"""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.
382
380
This overrides the method from MandosClientPropertyCache"""
383
property_name = str(property)
384
old_value = self.properties.get(property_name)
385
super(MandosClientWidget, self).property_changed(
386
property=property, **kwargs)
387
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):
404
403
"""This is the entire user interface - the whole screen
405
404
with boxes, lists of client widgets, etc.
407
def __init__(self, max_log_length=1000):
406
def __init__(self, max_log_length=1000, log_level=1):
408
407
DBusGMainLoop(set_as_default=True)
410
409
self.screen = urwid.curses_display.Screen()
467
468
self.main_loop = gobject.MainLoop()
469
470
def client_not_found(self, fingerprint, address):
470
self.log_message("Client with address {0} and fingerprint"
471
" {1} could not be found"
471
self.log_message("Client with address {} and fingerprint {}"
472
" could not be found"
472
473
.format(address, fingerprint))
474
475
def rebuild(self):
487
488
self.uilist.append(self.logbox)
488
489
self.topwidget = urwid.Pile(self.uilist)
490
def log_message(self, message):
491
def log_message(self, message, level=1):
491
492
"""Log message formatted with timestamp"""
493
if level < self.log_level:
492
495
timestamp = datetime.datetime.now().isoformat()
493
self.log_message_raw(timestamp + ": " + message)
496
self.log_message_raw("{}: {}".format(timestamp, message),
495
def log_message_raw(self, markup):
499
def log_message_raw(self, markup, level=1):
496
500
"""Add a log message to the log buffer."""
501
if level < self.log_level:
497
503
self.log.append(urwid.Text(markup, wrap=self.log_wrap))
498
504
if (self.max_log_length
499
505
and len(self.log) > self.max_log_length):
506
512
"""Toggle visibility of the log buffer."""
507
513
self.log_visible = not self.log_visible
509
#self.log_message("Log visibility changed to: "
510
# + str(self.log_visible))
515
self.log_message("Log visibility changed to: {}"
516
.format(self.log_visible), level=0)
512
518
def change_log_display(self):
513
519
"""Change type of log display.
518
524
self.log_wrap = "clip"
519
525
for textwidget in self.log:
520
526
textwidget.set_wrap_mode(self.log_wrap)
521
#self.log_message("Wrap mode: " + self.log_wrap)
527
self.log_message("Wrap mode: {}".format(self.log_wrap),
523
def find_and_remove_client(self, path, name):
530
def find_and_remove_client(self, path, interfaces):
524
531
"""Find a client by its object path and remove it.
526
This is connected to the ClientRemoved signal from the
533
This is connected to the InterfacesRemoved signal from the
527
534
Mandos server object."""
535
if client_interface not in interfaces:
536
# Not a Mandos client object; ignore
529
539
client = self.clients_dict[path]
532
self.log_message("Unknown client {0!r} ({1!r}) removed"
542
self.log_message("Unknown client {!r} removed"
537
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
538
556
client_proxy_object = self.bus.get_object(self.busname, path)
539
557
self.add_client(MandosClientWidget(server_proxy_object
540
558
=self.mandos_serv,
586
607
mandos_clients = dbus.Dictionary()
588
609
(self.mandos_serv
589
.connect_to_signal("ClientRemoved",
610
.connect_to_signal("InterfacesRemoved",
590
611
self.find_and_remove_client,
591
dbus_interface=server_interface,
613
= dbus.OBJECT_MANAGER_IFACE,
592
614
byte_arrays=True))
593
615
(self.mandos_serv
594
.connect_to_signal("ClientAdded",
616
.connect_to_signal("InterfacesAdded",
595
617
self.add_new_client,
596
dbus_interface=server_interface,
619
= dbus.OBJECT_MANAGER_IFACE,
597
620
byte_arrays=True))
598
621
(self.mandos_serv
599
622
.connect_to_signal("ClientNotFound",