65
66
"Parse an ISO 8601 date string to a datetime.datetime()"
68
d, t = iso.split(u"T", 1)
69
year, month, day = d.split(u"-", 2)
70
hour, minute, second = t.split(u":", 2)
69
d, t = iso.split("T", 1)
70
year, month, day = d.split("-", 2)
71
hour, minute, second = t.split(":", 2)
71
72
second, fraction = divmod(float(second), 1)
72
73
return datetime.datetime(int(year),
86
87
self.proxy = proxy_object # Mandos Client proxy object
88
89
self.properties = dict()
89
self.proxy.connect_to_signal(u"PropertyChanged",
90
self.property_changed,
90
self.property_changed_match = (
91
self.proxy.connect_to_signal("PropertyChanged",
92
self.property_changed,
94
96
self.properties.update(
95
97
self.proxy.GetAll(client_interface,
96
98
dbus_interface = dbus.PROPERTIES_IFACE))
98
#XXX This break good super behaviour!
100
#XXX This breaks good super behaviour
99
101
# super(MandosClientPropertyCache, self).__init__(
100
102
# *args, **kwargs)
127
134
self.last_checker_failed = False
129
136
# The widget shown normally
130
self._text_widget = urwid.Text(u"")
137
self._text_widget = urwid.Text("")
131
138
# The widget shown when we have focus
132
self._focus_text_widget = urwid.Text(u"")
139
self._focus_text_widget = urwid.Text("")
133
140
super(MandosClientWidget, self).__init__(
134
141
update_hook=update_hook, delete_hook=delete_hook,
154
161
if self.need_approval:
155
162
self.using_timer(True)
157
self.proxy.connect_to_signal(u"CheckerCompleted",
158
self.checker_completed,
161
self.proxy.connect_to_signal(u"CheckerStarted",
162
self.checker_started,
165
self.proxy.connect_to_signal(u"GotSecret",
169
self.proxy.connect_to_signal(u"NeedApproval",
173
self.proxy.connect_to_signal(u"Rejected",
164
self.match_objects = (
165
self.proxy.connect_to_signal("CheckerCompleted",
166
self.checker_completed,
169
self.proxy.connect_to_signal("CheckerStarted",
170
self.checker_started,
173
self.proxy.connect_to_signal("GotSecret",
177
self.proxy.connect_to_signal("NeedApproval",
181
self.proxy.connect_to_signal("Rejected",
185
#self.logger('Created client %s' % (self.properties["Name"]))
178
187
def property_changed(self, property=None, value=None):
179
188
super(self, MandosClientWidget).property_changed(property,
181
if property == u"ApprovalPending":
190
if property == "ApprovalPending":
182
191
using_timer(bool(value))
184
193
def using_timer(self, flag):
213
222
self.last_checker_failed = True
214
223
self.using_timer(True)
215
224
if os.WIFEXITED(condition):
216
self.logger(u'Checker for client %s (command "%s")'
217
u' failed with exit code %s'
218
% (self.properties[u"Name"], command,
225
self.logger('Checker for client %s (command "%s")'
226
' failed with exit code %s'
227
% (self.properties["Name"], command,
219
228
os.WEXITSTATUS(condition)))
220
229
elif os.WIFSIGNALED(condition):
221
self.logger(u'Checker for client %s (command "%s")'
222
u' was killed by signal %s'
223
% (self.properties[u"Name"], command,
230
self.logger('Checker for client %s (command "%s")'
231
' was killed by signal %s'
232
% (self.properties["Name"], command,
224
233
os.WTERMSIG(condition)))
225
234
elif os.WCOREDUMP(condition):
226
self.logger(u'Checker for client %s (command "%s")'
228
% (self.properties[u"Name"], command))
235
self.logger('Checker for client %s (command "%s")'
237
% (self.properties["Name"], command))
230
self.logger(u'Checker for client %s completed'
239
self.logger('Checker for client %s completed'
234
243
def checker_started(self, command):
235
#self.logger(u'Client %s started checker "%s"'
236
# % (self.properties[u"Name"], unicode(command)))
244
#self.logger('Client %s started checker "%s"'
245
# % (self.properties["Name"], unicode(command)))
239
248
def got_secret(self):
240
249
self.last_checker_failed = False
241
self.logger(u'Client %s received its secret'
242
% self.properties[u"Name"])
250
self.logger('Client %s received its secret'
251
% self.properties["Name"])
244
253
def need_approval(self, timeout, default):
246
message = u'Client %s needs approval within %s seconds'
255
message = 'Client %s needs approval within %s seconds'
248
message = u'Client %s will get its secret in %s seconds'
257
message = 'Client %s will get its secret in %s seconds'
249
258
self.logger(message
250
% (self.properties[u"Name"], timeout/1000))
259
% (self.properties["Name"], timeout/1000))
251
260
self.using_timer(True)
253
262
def rejected(self, reason):
254
self.logger(u'Client %s was rejected; reason: %s'
255
% (self.properties[u"Name"], reason))
263
self.logger('Client %s was rejected; reason: %s'
264
% (self.properties["Name"], reason))
257
266
def selectable(self):
258
267
"""Make this a "selectable" widget.
259
268
This overrides the method from urwid.FlowWidget."""
262
def rows(self, (maxcol,), focus=False):
271
def rows(self, maxcolrow, focus=False):
263
272
"""How many rows this widget will occupy might depend on
264
273
whether we have focus or not.
265
274
This overrides the method from urwid.FlowWidget"""
266
return self.current_widget(focus).rows((maxcol,), focus=focus)
275
return self.current_widget(focus).rows(maxcolrow, focus=focus)
268
277
def current_widget(self, focus=False):
269
278
if focus or self.opened:
273
282
def update(self):
274
283
"Called when what is visible on the screen should be updated."
275
284
# How to add standout mode to a style
276
with_standout = { u"normal": u"standout",
277
u"bold": u"bold-standout",
279
u"underline-blink-standout",
280
u"bold-underline-blink":
281
u"bold-underline-blink-standout",
285
with_standout = { "normal": "standout",
286
"bold": "bold-standout",
288
"underline-blink-standout",
289
"bold-underline-blink":
290
"bold-underline-blink-standout",
284
293
# Rebuild focus and non-focus widgets using current properties
286
295
# Base part of a client. Name!
287
base = (u'%(name)s: '
288
% {u"name": self.properties[u"Name"]})
289
if not self.properties[u"Enabled"]:
290
message = u"DISABLED"
291
elif self.properties[u"ApprovalPending"]:
297
% {"name": self.properties["Name"]})
298
if not self.properties["Enabled"]:
300
elif self.properties["ApprovalPending"]:
292
301
timeout = datetime.timedelta(milliseconds
293
302
= self.properties
295
304
last_approval_request = isoformat_to_datetime(
296
self.properties[u"LastApprovalRequest"])
305
self.properties["LastApprovalRequest"])
297
306
if last_approval_request is not None:
298
307
timer = timeout - (datetime.datetime.utcnow()
299
308
- last_approval_request)
301
310
timer = datetime.timedelta()
302
if self.properties[u"ApprovedByDefault"]:
303
message = u"Approval in %s. (d)eny?"
311
if self.properties["ApprovedByDefault"]:
312
message = "Approval in %s. (d)eny?"
305
message = u"Denial in %s. (a)pprove?"
314
message = "Denial in %s. (a)pprove?"
306
315
message = message % unicode(timer).rsplit(".", 1)[0]
307
316
elif self.last_checker_failed:
308
317
timeout = datetime.timedelta(milliseconds
309
318
= self.properties
311
320
last_ok = isoformat_to_datetime(
312
max((self.properties[u"LastCheckedOK"]
313
or self.properties[u"Created"]),
314
self.properties[u"LastEnabled"]))
321
max((self.properties["LastCheckedOK"]
322
or self.properties["Created"]),
323
self.properties["LastEnabled"]))
315
324
timer = timeout - (datetime.datetime.utcnow() - last_ok)
316
message = (u'A checker has failed! Time until client'
317
u' gets disabled: %s'
325
message = ('A checker has failed! Time until client'
318
327
% unicode(timer).rsplit(".", 1)[0])
321
330
self._text = "%s%s" % (base, message)
323
332
if not urwid.supports_unicode():
324
333
self._text = self._text.encode("ascii", "replace")
325
textlist = [(u"normal", self._text)]
334
textlist = [("normal", self._text)]
326
335
self._text_widget.set_text(textlist)
327
336
self._focus_text_widget.set_text([(with_standout[text[0]],
342
351
return True # Keep calling this
353
def delete(self, *args, **kwargs):
345
354
if self._update_timer_callback_tag is not None:
346
355
gobject.source_remove(self._update_timer_callback_tag)
347
356
self._update_timer_callback_tag = None
357
for match in self.match_objects:
359
self.match_objects = ()
348
360
if self.delete_hook is not None:
349
361
self.delete_hook(self)
362
return super(MandosClientWidget, self).delete(*args, **kwargs)
351
def render(self, (maxcol,), focus=False):
364
def render(self, maxcolrow, focus=False):
352
365
"""Render differently if we have focus.
353
366
This overrides the method from urwid.FlowWidget"""
354
return self.current_widget(focus).render((maxcol,),
367
return self.current_widget(focus).render(maxcolrow,
357
def keypress(self, (maxcol,), key):
370
def keypress(self, maxcolrow, key):
359
372
This overrides the method from urwid.FlowWidget"""
361
self.proxy.Enable(dbus_interface = client_interface)
363
self.proxy.Disable(dbus_interface = client_interface)
374
self.proxy.Enable(dbus_interface = client_interface,
377
self.proxy.Disable(dbus_interface = client_interface,
365
380
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
366
dbus_interface = client_interface)
381
dbus_interface = client_interface,
368
384
self.proxy.Approve(dbus.Boolean(False, variant_level=1),
369
dbus_interface = client_interface)
370
elif key == u"R" or key == u"_" or key == u"ctrl k":
385
dbus_interface = client_interface,
387
elif key == "R" or key == "_" or key == "ctrl k":
371
388
self.server_proxy_object.RemoveClient(self.proxy
374
self.proxy.StartChecker(dbus_interface = client_interface)
376
self.proxy.StopChecker(dbus_interface = client_interface)
378
self.proxy.CheckedOK(dbus_interface = client_interface)
392
self.proxy.StartChecker(dbus_interface = client_interface,
395
self.proxy.StopChecker(dbus_interface = client_interface,
398
self.proxy.CheckedOK(dbus_interface = client_interface,
380
# elif key == u"p" or key == "=":
401
# elif key == "p" or key == "=":
381
402
# self.proxy.pause()
382
# elif key == u"u" or key == ":":
403
# elif key == "u" or key == ":":
383
404
# self.proxy.unpause()
384
# elif key == u"RET":
403
424
"down" key presses, thus not allowing any containing widgets to
404
425
use them as an excuse to shift focus away from this widget.
406
def keypress(self, (maxcol, maxrow), key):
407
ret = super(ConstrainedListBox, self).keypress((maxcol,
409
if ret in (u"up", u"down"):
427
def keypress(self, maxcolrow, key):
428
ret = super(ConstrainedListBox, self).keypress(maxcolrow, key)
429
if ret in ("up", "down"):
421
441
self.screen = urwid.curses_display.Screen()
423
443
self.screen.register_palette((
425
u"default", u"default", None),
427
u"default", u"default", u"bold"),
429
u"default", u"default", u"underline"),
431
u"default", u"default", u"standout"),
432
(u"bold-underline-blink",
433
u"default", u"default", (u"bold", u"underline")),
435
u"default", u"default", (u"bold", u"standout")),
436
(u"underline-blink-standout",
437
u"default", u"default", (u"underline", u"standout")),
438
(u"bold-underline-blink-standout",
439
u"default", u"default", (u"bold", u"underline",
445
"default", "default", None),
447
"default", "default", "bold"),
449
"default", "default", "underline"),
451
"default", "default", "standout"),
452
("bold-underline-blink",
453
"default", "default", ("bold", "underline")),
455
"default", "default", ("bold", "standout")),
456
("underline-blink-standout",
457
"default", "default", ("underline", "standout")),
458
("bold-underline-blink-standout",
459
"default", "default", ("bold", "underline",
443
463
if urwid.supports_unicode():
444
self.divider = u"─" # \u2500
445
#self.divider = u"━" # \u2501
464
self.divider = "─" # \u2500
465
#self.divider = "━" # \u2501
447
#self.divider = u"-" # \u002d
448
self.divider = u"_" # \u005f
467
#self.divider = "-" # \u002d
468
self.divider = "_" # \u005f
450
470
self.screen.start()
465
485
# This keeps track of whether self.uilist currently has
466
486
# self.logbox in it or not
467
487
self.log_visible = True
468
self.log_wrap = u"any"
488
self.log_wrap = "any"
471
self.log_message_raw((u"bold",
472
u"Mandos Monitor version " + version))
473
self.log_message_raw((u"bold",
491
self.log_message_raw(("bold",
492
"Mandos Monitor version " + version))
493
self.log_message_raw(("bold",
476
496
self.busname = domain + '.Mandos'
477
497
self.main_loop = gobject.MainLoop()
478
498
self.bus = dbus.SystemBus()
479
499
mandos_dbus_objc = self.bus.get_object(
480
self.busname, u"/", follow_name_owner_changes=True)
500
self.busname, "/", follow_name_owner_changes=True)
481
501
self.mandos_serv = dbus.Interface(mandos_dbus_objc,
483
503
= server_interface)
488
508
mandos_clients = dbus.Dictionary()
490
510
(self.mandos_serv
491
.connect_to_signal(u"ClientRemoved",
511
.connect_to_signal("ClientRemoved",
492
512
self.find_and_remove_client,
493
513
dbus_interface=server_interface,
494
514
byte_arrays=True))
495
515
(self.mandos_serv
496
.connect_to_signal(u"ClientAdded",
516
.connect_to_signal("ClientAdded",
497
517
self.add_new_client,
498
518
dbus_interface=server_interface,
499
519
byte_arrays=True))
500
520
(self.mandos_serv
501
.connect_to_signal(u"ClientNotFound",
521
.connect_to_signal("ClientNotFound",
502
522
self.client_not_found,
503
523
dbus_interface=server_interface,
504
524
byte_arrays=True))
551
571
and len(self.log) > self.max_log_length):
552
572
del self.log[0:len(self.log)-self.max_log_length-1]
553
573
self.logbox.set_focus(len(self.logbox.body.contents),
554
coming_from=u"above")
557
577
def toggle_log_display(self):
558
578
"""Toggle visibility of the log buffer."""
559
579
self.log_visible = not self.log_visible
561
#self.log_message(u"Log visibility changed to: "
581
#self.log_message("Log visibility changed to: "
562
582
# + unicode(self.log_visible))
564
584
def change_log_display(self):
565
585
"""Change type of log display.
566
586
Currently, this toggles wrapping of text lines."""
567
if self.log_wrap == u"clip":
568
self.log_wrap = u"any"
587
if self.log_wrap == "clip":
588
self.log_wrap = "any"
570
self.log_wrap = u"clip"
590
self.log_wrap = "clip"
571
591
for textwidget in self.log:
572
592
textwidget.set_wrap_mode(self.log_wrap)
573
#self.log_message(u"Wrap mode: " + self.log_wrap)
593
#self.log_message("Wrap mode: " + self.log_wrap)
575
595
def find_and_remove_client(self, path, name):
576
"""Find an client from its object path and remove it.
596
"""Find a client by its object path and remove it.
578
598
This is connected to the ClientRemoved signal from the
579
599
Mandos server object."""
641
663
def process_input(self, source, condition):
642
664
keys = self.screen.get_input()
643
translations = { u"ctrl n": u"down", # Emacs
644
u"ctrl p": u"up", # Emacs
645
u"ctrl v": u"page down", # Emacs
646
u"meta v": u"page up", # Emacs
647
u" ": u"page down", # less
648
u"f": u"page down", # less
649
u"b": u"page up", # less
665
translations = { "ctrl n": "down", # Emacs
666
"ctrl p": "up", # Emacs
667
"ctrl v": "page down", # Emacs
668
"meta v": "page up", # Emacs
669
" ": "page down", # less
670
"f": "page down", # less
671
"b": "page up", # less
656
678
except KeyError: # :-)
659
if key == u"q" or key == u"Q":
681
if key == "q" or key == "Q":
662
elif key == u"window resize":
684
elif key == "window resize":
663
685
self.size = self.screen.get_cols_rows()
665
elif key == u"\f": # Ctrl-L
687
elif key == "\f": # Ctrl-L
667
elif key == u"l" or key == u"D":
689
elif key == "l" or key == "D":
668
690
self.toggle_log_display()
670
elif key == u"w" or key == u"i":
692
elif key == "w" or key == "i":
671
693
self.change_log_display()
673
elif key == u"?" or key == u"f1" or key == u"esc":
695
elif key == "?" or key == "f1" or key == "esc":
674
696
if not self.log_visible:
675
697
self.log_visible = True
677
self.log_message_raw((u"bold",
681
u"l: Log window toggle",
682
u"TAB: Switch window",
684
self.log_message_raw((u"bold",
690
u"s: Start new checker",
699
self.log_message_raw(("bold",
703
"l: Log window toggle",
704
"TAB: Switch window",
706
self.log_message_raw(("bold",
712
"s: Start new checker",
697
719
if self.topwidget.get_focus() is self.logbox:
698
720
self.topwidget.set_focus(0)
700
722
self.topwidget.set_focus(self.logbox)
702
#elif (key == u"end" or key == u"meta >" or key == u"G"
724
#elif (key == "end" or key == "meta >" or key == "G"
704
726
# pass # xxx end-of-buffer
705
#elif (key == u"home" or key == u"meta <" or key == u"g"
727
#elif (key == "home" or key == "meta <" or key == "g"
707
729
# pass # xxx beginning-of-buffer
708
#elif key == u"ctrl e" or key == u"$":
730
#elif key == "ctrl e" or key == "$":
709
731
# pass # xxx move-end-of-line
710
#elif key == u"ctrl a" or key == u"^":
732
#elif key == "ctrl a" or key == "^":
711
733
# pass # xxx move-beginning-of-line
712
#elif key == u"ctrl b" or key == u"meta (" or key == u"h":
734
#elif key == "ctrl b" or key == "meta (" or key == "h":
713
735
# pass # xxx left
714
#elif key == u"ctrl f" or key == u"meta )" or key == u"l":
736
#elif key == "ctrl f" or key == "meta )" or key == "l":
715
737
# pass # xxx right
717
739
# pass # scroll up log
719
741
# pass # scroll down log
720
742
elif self.topwidget.selectable():
721
743
self.topwidget.keypress(self.size, key)