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