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