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,2010 Teddy Hogeborn
7
# Copyright © 2009,2010 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
19
19
# You should have received a copy of the GNU General Public License
20
20
# along with this program. If not, see <http://www.gnu.org/licenses/>.
22
# Contact the authors at <mandos@recompile.se>.
22
# Contact the authors at <mandos@fukt.bsnet.se>.
25
from __future__ import (division, absolute_import, print_function,
25
from __future__ import division, absolute_import, print_function, unicode_literals
49
48
logging.getLogger('dbus.proxies').setLevel(logging.CRITICAL)
51
50
# Some useful constants
52
domain = 'se.recompile'
51
domain = 'se.bsnet.fukt'
53
52
server_interface = domain + '.Mandos'
54
53
client_interface = domain + '.Mandos.Client'
57
56
# Always run in monochrome mode
58
57
urwid.curses_display.curses.has_colors = lambda : False
87
86
self.proxy = proxy_object # Mandos Client proxy object
89
88
self.properties = dict()
90
self.property_changed_match = (
91
self.proxy.connect_to_signal("PropertyChanged",
92
self.property_changed,
89
self.proxy.connect_to_signal("PropertyChanged",
90
self.property_changed,
96
94
self.properties.update(
97
95
self.proxy.GetAll(client_interface,
98
96
dbus_interface = dbus.PROPERTIES_IFACE))
100
#XXX This breaks good super behaviour
98
#XXX This break good super behaviour!
101
99
# super(MandosClientPropertyCache, self).__init__(
102
100
# *args, **kwargs)
108
106
# Update properties dict with new value
109
107
self.properties[property] = value
111
def delete(self, *args, **kwargs):
112
self.property_changed_match.remove()
113
super(MandosClientPropertyCache, self).__init__(
117
110
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
132
125
self._update_timer_callback_tag = None
133
126
self._update_timer_callback_lock = 0
127
self.last_checker_failed = False
135
129
# The widget shown normally
136
130
self._text_widget = urwid.Text("")
145
139
last_checked_ok = isoformat_to_datetime(self.properties
146
140
["LastCheckedOK"])
141
if last_checked_ok is None:
142
self.last_checker_failed = True
144
self.last_checker_failed = ((datetime.datetime.utcnow()
148
if self.properties ["LastCheckerStatus"] != 0:
151
if self.last_checker_failed:
149
152
self.using_timer(True)
151
154
if self.need_approval:
152
155
self.using_timer(True)
154
self.match_objects = (
155
self.proxy.connect_to_signal("CheckerCompleted",
156
self.checker_completed,
159
self.proxy.connect_to_signal("CheckerStarted",
160
self.checker_started,
163
self.proxy.connect_to_signal("GotSecret",
167
self.proxy.connect_to_signal("NeedApproval",
171
self.proxy.connect_to_signal("Rejected",
175
#self.logger('Created client %s' % (self.properties["Name"]))
157
self.proxy.connect_to_signal("CheckerCompleted",
158
self.checker_completed,
161
self.proxy.connect_to_signal("CheckerStarted",
162
self.checker_started,
165
self.proxy.connect_to_signal("GotSecret",
169
self.proxy.connect_to_signal("NeedApproval",
173
self.proxy.connect_to_signal("Rejected",
177
178
def property_changed(self, property=None, value=None):
178
179
super(self, MandosClientWidget).property_changed(property,
180
181
if property == "ApprovalPending":
181
182
using_timer(bool(value))
182
if property == "LastCheckerStatus":
183
using_timer(value != 0)
184
#self.logger('Checker for client %s (command "%s")'
186
# % (self.properties["Name"], command))
188
184
def using_timer(self, flag):
189
185
"""Call this method with True or False when timer should be
190
186
activated or deactivated.
206
201
def checker_completed(self, exitstatus, condition, command):
207
202
if exitstatus == 0:
203
if self.last_checker_failed:
204
self.last_checker_failed = False
205
self.using_timer(False)
206
#self.logger('Checker for client %s (command "%s")'
208
# % (self.properties["Name"], command))
212
if not self.last_checker_failed:
213
self.last_checker_failed = True
214
self.using_timer(True)
211
215
if os.WIFEXITED(condition):
212
216
self.logger('Checker for client %s (command "%s")'
213
217
' failed with exit code %s'
230
234
def checker_started(self, command):
231
"""Server signals that a checker started. This could be useful
232
to log in the future. """
233
235
#self.logger('Client %s started checker "%s"'
234
236
# % (self.properties["Name"], unicode(command)))
237
239
def got_secret(self):
240
self.last_checker_failed = False
238
241
self.logger('Client %s received its secret'
239
242
% self.properties["Name"])
302
305
message = "Denial in %s. (a)pprove?"
303
306
message = message % unicode(timer).rsplit(".", 1)[0]
304
elif self.properties["LastCheckerStatus"] != 0:
305
# When checker has failed, print a timer until client expires
306
expires = self.properties["Expires"]
308
timer = datetime.timedelta(0)
310
expires = datetime.datetime.strptime(expires,
311
'%Y-%m-%dT%H:%M:%S.%f')
312
timer = expires - datetime.datetime.utcnow()
307
elif self.last_checker_failed:
308
timeout = datetime.timedelta(milliseconds
311
last_ok = isoformat_to_datetime(
312
max((self.properties["LastCheckedOK"]
313
or self.properties["Created"]),
314
self.properties["LastEnabled"]))
315
timer = timeout - (datetime.datetime.utcnow() - last_ok)
313
316
message = ('A checker has failed! Time until client'
314
317
' gets disabled: %s'
315
318
% unicode(timer).rsplit(".", 1)[0])
334
337
self.update_hook()
336
339
def update_timer(self):
337
"""called by gobject. Will indefinitely loop until
338
gobject.source_remove() on tag is called"""
340
342
return True # Keep calling this
342
def delete(self, *args, **kwargs):
343
345
if self._update_timer_callback_tag is not None:
344
346
gobject.source_remove(self._update_timer_callback_tag)
345
347
self._update_timer_callback_tag = None
346
for match in self.match_objects:
348
self.match_objects = ()
349
348
if self.delete_hook is not None:
350
349
self.delete_hook(self)
351
return super(MandosClientWidget, self).delete(*args, **kwargs)
353
351
def render(self, maxcolrow, focus=False):
354
352
"""Render differently if we have focus.
361
359
This overrides the method from urwid.FlowWidget"""
363
self.proxy.Enable(dbus_interface = client_interface,
361
self.proxy.Enable(dbus_interface = client_interface)
366
self.proxy.Disable(dbus_interface = client_interface,
363
self.proxy.Disable(dbus_interface = client_interface)
369
365
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
370
dbus_interface = client_interface,
366
dbus_interface = client_interface)
373
368
self.proxy.Approve(dbus.Boolean(False, variant_level=1),
374
dbus_interface = client_interface,
369
dbus_interface = client_interface)
376
370
elif key == "R" or key == "_" or key == "ctrl k":
377
371
self.server_proxy_object.RemoveClient(self.proxy
381
self.proxy.StartChecker(dbus_interface = client_interface,
374
self.proxy.StartChecker(dbus_interface = client_interface)
384
self.proxy.StopChecker(dbus_interface = client_interface,
376
self.proxy.StopChecker(dbus_interface = client_interface)
387
self.proxy.CheckedOK(dbus_interface = client_interface,
378
self.proxy.CheckedOK(dbus_interface = client_interface)
390
380
# elif key == "p" or key == "=":
391
381
# self.proxy.pause()
485
475
self.busname = domain + '.Mandos'
486
476
self.main_loop = gobject.MainLoop()
477
self.bus = dbus.SystemBus()
478
mandos_dbus_objc = self.bus.get_object(
479
self.busname, "/", follow_name_owner_changes=True)
480
self.mandos_serv = dbus.Interface(mandos_dbus_objc,
484
mandos_clients = (self.mandos_serv
485
.GetAllClientsWithProperties())
486
except dbus.exceptions.DBusException:
487
mandos_clients = dbus.Dictionary()
490
.connect_to_signal("ClientRemoved",
491
self.find_and_remove_client,
492
dbus_interface=server_interface,
495
.connect_to_signal("ClientAdded",
497
dbus_interface=server_interface,
500
.connect_to_signal("ClientNotFound",
501
self.client_not_found,
502
dbus_interface=server_interface,
504
for path, client in mandos_clients.iteritems():
505
client_proxy_object = self.bus.get_object(self.busname,
507
self.add_client(MandosClientWidget(server_proxy_object
510
=client_proxy_object,
488
520
def client_not_found(self, fingerprint, address):
489
521
self.log_message(("Client with address %s and fingerprint %s"
539
572
#self.log_message("Wrap mode: " + self.log_wrap)
541
574
def find_and_remove_client(self, path, name):
542
"""Find a client by its object path and remove it.
575
"""Find an client from its object path and remove it.
544
577
This is connected to the ClientRemoved signal from the
545
578
Mandos server object."""
547
580
client = self.clients_dict[path]
550
self.log_message("Unknown client %r (%r) removed", name,
584
self.remove_client(client, path)
555
586
def add_new_client(self, path):
556
587
client_proxy_object = self.bus.get_object(self.busname, path)
595
626
"""Start the main loop and exit when it's done."""
596
self.bus = dbus.SystemBus()
597
mandos_dbus_objc = self.bus.get_object(
598
self.busname, "/", follow_name_owner_changes=True)
599
self.mandos_serv = dbus.Interface(mandos_dbus_objc,
603
mandos_clients = (self.mandos_serv
604
.GetAllClientsWithProperties())
605
except dbus.exceptions.DBusException:
606
mandos_clients = dbus.Dictionary()
609
.connect_to_signal("ClientRemoved",
610
self.find_and_remove_client,
611
dbus_interface=server_interface,
614
.connect_to_signal("ClientAdded",
616
dbus_interface=server_interface,
619
.connect_to_signal("ClientNotFound",
620
self.client_not_found,
621
dbus_interface=server_interface,
623
for path, client in mandos_clients.iteritems():
624
client_proxy_object = self.bus.get_object(self.busname,
626
self.add_client(MandosClientWidget(server_proxy_object
629
=client_proxy_object,
640
628
self._input_callback_tag = (gobject.io_add_watch
641
629
(sys.stdin.fileno(),