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