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