1
#!/usr/bin/python3 -bbI
2
2
# -*- mode: python; coding: utf-8 -*-
4
4
# Mandos Monitor - Control and monitor the Mandos server
6
# Copyright © 2009-2011 Teddy Hogeborn
7
# Copyright © 2009-2011 Björn Påhlsson
9
# This program is free software: you can redistribute it and/or modify
10
# it under the terms of the GNU General Public License as published by
6
# Copyright © 2009-2019 Teddy Hogeborn
7
# Copyright © 2009-2019 Björn Påhlsson
9
# This file is part of Mandos.
11
# Mandos is free software: you can redistribute it and/or modify it
12
# under the terms of the GNU General Public License as published by
11
13
# the Free Software Foundation, either version 3 of the License, or
12
14
# (at your option) any later version.
14
# This program is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# Mandos is distributed in the hope that it will be useful, but
17
# WITHOUT ANY WARRANTY; without even the implied warranty of
16
18
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
19
# GNU General Public License for more details.
19
21
# You should have received a copy of the GNU General Public License
20
# along with this program. If not, see <http://www.gnu.org/licenses/>.
22
# Contact the authors at <mandos@fukt.bsnet.se>.
22
# along with Mandos. If not, see <http://www.gnu.org/licenses/>.
24
# Contact the authors at <mandos@recompile.se>.
25
26
from __future__ import (division, absolute_import, print_function,
30
from future_builtins import *
34
41
import urwid.curses_display
37
44
from dbus.mainloop.glib import DBusGMainLoop
45
from gi.repository import GLib
46
locale.setlocale(locale.LC_ALL, '')
49
logging.getLogger('dbus.proxies').setLevel(logging.CRITICAL)
49
if sys.version_info.major == 2:
54
# Show warnings by default
55
if not sys.warnoptions:
56
warnings.simplefilter("default")
58
log = logging.getLogger(os.path.basename(sys.argv[0]))
59
logging.basicConfig(level="NOTSET", # Show all messages
60
format="%(message)s") # Show basic log messages
62
logging.captureWarnings(True) # Show warnings via the logging system
64
locale.setlocale(locale.LC_ALL, "")
66
logging.getLogger("dbus.proxies").setLevel(logging.CRITICAL)
67
logging.getLogger("urwid").setLevel(logging.INFO)
51
69
# Some useful constants
52
domain = 'se.bsnet.fukt'
53
server_interface = domain + '.Mandos'
54
client_interface = domain + '.Mandos.Client'
57
# Always run in monochrome mode
58
urwid.curses_display.curses.has_colors = lambda : False
60
# Urwid doesn't support blinking, but we want it. Since we have no
61
# use for underline on its own, we make underline also always blink.
62
urwid.curses_display.curses.A_UNDERLINE |= (
63
urwid.curses_display.curses.A_BLINK)
70
domain = "se.recompile"
71
server_interface = domain + ".Mandos"
72
client_interface = domain + ".Mandos.Client"
76
dbus.OBJECT_MANAGER_IFACE
77
except AttributeError:
78
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
65
81
def isoformat_to_datetime(iso):
66
82
"Parse an ISO 8601 date string to a datetime.datetime()"
78
int(second), # Whole seconds
79
int(fraction*1000000)) # Microseconds
81
class MandosClientPropertyCache(object):
94
int(second), # Whole seconds
95
int(fraction*1000000)) # Microseconds
98
class MandosClientPropertyCache:
82
99
"""This wraps a Mandos Client D-Bus proxy object, caches the
83
100
properties and calls a hook function when any of them are
86
def __init__(self, proxy_object=None, *args, **kwargs):
87
self.proxy = proxy_object # Mandos Client proxy object
89
self.properties = dict()
103
def __init__(self, proxy_object=None, properties=None, **kwargs):
104
self.proxy = proxy_object # Mandos Client proxy object
105
self.properties = dict() if properties is None else properties
90
106
self.property_changed_match = (
91
self.proxy.connect_to_signal("PropertyChanged",
92
self.property_changed,
107
self.proxy.connect_to_signal("PropertiesChanged",
108
self.properties_changed,
109
dbus.PROPERTIES_IFACE,
94
110
byte_arrays=True))
96
self.properties.update(
97
self.proxy.GetAll(client_interface,
98
dbus_interface = dbus.PROPERTIES_IFACE))
100
#XXX This breaks good super behaviour
101
# super(MandosClientPropertyCache, self).__init__(
104
def property_changed(self, property=None, value=None):
105
"""This is called whenever we get a PropertyChanged signal
106
It updates the changed property in the "properties" dict.
112
if properties is None:
113
self.properties.update(self.proxy.GetAll(
115
dbus_interface=dbus.PROPERTIES_IFACE))
117
super(MandosClientPropertyCache, self).__init__(**kwargs)
119
def properties_changed(self, interface, properties, invalidated):
120
"""This is called whenever we get a PropertiesChanged signal
121
It updates the changed properties in the "properties" dict.
108
123
# Update properties dict with new value
109
self.properties[property] = value
111
def delete(self, *args, **kwargs):
124
if interface == client_interface:
125
self.properties.update(properties)
112
128
self.property_changed_match.remove()
113
super(MandosClientPropertyCache, self).__init__(
117
131
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
118
132
"""A Mandos Client which is visible on the screen.
121
135
def __init__(self, server_proxy_object=None, update_hook=None,
122
delete_hook=None, logger=None, *args, **kwargs):
136
delete_hook=None, **kwargs):
123
137
# Called on update
124
138
self.update_hook = update_hook
125
139
# Called on delete
126
140
self.delete_hook = delete_hook
127
141
# Mandos Server proxy object
128
142
self.server_proxy_object = server_proxy_object
132
144
self._update_timer_callback_tag = None
133
self._update_timer_callback_lock = 0
134
self.last_checker_failed = False
136
146
# The widget shown normally
137
147
self._text_widget = urwid.Text("")
138
148
# The widget shown when we have focus
139
149
self._focus_text_widget = urwid.Text("")
140
super(MandosClientWidget, self).__init__(
141
update_hook=update_hook, delete_hook=delete_hook,
150
super(MandosClientWidget, self).__init__(**kwargs)
144
152
self.opened = False
146
last_checked_ok = isoformat_to_datetime(self.properties
148
if last_checked_ok is None:
149
self.last_checker_failed = True
151
self.last_checker_failed = ((datetime.datetime.utcnow()
158
if self.last_checker_failed:
159
self.using_timer(True)
161
if self.need_approval:
162
self.using_timer(True)
164
154
self.match_objects = (
165
155
self.proxy.connect_to_signal("CheckerCompleted",
166
156
self.checker_completed,
183
173
client_interface,
184
174
byte_arrays=True))
185
#self.logger('Created client %s' % (self.properties["Name"]))
187
def property_changed(self, property=None, value=None):
188
super(self, MandosClientWidget).property_changed(property,
190
if property == "ApprovalPending":
191
using_timer(bool(value))
175
log.debug("Created client %s", self.properties["Name"])
193
177
def using_timer(self, flag):
194
178
"""Call this method with True or False when timer should be
195
179
activated or deactivated.
197
old = self._update_timer_callback_lock
199
self._update_timer_callback_lock += 1
201
self._update_timer_callback_lock -= 1
202
if old == 0 and self._update_timer_callback_lock:
203
self._update_timer_callback_tag = (gobject.timeout_add
206
elif old and self._update_timer_callback_lock == 0:
207
gobject.source_remove(self._update_timer_callback_tag)
181
if flag and self._update_timer_callback_tag is None:
182
# Will update the shown timer value every second
183
self._update_timer_callback_tag = (
184
GLib.timeout_add(1000,
185
glib_safely(self.update_timer)))
186
elif not (flag or self._update_timer_callback_tag is None):
187
GLib.source_remove(self._update_timer_callback_tag)
208
188
self._update_timer_callback_tag = None
210
190
def checker_completed(self, exitstatus, condition, command):
211
191
if exitstatus == 0:
212
if self.last_checker_failed:
213
self.last_checker_failed = False
214
self.using_timer(False)
215
#self.logger('Checker for client %s (command "%s")'
217
# % (self.properties["Name"], command))
192
log.debug('Checker for client %s (command "%s")'
193
" succeeded", self.properties["Name"], command)
221
if not self.last_checker_failed:
222
self.last_checker_failed = True
223
self.using_timer(True)
224
197
if os.WIFEXITED(condition):
225
self.logger('Checker for client %s (command "%s")'
226
' failed with exit code %s'
227
% (self.properties["Name"], command,
228
os.WEXITSTATUS(condition)))
198
log.info('Checker for client %s (command "%s") failed'
199
" with exit code %d", self.properties["Name"],
200
command, os.WEXITSTATUS(condition))
229
201
elif os.WIFSIGNALED(condition):
230
self.logger('Checker for client %s (command "%s")'
231
' was killed by signal %s'
232
% (self.properties["Name"], command,
233
os.WTERMSIG(condition)))
234
elif os.WCOREDUMP(condition):
235
self.logger('Checker for client %s (command "%s")'
237
% (self.properties["Name"], command))
239
self.logger('Checker for client %s completed'
202
log.info('Checker for client %s (command "%s") was'
203
" killed by signal %d", self.properties["Name"],
204
command, os.WTERMSIG(condition))
243
207
def checker_started(self, command):
244
#self.logger('Client %s started checker "%s"'
245
# % (self.properties["Name"], unicode(command)))
208
"""Server signals that a checker started."""
209
log.debug('Client %s started checker "%s"',
210
self.properties["Name"], command)
248
212
def got_secret(self):
249
self.last_checker_failed = False
250
self.logger('Client %s received its secret'
251
% self.properties["Name"])
213
log.info("Client %s received its secret",
214
self.properties["Name"])
253
216
def need_approval(self, timeout, default):
255
message = 'Client %s needs approval within %s seconds'
218
message = "Client %s needs approval within %f seconds"
257
message = 'Client %s will get its secret in %s seconds'
259
% (self.properties["Name"], timeout/1000))
260
self.using_timer(True)
220
message = "Client %s will get its secret in %f seconds"
221
log.info(message, self.properties["Name"], timeout/1000)
262
223
def rejected(self, reason):
263
self.logger('Client %s was rejected; reason: %s'
264
% (self.properties["Name"], reason))
224
log.info("Client %s was rejected; reason: %s",
225
self.properties["Name"], reason)
266
227
def selectable(self):
267
228
"""Make this a "selectable" widget.
268
229
This overrides the method from urwid.FlowWidget."""
271
232
def rows(self, maxcolrow, focus=False):
272
233
"""How many rows this widget will occupy might depend on
273
234
whether we have focus or not.
274
235
This overrides the method from urwid.FlowWidget"""
275
236
return self.current_widget(focus).rows(maxcolrow, focus=focus)
277
238
def current_widget(self, focus=False):
278
239
if focus or self.opened:
279
240
return self._focus_widget
280
241
return self._widget
282
243
def update(self):
283
244
"Called when what is visible on the screen should be updated."
284
245
# How to add standout mode to a style
285
with_standout = { "normal": "standout",
286
"bold": "bold-standout",
288
"underline-blink-standout",
289
"bold-underline-blink":
290
"bold-underline-blink-standout",
246
with_standout = {"normal": "standout",
247
"bold": "bold-standout",
249
"underline-blink-standout",
250
"bold-underline-blink":
251
"bold-underline-blink-standout",
293
254
# Rebuild focus and non-focus widgets using current properties
295
256
# Base part of a client. Name!
297
% {"name": self.properties["Name"]})
257
base = "{name}: ".format(name=self.properties["Name"])
298
258
if not self.properties["Enabled"]:
299
259
message = "DISABLED"
260
self.using_timer(False)
300
261
elif self.properties["ApprovalPending"]:
301
timeout = datetime.timedelta(milliseconds
262
timeout = datetime.timedelta(
263
milliseconds=self.properties["ApprovalDelay"])
304
264
last_approval_request = isoformat_to_datetime(
305
265
self.properties["LastApprovalRequest"])
306
266
if last_approval_request is not None:
307
timer = timeout - (datetime.datetime.utcnow()
308
- last_approval_request)
267
timer = max(timeout - (datetime.datetime.utcnow()
268
- last_approval_request),
269
datetime.timedelta())
310
271
timer = datetime.timedelta()
311
272
if self.properties["ApprovedByDefault"]:
312
message = "Approval in %s. (d)eny?"
314
message = "Denial in %s. (a)pprove?"
315
message = message % unicode(timer).rsplit(".", 1)[0]
316
elif self.last_checker_failed:
317
timeout = datetime.timedelta(milliseconds
320
last_ok = isoformat_to_datetime(
321
max((self.properties["LastCheckedOK"]
322
or self.properties["Created"]),
323
self.properties["LastEnabled"]))
324
timer = timeout - (datetime.datetime.utcnow() - last_ok)
325
message = ('A checker has failed! Time until client'
327
% unicode(timer).rsplit(".", 1)[0])
273
message = "Approval in {}. (d)eny?"
275
message = "Denial in {}. (a)pprove?"
276
message = message.format(str(timer).rsplit(".", 1)[0])
277
self.using_timer(True)
278
elif self.properties["LastCheckerStatus"] != 0:
279
# When checker has failed, show timer until client expires
280
expires = self.properties["Expires"]
282
timer = datetime.timedelta(0)
284
expires = (datetime.datetime.strptime
285
(expires, "%Y-%m-%dT%H:%M:%S.%f"))
286
timer = max(expires - datetime.datetime.utcnow(),
287
datetime.timedelta())
288
message = ("A checker has failed! Time until client"
290
.format(str(timer).rsplit(".", 1)[0]))
291
self.using_timer(True)
329
293
message = "enabled"
330
self._text = "%s%s" % (base, message)
294
self.using_timer(False)
295
self._text = "{}{}".format(base, message)
332
297
if not urwid.supports_unicode():
333
298
self._text = self._text.encode("ascii", "replace")
334
299
textlist = [("normal", self._text)]
344
309
# Run update hook, if any
345
310
if self.update_hook is not None:
346
311
self.update_hook()
348
313
def update_timer(self):
314
"""called by GLib. Will indefinitely loop until
315
GLib.source_remove() on tag is called
351
318
return True # Keep calling this
353
def delete(self, *args, **kwargs):
320
def delete(self, **kwargs):
354
321
if self._update_timer_callback_tag is not None:
355
gobject.source_remove(self._update_timer_callback_tag)
322
GLib.source_remove(self._update_timer_callback_tag)
356
323
self._update_timer_callback_tag = None
357
324
for match in self.match_objects:
359
326
self.match_objects = ()
360
327
if self.delete_hook is not None:
361
328
self.delete_hook(self)
362
return super(MandosClientWidget, self).delete(*args, **kwargs)
329
return super(MandosClientWidget, self).delete(**kwargs)
364
331
def render(self, maxcolrow, focus=False):
365
332
"""Render differently if we have focus.
366
333
This overrides the method from urwid.FlowWidget"""
367
334
return self.current_widget(focus).render(maxcolrow,
370
337
def keypress(self, maxcolrow, key):
372
339
This overrides the method from urwid.FlowWidget"""
374
self.proxy.Enable(dbus_interface = client_interface,
341
self.proxy.Set(client_interface, "Enabled",
342
dbus.Boolean(True), ignore_reply=True,
343
dbus_interface=dbus.PROPERTIES_IFACE)
377
self.proxy.Disable(dbus_interface = client_interface,
345
self.proxy.Set(client_interface, "Enabled", False,
347
dbus_interface=dbus.PROPERTIES_IFACE)
380
349
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
381
dbus_interface = client_interface,
350
dbus_interface=client_interface,
382
351
ignore_reply=True)
384
353
self.proxy.Approve(dbus.Boolean(False, variant_level=1),
385
dbus_interface = client_interface,
354
dbus_interface=client_interface,
386
355
ignore_reply=True)
387
356
elif key == "R" or key == "_" or key == "ctrl k":
388
357
self.server_proxy_object.RemoveClient(self.proxy
390
359
ignore_reply=True)
392
self.proxy.StartChecker(dbus_interface = client_interface,
361
self.proxy.Set(client_interface, "CheckerRunning",
362
dbus.Boolean(True), ignore_reply=True,
363
dbus_interface=dbus.PROPERTIES_IFACE)
395
self.proxy.StopChecker(dbus_interface = client_interface,
365
self.proxy.Set(client_interface, "CheckerRunning",
366
dbus.Boolean(False), ignore_reply=True,
367
dbus_interface=dbus.PROPERTIES_IFACE)
398
self.proxy.CheckedOK(dbus_interface = client_interface,
369
self.proxy.CheckedOK(dbus_interface=client_interface,
399
370
ignore_reply=True)
401
372
# elif key == "p" or key == "=":
410
def property_changed(self, property=None, value=None,
412
"""Call self.update() if old value is not new value.
381
def properties_changed(self, interface, properties, invalidated):
382
"""Call self.update() if any properties changed.
413
383
This overrides the method from MandosClientPropertyCache"""
414
property_name = unicode(property)
415
old_value = self.properties.get(property_name)
416
super(MandosClientWidget, self).property_changed(
417
property=property, value=value, *args, **kwargs)
418
if self.properties.get(property_name) != old_value:
384
old_values = {key: self.properties.get(key)
385
for key in properties.keys()}
386
super(MandosClientWidget, self).properties_changed(
387
interface, properties, invalidated)
388
if any(old_values[key] != self.properties.get(key)
389
for key in old_values):
393
def glib_safely(func, retval=True):
394
def safe_func(*args, **kwargs):
396
return func(*args, **kwargs)
422
403
class ConstrainedListBox(urwid.ListBox):
423
404
"""Like a normal urwid.ListBox, but will consume all "up" or
424
405
"down" key presses, thus not allowing any containing widgets to
425
406
use them as an excuse to shift focus away from this widget.
427
def keypress(self, maxcolrow, key):
428
ret = super(ConstrainedListBox, self).keypress(maxcolrow, key)
408
def keypress(self, *args, **kwargs):
409
ret = (super(ConstrainedListBox, self)
410
.keypress(*args, **kwargs))
429
411
if ret in ("up", "down"):
434
class UserInterface(object):
435
417
"""This is the entire user interface - the whole screen
436
418
with boxes, lists of client widgets, etc.
438
420
def __init__(self, max_log_length=1000):
439
421
DBusGMainLoop(set_as_default=True)
441
423
self.screen = urwid.curses_display.Screen()
443
425
self.screen.register_palette((
445
427
"default", "default", None),
447
"default", "default", "bold"),
429
"bold", "default", "bold"),
448
430
("underline-blink",
449
"default", "default", "underline"),
431
"underline,blink", "default", "underline,blink"),
451
"default", "default", "standout"),
433
"standout", "default", "standout"),
452
434
("bold-underline-blink",
453
"default", "default", ("bold", "underline")),
435
"bold,underline,blink", "default",
436
"bold,underline,blink"),
454
437
("bold-standout",
455
"default", "default", ("bold", "standout")),
438
"bold,standout", "default", "bold,standout"),
456
439
("underline-blink-standout",
457
"default", "default", ("underline", "standout")),
440
"underline,blink,standout", "default",
441
"underline,blink,standout"),
458
442
("bold-underline-blink-standout",
459
"default", "default", ("bold", "underline",
443
"bold,underline,blink,standout", "default",
444
"bold,underline,blink,standout"),
463
447
if urwid.supports_unicode():
464
self.divider = "─" # \u2500
465
#self.divider = "━" # \u2501
448
self.divider = "─" # \u2500
467
#self.divider = "-" # \u002d
468
self.divider = "_" # \u005f
450
self.divider = "_" # \u005f
470
452
self.screen.start()
472
454
self.size = self.screen.get_cols_rows()
474
456
self.clients = urwid.SimpleListWalker([])
475
457
self.clients_dict = {}
477
459
# We will add Text widgets to this list
460
self.log = urwid.SimpleListWalker([])
479
461
self.max_log_length = max_log_length
481
463
# We keep a reference to the log widget so we can remove it
482
464
# from the ListWalker without it getting destroyed
483
465
self.logbox = ConstrainedListBox(self.log)
485
467
# This keeps track of whether self.uilist currently has
486
468
# self.logbox in it or not
487
469
self.log_visible = True
488
470
self.log_wrap = "any"
472
self.loghandler = UILogHandler(self)
491
self.log_message_raw(("bold",
492
"Mandos Monitor version " + version))
493
self.log_message_raw(("bold",
496
self.busname = domain + '.Mandos'
497
self.main_loop = gobject.MainLoop()
498
self.bus = dbus.SystemBus()
499
mandos_dbus_objc = self.bus.get_object(
500
self.busname, "/", follow_name_owner_changes=True)
501
self.mandos_serv = dbus.Interface(mandos_dbus_objc,
505
mandos_clients = (self.mandos_serv
506
.GetAllClientsWithProperties())
507
except dbus.exceptions.DBusException:
508
mandos_clients = dbus.Dictionary()
511
.connect_to_signal("ClientRemoved",
512
self.find_and_remove_client,
513
dbus_interface=server_interface,
516
.connect_to_signal("ClientAdded",
518
dbus_interface=server_interface,
521
.connect_to_signal("ClientNotFound",
522
self.client_not_found,
523
dbus_interface=server_interface,
525
for path, client in mandos_clients.iteritems():
526
client_proxy_object = self.bus.get_object(self.busname,
528
self.add_client(MandosClientWidget(server_proxy_object
531
=client_proxy_object,
541
def client_not_found(self, fingerprint, address):
542
self.log_message(("Client with address %s and fingerprint %s"
543
" could not be found" % (address,
475
self.add_log_line(("bold",
476
"Mandos Monitor version " + version))
477
self.add_log_line(("bold", "q: Quit ?: Help"))
479
self.busname = domain + ".Mandos"
480
self.main_loop = GLib.MainLoop()
482
def client_not_found(self, key_id, address):
483
log.info("Client with address %s and key ID %s could"
484
" not be found", address, key_id)
546
486
def rebuild(self):
547
487
"""This rebuilds the User Interface.
548
488
Call this when the widget layout needs to change"""
550
#self.uilist.append(urwid.ListBox(self.clients))
490
# self.uilist.append(urwid.ListBox(self.clients))
551
491
self.uilist.append(urwid.Frame(ConstrainedListBox(self.
553
#header=urwid.Divider(),
493
# header=urwid.Divider(),
556
urwid.Divider(div_char=
495
footer=urwid.Divider(
496
div_char=self.divider)))
558
497
if self.log_visible:
559
498
self.uilist.append(self.logbox)
561
499
self.topwidget = urwid.Pile(self.uilist)
563
def log_message(self, message):
564
timestamp = datetime.datetime.now().isoformat()
565
self.log_message_raw(timestamp + ": " + message)
567
def log_message_raw(self, markup):
568
"""Add a log message to the log buffer."""
501
def add_log_line(self, markup):
569
502
self.log.append(urwid.Text(markup, wrap=self.log_wrap))
570
if (self.max_log_length
571
and len(self.log) > self.max_log_length):
572
del self.log[0:len(self.log)-self.max_log_length-1]
573
self.logbox.set_focus(len(self.logbox.body.contents),
503
if self.max_log_length:
504
if len(self.log) > self.max_log_length:
505
del self.log[0:(len(self.log) - self.max_log_length)]
506
self.logbox.set_focus(len(self.logbox.body.contents)-1,
574
507
coming_from="above")
577
510
def toggle_log_display(self):
578
511
"""Toggle visibility of the log buffer."""
579
512
self.log_visible = not self.log_visible
581
#self.log_message("Log visibility changed to: "
582
# + unicode(self.log_visible))
514
log.debug("Log visibility changed to: %s", self.log_visible)
584
516
def change_log_display(self):
585
517
"""Change type of log display.
586
518
Currently, this toggles wrapping of text lines."""
590
522
self.log_wrap = "clip"
591
523
for textwidget in self.log:
592
524
textwidget.set_wrap_mode(self.log_wrap)
593
#self.log_message("Wrap mode: " + self.log_wrap)
595
def find_and_remove_client(self, path, name):
525
log.debug("Wrap mode: %s", self.log_wrap)
527
def find_and_remove_client(self, path, interfaces):
596
528
"""Find a client by its object path and remove it.
598
This is connected to the ClientRemoved signal from the
530
This is connected to the InterfacesRemoved signal from the
599
531
Mandos server object."""
532
if client_interface not in interfaces:
533
# Not a Mandos client object; ignore
601
536
client = self.clients_dict[path]
604
self.log_message("Unknown client %r (%r) removed", name,
539
log.warning("Unknown client %s removed", path)
609
def add_new_client(self, path):
543
def add_new_client(self, path, ifs_and_props):
544
"""Find a client by its object path and remove it.
546
This is connected to the InterfacesAdded signal from the
547
Mandos server object.
549
if client_interface not in ifs_and_props:
550
# Not a Mandos client object; ignore
610
552
client_proxy_object = self.bus.get_object(self.busname, path)
611
self.add_client(MandosClientWidget(server_proxy_object
614
=client_proxy_object,
553
self.add_client(MandosClientWidget(
554
server_proxy_object=self.mandos_serv,
555
proxy_object=client_proxy_object,
556
update_hook=self.refresh,
557
delete_hook=self.remove_client,
558
properties=dict(ifs_and_props[client_interface])),
623
561
def add_client(self, client, path=None):
624
562
self.clients.append(client)
626
564
path = client.proxy.object_path
627
565
self.clients_dict[path] = client
628
self.clients.sort(None, lambda c: c.properties["Name"])
566
self.clients.sort(key=lambda c: c.properties["Name"])
631
569
def remove_client(self, client, path=None):
632
570
self.clients.remove(client)
634
572
path = client.proxy.object_path
635
573
del self.clients_dict[path]
636
if not self.clients_dict:
637
# Work around bug in Urwid 0.9.8.3 - if a SimpleListWalker
638
# is completely emptied, we need to recreate it.
639
self.clients = urwid.SimpleListWalker([])
643
576
def refresh(self):
644
577
"""Redraw the screen"""
645
578
canvas = self.topwidget.render(self.size, focus=True)
646
579
self.screen.draw_screen(self.size, canvas)
649
582
"""Start the main loop and exit when it's done."""
583
log.addHandler(self.loghandler)
584
self.orig_log_propagate = log.propagate
585
log.propagate = False
586
self.orig_log_level = log.level
588
self.bus = dbus.SystemBus()
589
mandos_dbus_objc = self.bus.get_object(
590
self.busname, "/", follow_name_owner_changes=True)
591
self.mandos_serv = dbus.Interface(
592
mandos_dbus_objc, dbus_interface=server_interface)
594
mandos_clients = (self.mandos_serv
595
.GetAllClientsWithProperties())
596
if not mandos_clients:
597
log.warning("Note: Server has no clients.")
598
except dbus.exceptions.DBusException:
599
log.warning("Note: No Mandos server running.")
600
mandos_clients = dbus.Dictionary()
603
.connect_to_signal("InterfacesRemoved",
604
self.find_and_remove_client,
605
dbus_interface=dbus.OBJECT_MANAGER_IFACE,
608
.connect_to_signal("InterfacesAdded",
610
dbus_interface=dbus.OBJECT_MANAGER_IFACE,
613
.connect_to_signal("ClientNotFound",
614
self.client_not_found,
615
dbus_interface=server_interface,
617
for path, client in mandos_clients.items():
618
client_proxy_object = self.bus.get_object(self.busname,
620
self.add_client(MandosClientWidget(
621
server_proxy_object=self.mandos_serv,
622
proxy_object=client_proxy_object,
624
update_hook=self.refresh,
625
delete_hook=self.remove_client),
651
self._input_callback_tag = (gobject.io_add_watch
629
self._input_callback_tag = (
631
GLib.IOChannel.unix_new(sys.stdin.fileno()),
632
GLib.PRIORITY_DEFAULT, GLib.IO_IN,
633
glib_safely(self.process_input)))
655
634
self.main_loop.run()
656
635
# Main loop has finished, we should close everything now
657
gobject.source_remove(self._input_callback_tag)
636
GLib.source_remove(self._input_callback_tag)
637
with warnings.catch_warnings():
638
warnings.simplefilter("ignore", BytesWarning)
661
642
self.main_loop.quit()
643
log.removeHandler(self.loghandler)
644
log.propagate = self.orig_log_propagate
663
646
def process_input(self, source, condition):
664
647
keys = self.screen.get_input()
665
translations = { "ctrl n": "down", # Emacs
666
"ctrl p": "up", # Emacs
667
"ctrl v": "page down", # Emacs
668
"meta v": "page up", # Emacs
669
" ": "page down", # less
670
"f": "page down", # less
671
"b": "page up", # less
648
translations = {"ctrl n": "down", # Emacs
649
"ctrl p": "up", # Emacs
650
"ctrl v": "page down", # Emacs
651
"meta v": "page up", # Emacs
652
" ": "page down", # less
653
"f": "page down", # less
654
"b": "page up", # less
677
660
key = translations[key]
678
661
except KeyError: # :-)
681
664
if key == "q" or key == "Q":
684
667
elif key == "window resize":
685
668
self.size = self.screen.get_cols_rows()
687
elif key == "\f": # Ctrl-L
670
elif key == "ctrl l":
689
673
elif key == "l" or key == "D":
690
674
self.toggle_log_display()
722
707
self.topwidget.set_focus(self.logbox)
724
#elif (key == "end" or key == "meta >" or key == "G"
726
# pass # xxx end-of-buffer
727
#elif (key == "home" or key == "meta <" or key == "g"
729
# pass # xxx beginning-of-buffer
730
#elif key == "ctrl e" or key == "$":
731
# pass # xxx move-end-of-line
732
#elif key == "ctrl a" or key == "^":
733
# pass # xxx move-beginning-of-line
734
#elif key == "ctrl b" or key == "meta (" or key == "h":
736
#elif key == "ctrl f" or key == "meta )" or key == "l":
739
# pass # scroll up log
741
# pass # scroll down log
710
if log.level < logging.INFO:
711
log.setLevel(logging.INFO)
712
log.info("Verbose mode: Off")
714
log.setLevel(logging.NOTSET)
715
log.info("Verbose mode: On")
716
# elif (key == "end" or key == "meta >" or key == "G"
718
# pass # xxx end-of-buffer
719
# elif (key == "home" or key == "meta <" or key == "g"
721
# pass # xxx beginning-of-buffer
722
# elif key == "ctrl e" or key == "$":
723
# pass # xxx move-end-of-line
724
# elif key == "ctrl a" or key == "^":
725
# pass # xxx move-beginning-of-line
726
# elif key == "ctrl b" or key == "meta (" or key == "h":
728
# elif key == "ctrl f" or key == "meta )" or key == "l":
731
# pass # scroll up log
733
# pass # scroll down log
742
734
elif self.topwidget.selectable():
743
735
self.topwidget.keypress(self.size, key)
740
class UILogHandler(logging.Handler):
741
def __init__(self, ui, *args, **kwargs):
743
super(UILogHandler, self).__init__(*args, **kwargs)
745
logging.Formatter("%(asctime)s: %(message)s"))
746
def emit(self, record):
747
msg = self.format(record)
748
if record.levelno > logging.INFO:
750
self.ui.add_log_line(msg)
747
753
ui = UserInterface()
750
756
except KeyboardInterrupt:
753
ui.log_message(unicode(e))
757
with warnings.catch_warnings():
758
warnings.filterwarnings("ignore", "", BytesWarning)
761
with warnings.catch_warnings():
762
warnings.filterwarnings("ignore", "", BytesWarning)