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>.
27
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:
52
log = logging.getLogger(os.path.basename(sys.argv[0]))
53
logging.basicConfig(level="NOTSET", # Show all messages
54
format="%(message)s") # Show basic log messages
56
logging.captureWarnings(True) # Show warnings via the logging system
58
locale.setlocale(locale.LC_ALL, "")
60
logging.getLogger("dbus.proxies").setLevel(logging.CRITICAL)
46
locale.setlocale(locale.LC_ALL, '')
49
logging.getLogger('dbus.proxies').setLevel(logging.CRITICAL)
62
51
# Some useful constants
63
domain = "se.recompile"
64
server_interface = domain + ".Mandos"
65
client_interface = domain + ".Mandos.Client"
69
dbus.OBJECT_MANAGER_IFACE
70
except AttributeError:
71
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)
74
65
def isoformat_to_datetime(iso):
75
66
"Parse an ISO 8601 date string to a datetime.datetime()"
87
int(second), # Whole seconds
88
int(fraction*1000000)) # Microseconds
78
int(second), # Whole seconds
79
int(fraction*1000000)) # Microseconds
91
81
class MandosClientPropertyCache(object):
92
82
"""This wraps a Mandos Client D-Bus proxy object, caches the
93
83
properties and calls a hook function when any of them are
96
def __init__(self, proxy_object=None, properties=None, **kwargs):
97
self.proxy = proxy_object # Mandos Client proxy object
98
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()
99
90
self.property_changed_match = (
100
self.proxy.connect_to_signal("PropertiesChanged",
101
self.properties_changed,
102
dbus.PROPERTIES_IFACE,
91
self.proxy.connect_to_signal("PropertyChanged",
92
self.property_changed,
103
94
byte_arrays=True))
105
if properties is None:
106
self.properties.update(self.proxy.GetAll(
108
dbus_interface=dbus.PROPERTIES_IFACE))
110
super(MandosClientPropertyCache, self).__init__(**kwargs)
112
def properties_changed(self, interface, properties, invalidated):
113
"""This is called whenever we get a PropertiesChanged signal
114
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.
116
108
# Update properties dict with new value
117
if interface == client_interface:
118
self.properties.update(properties)
109
self.properties[property] = value
111
def delete(self, *args, **kwargs):
121
112
self.property_changed_match.remove()
113
super(MandosClientPropertyCache, self).__init__(
124
117
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
125
118
"""A Mandos Client which is visible on the screen.
128
121
def __init__(self, server_proxy_object=None, update_hook=None,
129
delete_hook=None, **kwargs):
122
delete_hook=None, logger=None, *args, **kwargs):
130
123
# Called on update
131
124
self.update_hook = update_hook
132
125
# Called on delete
133
126
self.delete_hook = delete_hook
134
127
# Mandos Server proxy object
135
128
self.server_proxy_object = server_proxy_object
137
132
self._update_timer_callback_tag = None
133
self._update_timer_callback_lock = 0
139
135
# The widget shown normally
140
136
self._text_widget = urwid.Text("")
141
137
# The widget shown when we have focus
142
138
self._focus_text_widget = urwid.Text("")
143
super(MandosClientWidget, self).__init__(**kwargs)
139
super(MandosClientWidget, self).__init__(
140
update_hook=update_hook, delete_hook=delete_hook,
145
143
self.opened = False
145
last_checked_ok = isoformat_to_datetime(self.properties
148
if self.properties ["LastCheckerStatus"] != 0:
149
self.using_timer(True)
151
if self.need_approval:
152
self.using_timer(True)
147
154
self.match_objects = (
148
155
self.proxy.connect_to_signal("CheckerCompleted",
149
156
self.checker_completed,
166
173
client_interface,
167
174
byte_arrays=True))
168
log.debug("Created client %s", self.properties["Name"])
175
#self.logger('Created client %s' % (self.properties["Name"]))
177
def property_changed(self, property=None, value=None):
178
super(self, MandosClientWidget).property_changed(property,
180
if property == "ApprovalPending":
181
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))
170
188
def using_timer(self, flag):
171
189
"""Call this method with True or False when timer should be
172
190
activated or deactivated.
174
if flag and self._update_timer_callback_tag is None:
192
old = self._update_timer_callback_lock
194
self._update_timer_callback_lock += 1
196
self._update_timer_callback_lock -= 1
197
if old == 0 and self._update_timer_callback_lock:
175
198
# Will update the shown timer value every second
176
self._update_timer_callback_tag = (
177
GLib.timeout_add(1000,
178
glib_safely(self.update_timer)))
179
elif not (flag or self._update_timer_callback_tag is None):
180
GLib.source_remove(self._update_timer_callback_tag)
199
self._update_timer_callback_tag = (gobject.timeout_add
202
elif old and self._update_timer_callback_lock == 0:
203
gobject.source_remove(self._update_timer_callback_tag)
181
204
self._update_timer_callback_tag = None
183
206
def checker_completed(self, exitstatus, condition, command):
184
207
if exitstatus == 0:
185
log.debug('Checker for client %s (command "%s")'
186
" succeeded", self.properties["Name"], command)
190
211
if os.WIFEXITED(condition):
191
log.info('Checker for client %s (command "%s") failed'
192
" with exit code %d", self.properties["Name"],
193
command, os.WEXITSTATUS(condition))
212
self.logger('Checker for client %s (command "%s")'
213
' failed with exit code %s'
214
% (self.properties["Name"], command,
215
os.WEXITSTATUS(condition)))
194
216
elif os.WIFSIGNALED(condition):
195
log.info('Checker for client %s (command "%s") was'
196
" killed by signal %d", self.properties["Name"],
197
command, os.WTERMSIG(condition))
217
self.logger('Checker for client %s (command "%s")'
218
' was killed by signal %s'
219
% (self.properties["Name"], command,
220
os.WTERMSIG(condition)))
221
elif os.WCOREDUMP(condition):
222
self.logger('Checker for client %s (command "%s")'
224
% (self.properties["Name"], command))
226
self.logger('Checker for client %s completed'
200
230
def checker_started(self, command):
201
"""Server signals that a checker started."""
202
log.debug('Client %s started checker "%s"',
203
self.properties["Name"], command)
231
"""Server signals that a checker started. This could be useful
232
to log in the future. """
233
#self.logger('Client %s started checker "%s"'
234
# % (self.properties["Name"], unicode(command)))
205
237
def got_secret(self):
206
log.info("Client %s received its secret",
207
self.properties["Name"])
238
self.logger('Client %s received its secret'
239
% self.properties["Name"])
209
241
def need_approval(self, timeout, default):
211
message = "Client %s needs approval within %f seconds"
243
message = 'Client %s needs approval within %s seconds'
213
message = "Client %s will get its secret in %f seconds"
214
log.info(message, self.properties["Name"], timeout/1000)
245
message = 'Client %s will get its secret in %s seconds'
247
% (self.properties["Name"], timeout/1000))
248
self.using_timer(True)
216
250
def rejected(self, reason):
217
log.info("Client %s was rejected; reason: %s",
218
self.properties["Name"], reason)
251
self.logger('Client %s was rejected; reason: %s'
252
% (self.properties["Name"], reason))
220
254
def selectable(self):
221
255
"""Make this a "selectable" widget.
222
256
This overrides the method from urwid.FlowWidget."""
225
259
def rows(self, maxcolrow, focus=False):
226
260
"""How many rows this widget will occupy might depend on
227
261
whether we have focus or not.
228
262
This overrides the method from urwid.FlowWidget"""
229
263
return self.current_widget(focus).rows(maxcolrow, focus=focus)
231
265
def current_widget(self, focus=False):
232
266
if focus or self.opened:
233
267
return self._focus_widget
234
268
return self._widget
236
270
def update(self):
237
271
"Called when what is visible on the screen should be updated."
238
272
# How to add standout mode to a style
239
with_standout = {"normal": "standout",
240
"bold": "bold-standout",
242
"underline-blink-standout",
243
"bold-underline-blink":
244
"bold-underline-blink-standout",
273
with_standout = { "normal": "standout",
274
"bold": "bold-standout",
276
"underline-blink-standout",
277
"bold-underline-blink":
278
"bold-underline-blink-standout",
247
281
# Rebuild focus and non-focus widgets using current properties
249
283
# Base part of a client. Name!
250
base = "{name}: ".format(name=self.properties["Name"])
285
% {"name": self.properties["Name"]})
251
286
if not self.properties["Enabled"]:
252
287
message = "DISABLED"
253
self.using_timer(False)
254
288
elif self.properties["ApprovalPending"]:
255
timeout = datetime.timedelta(
256
milliseconds=self.properties["ApprovalDelay"])
289
timeout = datetime.timedelta(milliseconds
257
292
last_approval_request = isoformat_to_datetime(
258
293
self.properties["LastApprovalRequest"])
259
294
if last_approval_request is not None:
260
timer = max(timeout - (datetime.datetime.utcnow()
261
- last_approval_request),
262
datetime.timedelta())
295
timer = timeout - (datetime.datetime.utcnow()
296
- last_approval_request)
264
298
timer = datetime.timedelta()
265
299
if self.properties["ApprovedByDefault"]:
266
message = "Approval in {}. (d)eny?"
300
message = "Approval in %s. (d)eny?"
268
message = "Denial in {}. (a)pprove?"
269
message = message.format(str(timer).rsplit(".", 1)[0])
270
self.using_timer(True)
302
message = "Denial in %s. (a)pprove?"
303
message = message % unicode(timer).rsplit(".", 1)[0]
271
304
elif self.properties["LastCheckerStatus"] != 0:
272
# When checker has failed, show timer until client expires
305
# When checker has failed, print a timer until client expires
273
306
expires = self.properties["Expires"]
274
307
if expires == "":
275
308
timer = datetime.timedelta(0)
277
expires = (datetime.datetime.strptime
278
(expires, "%Y-%m-%dT%H:%M:%S.%f"))
279
timer = max(expires - datetime.datetime.utcnow(),
280
datetime.timedelta())
281
message = ("A checker has failed! Time until client"
283
.format(str(timer).rsplit(".", 1)[0]))
284
self.using_timer(True)
310
expires = datetime.datetime.strptime(expires,
311
'%Y-%m-%dT%H:%M:%S.%f')
312
timer = expires - datetime.datetime.utcnow()
313
message = ('A checker has failed! Time until client'
315
% unicode(timer).rsplit(".", 1)[0])
286
317
message = "enabled"
287
self.using_timer(False)
288
self._text = "{}{}".format(base, message)
318
self._text = "%s%s" % (base, message)
290
320
if not urwid.supports_unicode():
291
321
self._text = self._text.encode("ascii", "replace")
292
322
textlist = [("normal", self._text)]
302
332
# Run update hook, if any
303
333
if self.update_hook is not None:
304
334
self.update_hook()
306
336
def update_timer(self):
307
"""called by GLib. Will indefinitely loop until
308
GLib.source_remove() on tag is called
337
"""called by gobject. Will indefinitely loop until
338
gobject.source_remove() on tag is called"""
311
340
return True # Keep calling this
313
def delete(self, **kwargs):
342
def delete(self, *args, **kwargs):
314
343
if self._update_timer_callback_tag is not None:
315
GLib.source_remove(self._update_timer_callback_tag)
344
gobject.source_remove(self._update_timer_callback_tag)
316
345
self._update_timer_callback_tag = None
317
346
for match in self.match_objects:
319
348
self.match_objects = ()
320
349
if self.delete_hook is not None:
321
350
self.delete_hook(self)
322
return super(MandosClientWidget, self).delete(**kwargs)
351
return super(MandosClientWidget, self).delete(*args, **kwargs)
324
353
def render(self, maxcolrow, focus=False):
325
354
"""Render differently if we have focus.
326
355
This overrides the method from urwid.FlowWidget"""
327
356
return self.current_widget(focus).render(maxcolrow,
330
359
def keypress(self, maxcolrow, key):
332
361
This overrides the method from urwid.FlowWidget"""
334
self.proxy.Set(client_interface, "Enabled",
335
dbus.Boolean(True), ignore_reply=True,
336
dbus_interface=dbus.PROPERTIES_IFACE)
363
self.proxy.Enable(dbus_interface = client_interface,
338
self.proxy.Set(client_interface, "Enabled", False,
340
dbus_interface=dbus.PROPERTIES_IFACE)
366
self.proxy.Disable(dbus_interface = client_interface,
342
369
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
343
dbus_interface=client_interface,
370
dbus_interface = client_interface,
344
371
ignore_reply=True)
346
373
self.proxy.Approve(dbus.Boolean(False, variant_level=1),
347
dbus_interface=client_interface,
374
dbus_interface = client_interface,
348
375
ignore_reply=True)
349
376
elif key == "R" or key == "_" or key == "ctrl k":
350
377
self.server_proxy_object.RemoveClient(self.proxy
352
379
ignore_reply=True)
354
self.proxy.Set(client_interface, "CheckerRunning",
355
dbus.Boolean(True), ignore_reply=True,
356
dbus_interface=dbus.PROPERTIES_IFACE)
381
self.proxy.StartChecker(dbus_interface = client_interface,
358
self.proxy.Set(client_interface, "CheckerRunning",
359
dbus.Boolean(False), ignore_reply=True,
360
dbus_interface=dbus.PROPERTIES_IFACE)
384
self.proxy.StopChecker(dbus_interface = client_interface,
362
self.proxy.CheckedOK(dbus_interface=client_interface,
387
self.proxy.CheckedOK(dbus_interface = client_interface,
363
388
ignore_reply=True)
365
390
# elif key == "p" or key == "=":
374
def properties_changed(self, interface, properties, invalidated):
375
"""Call self.update() if any properties changed.
399
def property_changed(self, property=None, value=None,
401
"""Call self.update() if old value is not new value.
376
402
This overrides the method from MandosClientPropertyCache"""
377
old_values = {key: self.properties.get(key)
378
for key in properties.keys()}
379
super(MandosClientWidget, self).properties_changed(
380
interface, properties, invalidated)
381
if any(old_values[key] != self.properties.get(key)
382
for key in old_values):
403
property_name = unicode(property)
404
old_value = self.properties.get(property_name)
405
super(MandosClientWidget, self).property_changed(
406
property=property, value=value, *args, **kwargs)
407
if self.properties.get(property_name) != old_value:
386
def glib_safely(func, retval=True):
387
def safe_func(*args, **kwargs):
389
return func(*args, **kwargs)
396
411
class ConstrainedListBox(urwid.ListBox):
397
412
"""Like a normal urwid.ListBox, but will consume all "up" or
398
413
"down" key presses, thus not allowing any containing widgets to
399
414
use them as an excuse to shift focus away from this widget.
401
def keypress(self, *args, **kwargs):
402
ret = (super(ConstrainedListBox, self)
403
.keypress(*args, **kwargs))
416
def keypress(self, maxcolrow, key):
417
ret = super(ConstrainedListBox, self).keypress(maxcolrow, key)
404
418
if ret in ("up", "down"):
413
427
def __init__(self, max_log_length=1000):
414
428
DBusGMainLoop(set_as_default=True)
416
430
self.screen = urwid.curses_display.Screen()
418
432
self.screen.register_palette((
420
434
"default", "default", None),
422
"bold", "default", "bold"),
436
"default", "default", "bold"),
423
437
("underline-blink",
424
"underline,blink", "default", "underline,blink"),
438
"default", "default", "underline"),
426
"standout", "default", "standout"),
440
"default", "default", "standout"),
427
441
("bold-underline-blink",
428
"bold,underline,blink", "default",
429
"bold,underline,blink"),
442
"default", "default", ("bold", "underline")),
430
443
("bold-standout",
431
"bold,standout", "default", "bold,standout"),
444
"default", "default", ("bold", "standout")),
432
445
("underline-blink-standout",
433
"underline,blink,standout", "default",
434
"underline,blink,standout"),
446
"default", "default", ("underline", "standout")),
435
447
("bold-underline-blink-standout",
436
"bold,underline,blink,standout", "default",
437
"bold,underline,blink,standout"),
448
"default", "default", ("bold", "underline",
440
452
if urwid.supports_unicode():
441
self.divider = "─" # \u2500
453
self.divider = "─" # \u2500
454
#self.divider = "━" # \u2501
443
self.divider = "_" # \u005f
456
#self.divider = "-" # \u002d
457
self.divider = "_" # \u005f
445
459
self.screen.start()
447
461
self.size = self.screen.get_cols_rows()
449
463
self.clients = urwid.SimpleListWalker([])
450
464
self.clients_dict = {}
452
466
# We will add Text widgets to this list
453
self.log = urwid.SimpleListWalker([])
454
468
self.max_log_length = max_log_length
456
470
# We keep a reference to the log widget so we can remove it
457
471
# from the ListWalker without it getting destroyed
458
472
self.logbox = ConstrainedListBox(self.log)
460
474
# This keeps track of whether self.uilist currently has
461
475
# self.logbox in it or not
462
476
self.log_visible = True
463
477
self.log_wrap = "any"
465
self.loghandler = UILogHandler(self)
468
self.add_log_line(("bold",
469
"Mandos Monitor version " + version))
470
self.add_log_line(("bold", "q: Quit ?: Help"))
472
self.busname = domain + ".Mandos"
473
self.main_loop = GLib.MainLoop()
475
def client_not_found(self, key_id, address):
476
log.info("Client with address %s and key ID %s could"
477
" not be found", address, key_id)
480
self.log_message_raw(("bold",
481
"Mandos Monitor version " + version))
482
self.log_message_raw(("bold",
485
self.busname = domain + '.Mandos'
486
self.main_loop = gobject.MainLoop()
488
def client_not_found(self, fingerprint, address):
489
self.log_message(("Client with address %s and fingerprint %s"
490
" could not be found" % (address,
479
493
def rebuild(self):
480
494
"""This rebuilds the User Interface.
481
495
Call this when the widget layout needs to change"""
483
# self.uilist.append(urwid.ListBox(self.clients))
497
#self.uilist.append(urwid.ListBox(self.clients))
484
498
self.uilist.append(urwid.Frame(ConstrainedListBox(self.
486
# header=urwid.Divider(),
500
#header=urwid.Divider(),
488
footer=urwid.Divider(
489
div_char=self.divider)))
503
urwid.Divider(div_char=
490
505
if self.log_visible:
491
506
self.uilist.append(self.logbox)
492
507
self.topwidget = urwid.Pile(self.uilist)
494
def add_log_line(self, markup):
509
def log_message(self, message):
510
timestamp = datetime.datetime.now().isoformat()
511
self.log_message_raw(timestamp + ": " + message)
513
def log_message_raw(self, markup):
514
"""Add a log message to the log buffer."""
495
515
self.log.append(urwid.Text(markup, wrap=self.log_wrap))
496
if self.max_log_length:
497
if len(self.log) > self.max_log_length:
498
del self.log[0:(len(self.log) - self.max_log_length)]
499
self.logbox.set_focus(len(self.logbox.body.contents)-1,
516
if (self.max_log_length
517
and len(self.log) > self.max_log_length):
518
del self.log[0:len(self.log)-self.max_log_length-1]
519
self.logbox.set_focus(len(self.logbox.body.contents),
500
520
coming_from="above")
503
523
def toggle_log_display(self):
504
524
"""Toggle visibility of the log buffer."""
505
525
self.log_visible = not self.log_visible
507
log.debug("Log visibility changed to: %s", self.log_visible)
527
#self.log_message("Log visibility changed to: "
528
# + unicode(self.log_visible))
509
530
def change_log_display(self):
510
531
"""Change type of log display.
511
532
Currently, this toggles wrapping of text lines."""
515
536
self.log_wrap = "clip"
516
537
for textwidget in self.log:
517
538
textwidget.set_wrap_mode(self.log_wrap)
518
log.debug("Wrap mode: %s", self.log_wrap)
520
def find_and_remove_client(self, path, interfaces):
539
#self.log_message("Wrap mode: " + self.log_wrap)
541
def find_and_remove_client(self, path, name):
521
542
"""Find a client by its object path and remove it.
523
This is connected to the InterfacesRemoved signal from the
544
This is connected to the ClientRemoved signal from the
524
545
Mandos server object."""
525
if client_interface not in interfaces:
526
# Not a Mandos client object; ignore
529
547
client = self.clients_dict[path]
532
log.warning("Unknown client %s removed", path)
550
self.log_message("Unknown client %r (%r) removed", name,
536
def add_new_client(self, path, ifs_and_props):
537
"""Find a client by its object path and remove it.
539
This is connected to the InterfacesAdded signal from the
540
Mandos server object.
542
if client_interface not in ifs_and_props:
543
# Not a Mandos client object; ignore
555
def add_new_client(self, path):
545
556
client_proxy_object = self.bus.get_object(self.busname, path)
546
self.add_client(MandosClientWidget(
547
server_proxy_object=self.mandos_serv,
548
proxy_object=client_proxy_object,
549
update_hook=self.refresh,
550
delete_hook=self.remove_client,
551
properties=dict(ifs_and_props[client_interface])),
557
self.add_client(MandosClientWidget(server_proxy_object
560
=client_proxy_object,
554
569
def add_client(self, client, path=None):
555
570
self.clients.append(client)
557
572
path = client.proxy.object_path
558
573
self.clients_dict[path] = client
559
self.clients.sort(key=lambda c: c.properties["Name"])
574
self.clients.sort(None, lambda c: c.properties["Name"])
562
577
def remove_client(self, client, path=None):
563
578
self.clients.remove(client)
565
580
path = client.proxy.object_path
566
581
del self.clients_dict[path]
582
if not self.clients_dict:
583
# Work around bug in Urwid 0.9.8.3 - if a SimpleListWalker
584
# is completely emptied, we need to recreate it.
585
self.clients = urwid.SimpleListWalker([])
569
589
def refresh(self):
570
590
"""Redraw the screen"""
571
591
canvas = self.topwidget.render(self.size, focus=True)
572
592
self.screen.draw_screen(self.size, canvas)
575
595
"""Start the main loop and exit when it's done."""
576
log.addHandler(self.loghandler)
577
self.orig_log_propagate = log.propagate
578
log.propagate = False
579
self.orig_log_level = log.level
581
596
self.bus = dbus.SystemBus()
582
597
mandos_dbus_objc = self.bus.get_object(
583
598
self.busname, "/", follow_name_owner_changes=True)
584
self.mandos_serv = dbus.Interface(
585
mandos_dbus_objc, dbus_interface=server_interface)
599
self.mandos_serv = dbus.Interface(mandos_dbus_objc,
587
603
mandos_clients = (self.mandos_serv
588
604
.GetAllClientsWithProperties())
589
if not mandos_clients:
590
log.warning("Note: Server has no clients.")
591
605
except dbus.exceptions.DBusException:
592
log.warning("Note: No Mandos server running.")
593
606
mandos_clients = dbus.Dictionary()
595
608
(self.mandos_serv
596
.connect_to_signal("InterfacesRemoved",
609
.connect_to_signal("ClientRemoved",
597
610
self.find_and_remove_client,
598
dbus_interface=dbus.OBJECT_MANAGER_IFACE,
611
dbus_interface=server_interface,
599
612
byte_arrays=True))
600
613
(self.mandos_serv
601
.connect_to_signal("InterfacesAdded",
614
.connect_to_signal("ClientAdded",
602
615
self.add_new_client,
603
dbus_interface=dbus.OBJECT_MANAGER_IFACE,
616
dbus_interface=server_interface,
604
617
byte_arrays=True))
605
618
(self.mandos_serv
606
619
.connect_to_signal("ClientNotFound",
607
620
self.client_not_found,
608
621
dbus_interface=server_interface,
609
622
byte_arrays=True))
610
for path, client in mandos_clients.items():
623
for path, client in mandos_clients.iteritems():
611
624
client_proxy_object = self.bus.get_object(self.busname,
613
self.add_client(MandosClientWidget(
614
server_proxy_object=self.mandos_serv,
615
proxy_object=client_proxy_object,
617
update_hook=self.refresh,
618
delete_hook=self.remove_client),
626
self.add_client(MandosClientWidget(server_proxy_object
629
=client_proxy_object,
622
self._input_callback_tag = (
624
GLib.IOChannel.unix_new(sys.stdin.fileno()),
625
GLib.PRIORITY_DEFAULT, GLib.IO_IN,
626
glib_safely(self.process_input)))
640
self._input_callback_tag = (gobject.io_add_watch
627
644
self.main_loop.run()
628
645
# Main loop has finished, we should close everything now
629
GLib.source_remove(self._input_callback_tag)
630
with warnings.catch_warnings():
631
warnings.simplefilter("ignore", BytesWarning)
646
gobject.source_remove(self._input_callback_tag)
635
650
self.main_loop.quit()
636
log.removeHandler(self.loghandler)
637
log.propagate = self.orig_log_propagate
639
652
def process_input(self, source, condition):
640
653
keys = self.screen.get_input()
641
translations = {"ctrl n": "down", # Emacs
642
"ctrl p": "up", # Emacs
643
"ctrl v": "page down", # Emacs
644
"meta v": "page up", # Emacs
645
" ": "page down", # less
646
"f": "page down", # less
647
"b": "page up", # less
654
translations = { "ctrl n": "down", # Emacs
655
"ctrl p": "up", # Emacs
656
"ctrl v": "page down", # Emacs
657
"meta v": "page up", # Emacs
658
" ": "page down", # less
659
"f": "page down", # less
660
"b": "page up", # less
653
666
key = translations[key]
654
667
except KeyError: # :-)
657
670
if key == "q" or key == "Q":
660
673
elif key == "window resize":
661
674
self.size = self.screen.get_cols_rows()
663
elif key == "ctrl l":
676
elif key == "\f": # Ctrl-L
666
678
elif key == "l" or key == "D":
667
679
self.toggle_log_display()
700
711
self.topwidget.set_focus(self.logbox)
703
if log.level < logging.INFO:
704
log.setLevel(logging.INFO)
705
log.info("Verbose mode: Off")
707
log.setLevel(logging.NOTSET)
708
log.info("Verbose mode: On")
709
# elif (key == "end" or key == "meta >" or key == "G"
711
# pass # xxx end-of-buffer
712
# elif (key == "home" or key == "meta <" or key == "g"
714
# pass # xxx beginning-of-buffer
715
# elif key == "ctrl e" or key == "$":
716
# pass # xxx move-end-of-line
717
# elif key == "ctrl a" or key == "^":
718
# pass # xxx move-beginning-of-line
719
# elif key == "ctrl b" or key == "meta (" or key == "h":
721
# elif key == "ctrl f" or key == "meta )" or key == "l":
724
# pass # scroll up log
726
# pass # scroll down log
713
#elif (key == "end" or key == "meta >" or key == "G"
715
# pass # xxx end-of-buffer
716
#elif (key == "home" or key == "meta <" or key == "g"
718
# pass # xxx beginning-of-buffer
719
#elif key == "ctrl e" or key == "$":
720
# pass # xxx move-end-of-line
721
#elif key == "ctrl a" or key == "^":
722
# pass # xxx move-beginning-of-line
723
#elif key == "ctrl b" or key == "meta (" or key == "h":
725
#elif key == "ctrl f" or key == "meta )" or key == "l":
728
# pass # scroll up log
730
# pass # scroll down log
727
731
elif self.topwidget.selectable():
728
732
self.topwidget.keypress(self.size, key)
733
class UILogHandler(logging.Handler):
734
def __init__(self, ui, *args, **kwargs):
736
super(UILogHandler, self).__init__(*args, **kwargs)
738
logging.Formatter("%(asctime)s: %(message)s"))
739
def emit(self, record):
740
msg = self.format(record)
741
if record.levelno > logging.INFO:
743
self.ui.add_log_line(msg)
746
736
ui = UserInterface()
749
739
except KeyboardInterrupt:
750
with warnings.catch_warnings():
751
warnings.filterwarnings("ignore", "", BytesWarning)
754
with warnings.catch_warnings():
755
warnings.filterwarnings("ignore", "", BytesWarning)
742
ui.log_message(unicode(e))