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-2013 Teddy Hogeborn
7
# Copyright © 2009-2013 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
61
domain = 'se.recompile'
61
62
server_interface = domain + '.Mandos'
62
63
client_interface = domain + '.Mandos.Client'
66
dbus.OBJECT_MANAGER_IFACE
67
except AttributeError:
68
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
70
66
def isoformat_to_datetime(iso):
71
67
"Parse an ISO 8601 date string to a datetime.datetime()"
92
88
self.proxy = proxy_object # Mandos Client proxy object
93
89
self.properties = dict() if properties is None else properties
94
90
self.property_changed_match = (
95
self.proxy.connect_to_signal("PropertiesChanged",
96
self.properties_changed,
97
dbus.PROPERTIES_IFACE,
91
self.proxy.connect_to_signal("PropertyChanged",
92
self._property_changed,
100
96
if properties is None:
106
102
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.
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.
112
112
# Update properties dict with new value
113
if interface == client_interface:
114
self.properties.update(properties)
113
self.properties[property] = value
116
115
def delete(self):
117
116
self.property_changed_match.remove()
164
163
client_interface,
165
164
byte_arrays=True))
166
self.logger('Created client {}'
167
.format(self.properties["Name"]), level=0)
165
#self.logger('Created client {0}'
166
# .format(self.properties["Name"]))
169
168
def using_timer(self, flag):
170
169
"""Call this method with True or False when timer should be
182
181
def checker_completed(self, exitstatus, condition, command):
183
182
if exitstatus == 0:
184
self.logger('Checker for client {} (command "{}")'
185
' succeeded'.format(self.properties["Name"],
190
186
if os.WIFEXITED(condition):
191
self.logger('Checker for client {} (command "{}") failed'
187
self.logger('Checker for client {0} (command "{1}")'
188
' failed with exit code {2}'
193
189
.format(self.properties["Name"], command,
194
190
os.WEXITSTATUS(condition)))
195
191
elif os.WIFSIGNALED(condition):
196
self.logger('Checker for client {} (command "{}") was'
197
' killed by signal {}'
192
self.logger('Checker for client {0} (command "{1}") was'
193
' killed by signal {2}'
198
194
.format(self.properties["Name"], command,
199
195
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"]))
202
206
def checker_started(self, command):
203
"""Server signals that a checker started."""
204
self.logger('Client {} started checker "{}"'
205
.format(self.properties["Name"],
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"],
208
214
def got_secret(self):
209
self.logger('Client {} received its secret'
215
self.logger('Client {0} received its secret'
210
216
.format(self.properties["Name"]))
212
218
def need_approval(self, timeout, default):
214
message = 'Client {} needs approval within {} seconds'
220
message = 'Client {0} needs approval within {1} seconds'
216
message = 'Client {} will get its secret in {} seconds'
222
message = 'Client {0} will get its secret in {1} seconds'
217
223
self.logger(message.format(self.properties["Name"],
220
226
def rejected(self, reason):
221
self.logger('Client {} was rejected; reason: {}'
227
self.logger('Client {0} was rejected; reason: {1}'
222
228
.format(self.properties["Name"], reason))
224
230
def selectable(self):
269
275
timer = datetime.timedelta()
270
276
if self.properties["ApprovedByDefault"]:
271
message = "Approval in {}. (d)eny?"
277
message = "Approval in {0}. (d)eny?"
273
message = "Denial in {}. (a)pprove?"
279
message = "Denial in {0}. (a)pprove?"
274
280
message = message.format(str(timer).rsplit(".", 1)[0])
275
281
self.using_timer(True)
276
282
elif self.properties["LastCheckerStatus"] != 0:
284
290
timer = max(expires - datetime.datetime.utcnow(),
285
291
datetime.timedelta())
286
292
message = ('A checker has failed! Time until client'
293
' gets disabled: {0}'
288
294
.format(str(timer).rsplit(".", 1)[0]))
289
295
self.using_timer(True)
291
297
message = "enabled"
292
298
self.using_timer(False)
293
self._text = "{}{}".format(base, message)
299
self._text = "{0}{1}".format(base, message)
295
301
if not urwid.supports_unicode():
296
302
self._text = self._text.encode("ascii", "replace")
336
342
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)
344
self.proxy.Enable(dbus_interface = client_interface,
342
self.proxy.Set(client_interface, "Enabled", False,
344
dbus_interface = dbus.PROPERTIES_IFACE)
347
self.proxy.Disable(dbus_interface = client_interface,
346
350
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
347
351
dbus_interface = client_interface,
356
360
ignore_reply=True)
358
self.proxy.Set(client_interface, "CheckerRunning",
359
dbus.Boolean(True), ignore_reply = True,
360
dbus_interface = dbus.PROPERTIES_IFACE)
362
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)
365
self.proxy.StopChecker(dbus_interface = client_interface,
366
368
self.proxy.CheckedOK(dbus_interface = client_interface,
367
369
ignore_reply=True)
378
def properties_changed(self, interface, properties, invalidated):
379
"""Call self.update() if any properties changed.
380
def property_changed(self, property=None, **kwargs):
381
"""Call self.update() if old value is not new value.
380
382
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):
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:
403
404
"""This is the entire user interface - the whole screen
404
405
with boxes, lists of client widgets, etc.
406
def __init__(self, max_log_length=1000, log_level=1):
407
def __init__(self, max_log_length=1000):
407
408
DBusGMainLoop(set_as_default=True)
409
410
self.screen = urwid.curses_display.Screen()
468
467
self.main_loop = gobject.MainLoop()
470
469
def client_not_found(self, fingerprint, address):
471
self.log_message("Client with address {} and fingerprint {}"
472
" could not be found"
470
self.log_message("Client with address {0} and fingerprint"
471
" {1} could not be found"
473
472
.format(address, fingerprint))
475
474
def rebuild(self):
488
487
self.uilist.append(self.logbox)
489
488
self.topwidget = urwid.Pile(self.uilist)
491
def log_message(self, message, level=1):
490
def log_message(self, message):
492
491
"""Log message formatted with timestamp"""
493
if level < self.log_level:
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
# + str(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,
607
586
mandos_clients = dbus.Dictionary()
609
588
(self.mandos_serv
610
.connect_to_signal("InterfacesRemoved",
589
.connect_to_signal("ClientRemoved",
611
590
self.find_and_remove_client,
613
= dbus.OBJECT_MANAGER_IFACE,
591
dbus_interface=server_interface,
614
592
byte_arrays=True))
615
593
(self.mandos_serv
616
.connect_to_signal("InterfacesAdded",
594
.connect_to_signal("ClientAdded",
617
595
self.add_new_client,
619
= dbus.OBJECT_MANAGER_IFACE,
596
dbus_interface=server_interface,
620
597
byte_arrays=True))
621
598
(self.mandos_serv
622
599
.connect_to_signal("ClientNotFound",