87
91
def __init__(self, proxy_object=None, properties=None, **kwargs):
88
self.proxy = proxy_object # Mandos Client proxy object
92
self.proxy = proxy_object # Mandos Client proxy object
89
93
self.properties = dict() if properties is None else properties
90
94
self.property_changed_match = (
91
self.proxy.connect_to_signal("PropertyChanged",
92
self._property_changed,
95
self.proxy.connect_to_signal("PropertiesChanged",
96
self.properties_changed,
97
dbus.PROPERTIES_IFACE,
96
100
if properties is None:
97
self.properties.update(
98
self.proxy.GetAll(client_interface,
100
= dbus.PROPERTIES_IFACE))
101
self.properties.update(self.proxy.GetAll(
103
dbus_interface=dbus.PROPERTIES_IFACE))
102
105
super(MandosClientPropertyCache, self).__init__(**kwargs)
104
def _property_changed(self, property, value):
105
"""Helper which takes positional arguments"""
106
return self.property_changed(property=property, value=value)
108
def property_changed(self, property=None, value=None):
109
"""This is called whenever we get a PropertyChanged signal
110
It updates the changed property in the "properties" dict.
107
def properties_changed(self, interface, properties, invalidated):
108
"""This is called whenever we get a PropertiesChanged signal
109
It updates the changed properties in the "properties" dict.
112
111
# Update properties dict with new value
113
self.properties[property] = value
112
if interface == client_interface:
113
self.properties.update(properties)
115
115
def delete(self):
116
116
self.property_changed_match.remove()
163
163
client_interface,
164
164
byte_arrays=True))
165
#self.logger('Created client {0}'
166
# .format(self.properties["Name"]))
165
self.logger('Created client {}'
166
.format(self.properties["Name"]), level=0)
168
168
def using_timer(self, flag):
169
169
"""Call this method with True or False when timer should be
170
170
activated or deactivated.
172
172
if flag and self._update_timer_callback_tag is None:
173
173
# Will update the shown timer value every second
174
self._update_timer_callback_tag = (gobject.timeout_add
174
self._update_timer_callback_tag = (GLib.timeout_add
176
176
self.update_timer))
177
177
elif not (flag or self._update_timer_callback_tag is None):
178
gobject.source_remove(self._update_timer_callback_tag)
178
GLib.source_remove(self._update_timer_callback_tag)
179
179
self._update_timer_callback_tag = None
181
181
def checker_completed(self, exitstatus, condition, command):
182
182
if exitstatus == 0:
183
self.logger('Checker for client {} (command "{}")'
184
' succeeded'.format(self.properties["Name"],
186
189
if os.WIFEXITED(condition):
187
self.logger('Checker for client {0} (command "{1}")'
188
' failed with exit code {2}'
190
self.logger('Checker for client {} (command "{}") failed'
189
192
.format(self.properties["Name"], command,
190
193
os.WEXITSTATUS(condition)))
191
194
elif os.WIFSIGNALED(condition):
192
self.logger('Checker for client {0} (command "{1}") was'
193
' killed by signal {2}'
195
self.logger('Checker for client {} (command "{}") was'
196
' killed by signal {}'
194
197
.format(self.properties["Name"], command,
195
198
os.WTERMSIG(condition)))
196
elif os.WCOREDUMP(condition):
197
self.logger('Checker for client {0} (command "{1}")'
199
.format(self.properties["Name"], command))
201
self.logger('Checker for client {0} completed'
203
.format(self.properties["Name"]))
206
201
def checker_started(self, command):
207
"""Server signals that a checker started. This could be useful
208
to log in the future. """
209
#self.logger('Client {0} started checker "{1}"'
210
# .format(self.properties["Name"],
202
"""Server signals that a checker started."""
203
self.logger('Client {} started checker "{}"'
204
.format(self.properties["Name"],
214
207
def got_secret(self):
215
self.logger('Client {0} received its secret'
208
self.logger('Client {} received its secret'
216
209
.format(self.properties["Name"]))
218
211
def need_approval(self, timeout, default):
220
message = 'Client {0} needs approval within {1} seconds'
213
message = 'Client {} needs approval within {} seconds'
222
message = 'Client {0} will get its secret in {1} seconds'
215
message = 'Client {} will get its secret in {} seconds'
223
216
self.logger(message.format(self.properties["Name"],
226
219
def rejected(self, reason):
227
self.logger('Client {0} was rejected; reason: {1}'
220
self.logger('Client {} was rejected; reason: {}'
228
221
.format(self.properties["Name"], reason))
230
223
def selectable(self):
231
224
"""Make this a "selectable" widget.
232
225
This overrides the method from urwid.FlowWidget."""
235
228
def rows(self, maxcolrow, focus=False):
236
229
"""How many rows this widget will occupy might depend on
237
230
whether we have focus or not.
238
231
This overrides the method from urwid.FlowWidget"""
239
232
return self.current_widget(focus).rows(maxcolrow, focus=focus)
241
234
def current_widget(self, focus=False):
242
235
if focus or self.opened:
243
236
return self._focus_widget
244
237
return self._widget
246
239
def update(self):
247
240
"Called when what is visible on the screen should be updated."
248
241
# How to add standout mode to a style
249
with_standout = { "normal": "standout",
250
"bold": "bold-standout",
252
"underline-blink-standout",
253
"bold-underline-blink":
254
"bold-underline-blink-standout",
242
with_standout = {"normal": "standout",
243
"bold": "bold-standout",
245
"underline-blink-standout",
246
"bold-underline-blink":
247
"bold-underline-blink-standout",
257
250
# Rebuild focus and non-focus widgets using current properties
259
252
# Base part of a client. Name!
260
253
base = '{name}: '.format(name=self.properties["Name"])
261
254
if not self.properties["Enabled"]:
262
255
message = "DISABLED"
263
256
self.using_timer(False)
264
257
elif self.properties["ApprovalPending"]:
265
timeout = datetime.timedelta(milliseconds
258
timeout = datetime.timedelta(
259
milliseconds=self.properties["ApprovalDelay"])
268
260
last_approval_request = isoformat_to_datetime(
269
261
self.properties["LastApprovalRequest"])
270
262
if last_approval_request is not None:
330
323
if self.delete_hook is not None:
331
324
self.delete_hook(self)
332
325
return super(MandosClientWidget, self).delete(**kwargs)
334
327
def render(self, maxcolrow, focus=False):
335
328
"""Render differently if we have focus.
336
329
This overrides the method from urwid.FlowWidget"""
337
330
return self.current_widget(focus).render(maxcolrow,
340
333
def keypress(self, maxcolrow, key):
342
335
This overrides the method from urwid.FlowWidget"""
344
self.proxy.Enable(dbus_interface = client_interface,
337
self.proxy.Set(client_interface, "Enabled",
338
dbus.Boolean(True), ignore_reply=True,
339
dbus_interface=dbus.PROPERTIES_IFACE)
347
self.proxy.Disable(dbus_interface = client_interface,
341
self.proxy.Set(client_interface, "Enabled", False,
343
dbus_interface=dbus.PROPERTIES_IFACE)
350
345
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
351
dbus_interface = client_interface,
346
dbus_interface=client_interface,
352
347
ignore_reply=True)
354
349
self.proxy.Approve(dbus.Boolean(False, variant_level=1),
355
dbus_interface = client_interface,
350
dbus_interface=client_interface,
356
351
ignore_reply=True)
357
352
elif key == "R" or key == "_" or key == "ctrl k":
358
353
self.server_proxy_object.RemoveClient(self.proxy
360
355
ignore_reply=True)
362
self.proxy.StartChecker(dbus_interface = client_interface,
357
self.proxy.Set(client_interface, "CheckerRunning",
358
dbus.Boolean(True), ignore_reply=True,
359
dbus_interface=dbus.PROPERTIES_IFACE)
365
self.proxy.StopChecker(dbus_interface = client_interface,
361
self.proxy.Set(client_interface, "CheckerRunning",
362
dbus.Boolean(False), ignore_reply=True,
363
dbus_interface=dbus.PROPERTIES_IFACE)
368
self.proxy.CheckedOK(dbus_interface = client_interface,
365
self.proxy.CheckedOK(dbus_interface=client_interface,
369
366
ignore_reply=True)
371
368
# elif key == "p" or key == "=":
380
def property_changed(self, property=None, **kwargs):
381
"""Call self.update() if old value is not new value.
377
def properties_changed(self, interface, properties, invalidated):
378
"""Call self.update() if any properties changed.
382
379
This overrides the method from MandosClientPropertyCache"""
383
property_name = str(property)
384
old_value = self.properties.get(property_name)
385
super(MandosClientWidget, self).property_changed(
386
property=property, **kwargs)
387
if self.properties.get(property_name) != old_value:
380
old_values = {key: self.properties.get(key)
381
for key in properties.keys()}
382
super(MandosClientWidget, self).properties_changed(
383
interface, properties, invalidated)
384
if any(old_values[key] != self.properties.get(key)
385
for key in old_values):
429
429
"bold,underline,blink,standout", "default",
430
430
"bold,underline,blink,standout"),
433
433
if urwid.supports_unicode():
434
self.divider = "─" # \u2500
435
#self.divider = "━" # \u2501
434
self.divider = "─" # \u2500
437
#self.divider = "-" # \u002d
438
self.divider = "_" # \u005f
436
self.divider = "_" # \u005f
440
438
self.screen.start()
442
440
self.size = self.screen.get_cols_rows()
444
442
self.clients = urwid.SimpleListWalker([])
445
443
self.clients_dict = {}
447
445
# We will add Text widgets to this list
449
447
self.max_log_length = max_log_length
449
self.log_level = log_level
451
451
# We keep a reference to the log widget so we can remove it
452
452
# from the ListWalker without it getting destroyed
453
453
self.logbox = ConstrainedListBox(self.log)
455
455
# This keeps track of whether self.uilist currently has
456
456
# self.logbox in it or not
457
457
self.log_visible = True
458
458
self.log_wrap = "any"
461
461
self.log_message_raw(("bold",
462
462
"Mandos Monitor version " + version))
463
463
self.log_message_raw(("bold",
464
464
"q: Quit ?: Help"))
466
466
self.busname = domain + '.Mandos'
467
self.main_loop = gobject.MainLoop()
467
self.main_loop = GLib.MainLoop()
469
469
def client_not_found(self, fingerprint, address):
470
self.log_message("Client with address {0} and fingerprint"
471
" {1} could not be found"
470
self.log_message("Client with address {} and fingerprint {}"
471
" could not be found"
472
472
.format(address, fingerprint))
474
474
def rebuild(self):
475
475
"""This rebuilds the User Interface.
476
476
Call this when the widget layout needs to change"""
478
#self.uilist.append(urwid.ListBox(self.clients))
478
# self.uilist.append(urwid.ListBox(self.clients))
479
479
self.uilist.append(urwid.Frame(ConstrainedListBox(self.
481
#header=urwid.Divider(),
481
# header=urwid.Divider(),
484
urwid.Divider(div_char=
483
footer=urwid.Divider(
484
div_char=self.divider)))
486
485
if self.log_visible:
487
486
self.uilist.append(self.logbox)
488
487
self.topwidget = urwid.Pile(self.uilist)
490
def log_message(self, message):
489
def log_message(self, message, level=1):
491
490
"""Log message formatted with timestamp"""
491
if level < self.log_level:
492
493
timestamp = datetime.datetime.now().isoformat()
493
self.log_message_raw(timestamp + ": " + message)
495
def log_message_raw(self, markup):
494
self.log_message_raw("{}: {}".format(timestamp, message),
497
def log_message_raw(self, markup, level=1):
496
498
"""Add a log message to the log buffer."""
499
if level < self.log_level:
497
501
self.log.append(urwid.Text(markup, wrap=self.log_wrap))
498
if (self.max_log_length
499
and len(self.log) > self.max_log_length):
500
del self.log[0:len(self.log)-self.max_log_length-1]
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-1]
501
505
self.logbox.set_focus(len(self.logbox.body.contents),
502
506
coming_from="above")
505
509
def toggle_log_display(self):
506
510
"""Toggle visibility of the log buffer."""
507
511
self.log_visible = not self.log_visible
509
#self.log_message("Log visibility changed to: "
510
# + str(self.log_visible))
513
self.log_message("Log visibility changed to: {}"
514
.format(self.log_visible), level=0)
512
516
def change_log_display(self):
513
517
"""Change type of log display.
514
518
Currently, this toggles wrapping of text lines."""
518
522
self.log_wrap = "clip"
519
523
for textwidget in self.log:
520
524
textwidget.set_wrap_mode(self.log_wrap)
521
#self.log_message("Wrap mode: " + self.log_wrap)
523
def find_and_remove_client(self, path, name):
525
self.log_message("Wrap mode: {}".format(self.log_wrap),
528
def find_and_remove_client(self, path, interfaces):
524
529
"""Find a client by its object path and remove it.
526
This is connected to the ClientRemoved signal from the
531
This is connected to the InterfacesRemoved signal from the
527
532
Mandos server object."""
533
if client_interface not in interfaces:
534
# Not a Mandos client object; ignore
529
537
client = self.clients_dict[path]
532
self.log_message("Unknown client {0!r} ({1!r}) removed"
540
self.log_message("Unknown client {!r} removed"
537
def add_new_client(self, path):
545
def add_new_client(self, path, ifs_and_props):
546
"""Find a client by its object path and remove it.
548
This is connected to the InterfacesAdded signal from the
549
Mandos server object.
551
if client_interface not in ifs_and_props:
552
# Not a Mandos client object; ignore
538
554
client_proxy_object = self.bus.get_object(self.busname, path)
539
self.add_client(MandosClientWidget(server_proxy_object
542
=client_proxy_object,
555
self.add_client(MandosClientWidget(
556
server_proxy_object=self.mandos_serv,
557
proxy_object=client_proxy_object,
558
update_hook=self.refresh,
559
delete_hook=self.remove_client,
560
logger=self.log_message,
561
properties=dict(ifs_and_props[client_interface])),
551
564
def add_client(self, client, path=None):
552
565
self.clients.append(client)
555
568
self.clients_dict[path] = client
556
569
self.clients.sort(key=lambda c: c.properties["Name"])
559
572
def remove_client(self, client, path=None):
560
573
self.clients.remove(client)
562
575
path = client.proxy.object_path
563
576
del self.clients_dict[path]
566
579
def refresh(self):
567
580
"""Redraw the screen"""
568
581
canvas = self.topwidget.render(self.size, focus=True)
569
582
self.screen.draw_screen(self.size, canvas)
572
585
"""Start the main loop and exit when it's done."""
573
586
self.bus = dbus.SystemBus()
574
587
mandos_dbus_objc = self.bus.get_object(
575
588
self.busname, "/", follow_name_owner_changes=True)
576
self.mandos_serv = dbus.Interface(mandos_dbus_objc,
589
self.mandos_serv = dbus.Interface(
590
mandos_dbus_objc, dbus_interface=server_interface)
580
592
mandos_clients = (self.mandos_serv
581
593
.GetAllClientsWithProperties())
582
594
if not mandos_clients:
583
self.log_message_raw(("bold", "Note: Server has no clients."))
595
self.log_message_raw(("bold",
596
"Note: Server has no clients."))
584
597
except dbus.exceptions.DBusException:
585
self.log_message_raw(("bold", "Note: No Mandos server running."))
598
self.log_message_raw(("bold",
599
"Note: No Mandos server running."))
586
600
mandos_clients = dbus.Dictionary()
588
602
(self.mandos_serv
589
.connect_to_signal("ClientRemoved",
603
.connect_to_signal("InterfacesRemoved",
590
604
self.find_and_remove_client,
591
dbus_interface=server_interface,
605
dbus_interface=dbus.OBJECT_MANAGER_IFACE,
592
606
byte_arrays=True))
593
607
(self.mandos_serv
594
.connect_to_signal("ClientAdded",
608
.connect_to_signal("InterfacesAdded",
595
609
self.add_new_client,
596
dbus_interface=server_interface,
610
dbus_interface=dbus.OBJECT_MANAGER_IFACE,
597
611
byte_arrays=True))
598
612
(self.mandos_serv
599
613
.connect_to_signal("ClientNotFound",
603
617
for path, client in mandos_clients.items():
604
618
client_proxy_object = self.bus.get_object(self.busname,
606
self.add_client(MandosClientWidget(server_proxy_object
609
=client_proxy_object,
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,
626
logger=self.log_message),
620
self._input_callback_tag = (gobject.io_add_watch
630
self._input_callback_tag = (GLib.io_add_watch
621
631
(sys.stdin.fileno(),
623
633
self.process_input))
624
634
self.main_loop.run()
625
635
# Main loop has finished, we should close everything now
626
gobject.source_remove(self._input_callback_tag)
636
GLib.source_remove(self._input_callback_tag)
627
637
self.screen.stop()
630
640
self.main_loop.quit()
632
642
def process_input(self, source, condition):
633
643
keys = self.screen.get_input()
634
translations = { "ctrl n": "down", # Emacs
635
"ctrl p": "up", # Emacs
636
"ctrl v": "page down", # Emacs
637
"meta v": "page up", # Emacs
638
" ": "page down", # less
639
"f": "page down", # less
640
"b": "page up", # less
644
translations = {"ctrl n": "down", # Emacs
645
"ctrl p": "up", # Emacs
646
"ctrl v": "page down", # Emacs
647
"meta v": "page up", # Emacs
648
" ": "page down", # less
649
"f": "page down", # less
650
"b": "page up", # less
646
656
key = translations[key]
647
657
except KeyError: # :-)
650
660
if key == "q" or key == "Q":
653
663
elif key == "window resize":
654
664
self.size = self.screen.get_cols_rows()
656
elif key == "\f": # Ctrl-L
666
elif key == "ctrl l":
658
669
elif key == "l" or key == "D":
659
670
self.toggle_log_display()
691
704
self.topwidget.set_focus(self.logbox)
693
#elif (key == "end" or key == "meta >" or key == "G"
695
# pass # xxx end-of-buffer
696
#elif (key == "home" or key == "meta <" or key == "g"
698
# pass # xxx beginning-of-buffer
699
#elif key == "ctrl e" or key == "$":
700
# pass # xxx move-end-of-line
701
#elif key == "ctrl a" or key == "^":
702
# pass # xxx move-beginning-of-line
703
#elif key == "ctrl b" or key == "meta (" or key == "h":
705
#elif key == "ctrl f" or key == "meta )" or key == "l":
708
# pass # scroll up log
710
# pass # scroll down log
707
if self.log_level == 0:
709
self.log_message("Verbose mode: Off")
712
self.log_message("Verbose mode: On")
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
711
731
elif self.topwidget.selectable():
712
732
self.topwidget.keypress(self.size, key)
716
737
ui = UserInterface()