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
223
self.last_checker_failed = True
214
224
self.using_timer(True)
215
225
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,
226
self.logger('Checker for client %s (command "%s")'
227
' failed with exit code %s'
228
% (self.properties["Name"], command,
219
229
os.WEXITSTATUS(condition)))
220
230
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,
231
self.logger('Checker for client %s (command "%s")'
232
' was killed by signal %s'
233
% (self.properties["Name"], command,
224
234
os.WTERMSIG(condition)))
225
235
elif os.WCOREDUMP(condition):
226
self.logger(u'Checker for client %s (command "%s")'
228
% (self.properties[u"Name"], command))
236
self.logger('Checker for client %s (command "%s")'
238
% (self.properties["Name"], command))
230
self.logger(u'Checker for client %s completed'
240
self.logger('Checker for client %s completed'
234
244
def checker_started(self, command):
235
#self.logger(u'Client %s started checker "%s"'
236
# % (self.properties[u"Name"], unicode(command)))
245
#self.logger('Client %s started checker "%s"'
246
# % (self.properties["Name"], unicode(command)))
239
249
def got_secret(self):
240
250
self.last_checker_failed = False
241
self.logger(u'Client %s received its secret'
242
% self.properties[u"Name"])
251
self.logger('Client %s received its secret'
252
% self.properties["Name"])
244
254
def need_approval(self, timeout, default):
246
message = u'Client %s needs approval within %s seconds'
256
message = 'Client %s needs approval within %s seconds'
248
message = u'Client %s will get its secret in %s seconds'
258
message = 'Client %s will get its secret in %s seconds'
249
259
self.logger(message
250
% (self.properties[u"Name"], timeout/1000))
260
% (self.properties["Name"], timeout/1000))
251
261
self.using_timer(True)
253
263
def rejected(self, reason):
254
self.logger(u'Client %s was rejected; reason: %s'
255
% (self.properties[u"Name"], reason))
264
self.logger('Client %s was rejected; reason: %s'
265
% (self.properties["Name"], reason))
257
267
def selectable(self):
258
268
"""Make this a "selectable" widget.
259
269
This overrides the method from urwid.FlowWidget."""
262
def rows(self, (maxcol,), focus=False):
272
def rows(self, maxcolrow, focus=False):
263
273
"""How many rows this widget will occupy might depend on
264
274
whether we have focus or not.
265
275
This overrides the method from urwid.FlowWidget"""
266
return self.current_widget(focus).rows((maxcol,), focus=focus)
276
return self.current_widget(focus).rows(maxcolrow, focus=focus)
268
278
def current_widget(self, focus=False):
269
279
if focus or self.opened:
273
283
def update(self):
274
284
"Called when what is visible on the screen should be updated."
275
285
# 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",
286
with_standout = { "normal": "standout",
287
"bold": "bold-standout",
289
"underline-blink-standout",
290
"bold-underline-blink":
291
"bold-underline-blink-standout",
284
294
# Rebuild focus and non-focus widgets using current properties
286
296
# 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"]:
298
% {"name": self.properties["Name"]})
299
if not self.properties["Enabled"]:
301
elif self.properties["ApprovalPending"]:
292
302
timeout = datetime.timedelta(milliseconds
293
303
= self.properties
295
305
last_approval_request = isoformat_to_datetime(
296
self.properties[u"LastApprovalRequest"])
306
self.properties["LastApprovalRequest"])
297
307
if last_approval_request is not None:
298
308
timer = timeout - (datetime.datetime.utcnow()
299
309
- last_approval_request)
301
311
timer = datetime.timedelta()
302
if self.properties[u"ApprovedByDefault"]:
303
message = u"Approval in %s. (d)eny?"
312
if self.properties["ApprovedByDefault"]:
313
message = "Approval in %s. (d)eny?"
305
message = u"Denial in %s. (a)pprove?"
315
message = "Denial in %s. (a)pprove?"
306
316
message = message % unicode(timer).rsplit(".", 1)[0]
307
317
elif self.last_checker_failed:
308
timeout = datetime.timedelta(milliseconds
311
last_ok = isoformat_to_datetime(
312
max((self.properties[u"LastCheckedOK"]
313
or self.properties[u"Created"]),
314
self.properties[u"LastEnabled"]))
315
timer = timeout - (datetime.datetime.utcnow() - last_ok)
316
message = (u'A checker has failed! Time until client'
317
u' gets disabled: %s'
318
# When checker has failed, print a timer until client expires
319
expires = self.properties["Expires"]
321
timer = datetime.timedelta(0)
323
expires = datetime.datetime.strptime(expires,
324
'%Y-%m-%dT%H:%M:%S.%f')
325
timer = expires - datetime.datetime.utcnow()
326
message = ('A checker has failed! Time until client'
318
328
% unicode(timer).rsplit(".", 1)[0])
321
331
self._text = "%s%s" % (base, message)
323
333
if not urwid.supports_unicode():
324
334
self._text = self._text.encode("ascii", "replace")
325
textlist = [(u"normal", self._text)]
335
textlist = [("normal", self._text)]
326
336
self._text_widget.set_text(textlist)
327
337
self._focus_text_widget.set_text([(with_standout[text[0]],
337
347
self.update_hook()
339
349
def update_timer(self):
350
"""called by gobject. Will indefinitely loop until
351
gobject.source_remove() on tag is called"""
342
353
return True # Keep calling this
355
def delete(self, *args, **kwargs):
345
356
if self._update_timer_callback_tag is not None:
346
357
gobject.source_remove(self._update_timer_callback_tag)
347
358
self._update_timer_callback_tag = None
359
for match in self.match_objects:
361
self.match_objects = ()
348
362
if self.delete_hook is not None:
349
363
self.delete_hook(self)
364
return super(MandosClientWidget, self).delete(*args, **kwargs)
351
def render(self, (maxcol,), focus=False):
366
def render(self, maxcolrow, focus=False):
352
367
"""Render differently if we have focus.
353
368
This overrides the method from urwid.FlowWidget"""
354
return self.current_widget(focus).render((maxcol,),
369
return self.current_widget(focus).render(maxcolrow,
357
def keypress(self, (maxcol,), key):
372
def keypress(self, maxcolrow, key):
359
374
This overrides the method from urwid.FlowWidget"""
361
self.proxy.Enable(dbus_interface = client_interface)
363
self.proxy.Disable(dbus_interface = client_interface)
376
self.proxy.Enable(dbus_interface = client_interface,
379
self.proxy.Disable(dbus_interface = client_interface,
365
382
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
366
dbus_interface = client_interface)
383
dbus_interface = client_interface,
368
386
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":
387
dbus_interface = client_interface,
389
elif key == "R" or key == "_" or key == "ctrl k":
371
390
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)
394
self.proxy.StartChecker(dbus_interface = client_interface,
397
self.proxy.StopChecker(dbus_interface = client_interface,
400
self.proxy.CheckedOK(dbus_interface = client_interface,
380
# elif key == u"p" or key == "=":
403
# elif key == "p" or key == "=":
381
404
# self.proxy.pause()
382
# elif key == u"u" or key == ":":
405
# elif key == "u" or key == ":":
383
406
# self.proxy.unpause()
384
# elif key == u"RET":
403
426
"down" key presses, thus not allowing any containing widgets to
404
427
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"):
429
def keypress(self, maxcolrow, key):
430
ret = super(ConstrainedListBox, self).keypress(maxcolrow, key)
431
if ret in ("up", "down"):
421
443
self.screen = urwid.curses_display.Screen()
423
445
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",
447
"default", "default", None),
449
"default", "default", "bold"),
451
"default", "default", "underline"),
453
"default", "default", "standout"),
454
("bold-underline-blink",
455
"default", "default", ("bold", "underline")),
457
"default", "default", ("bold", "standout")),
458
("underline-blink-standout",
459
"default", "default", ("underline", "standout")),
460
("bold-underline-blink-standout",
461
"default", "default", ("bold", "underline",
443
465
if urwid.supports_unicode():
444
self.divider = u"─" # \u2500
445
#self.divider = u"━" # \u2501
466
self.divider = "─" # \u2500
467
#self.divider = "━" # \u2501
447
#self.divider = u"-" # \u002d
448
self.divider = u"_" # \u005f
469
#self.divider = "-" # \u002d
470
self.divider = "_" # \u005f
450
472
self.screen.start()
465
487
# This keeps track of whether self.uilist currently has
466
488
# self.logbox in it or not
467
489
self.log_visible = True
468
self.log_wrap = u"any"
490
self.log_wrap = "any"
471
self.log_message_raw((u"bold",
472
u"Mandos Monitor version " + version))
473
self.log_message_raw((u"bold",
493
self.log_message_raw(("bold",
494
"Mandos Monitor version " + version))
495
self.log_message_raw(("bold",
476
498
self.busname = domain + '.Mandos'
477
499
self.main_loop = gobject.MainLoop()
478
500
self.bus = dbus.SystemBus()
479
501
mandos_dbus_objc = self.bus.get_object(
480
self.busname, u"/", follow_name_owner_changes=True)
502
self.busname, "/", follow_name_owner_changes=True)
481
503
self.mandos_serv = dbus.Interface(mandos_dbus_objc,
483
505
= server_interface)
488
510
mandos_clients = dbus.Dictionary()
490
512
(self.mandos_serv
491
.connect_to_signal(u"ClientRemoved",
513
.connect_to_signal("ClientRemoved",
492
514
self.find_and_remove_client,
493
515
dbus_interface=server_interface,
494
516
byte_arrays=True))
495
517
(self.mandos_serv
496
.connect_to_signal(u"ClientAdded",
518
.connect_to_signal("ClientAdded",
497
519
self.add_new_client,
498
520
dbus_interface=server_interface,
499
521
byte_arrays=True))
500
522
(self.mandos_serv
501
.connect_to_signal(u"ClientNotFound",
523
.connect_to_signal("ClientNotFound",
502
524
self.client_not_found,
503
525
dbus_interface=server_interface,
504
526
byte_arrays=True))
551
573
and len(self.log) > self.max_log_length):
552
574
del self.log[0:len(self.log)-self.max_log_length-1]
553
575
self.logbox.set_focus(len(self.logbox.body.contents),
554
coming_from=u"above")
557
579
def toggle_log_display(self):
558
580
"""Toggle visibility of the log buffer."""
559
581
self.log_visible = not self.log_visible
561
#self.log_message(u"Log visibility changed to: "
583
#self.log_message("Log visibility changed to: "
562
584
# + unicode(self.log_visible))
564
586
def change_log_display(self):
565
587
"""Change type of log display.
566
588
Currently, this toggles wrapping of text lines."""
567
if self.log_wrap == u"clip":
568
self.log_wrap = u"any"
589
if self.log_wrap == "clip":
590
self.log_wrap = "any"
570
self.log_wrap = u"clip"
592
self.log_wrap = "clip"
571
593
for textwidget in self.log:
572
594
textwidget.set_wrap_mode(self.log_wrap)
573
#self.log_message(u"Wrap mode: " + self.log_wrap)
595
#self.log_message("Wrap mode: " + self.log_wrap)
575
597
def find_and_remove_client(self, path, name):
576
"""Find an client from its object path and remove it.
598
"""Find a client by its object path and remove it.
578
600
This is connected to the ClientRemoved signal from the
579
601
Mandos server object."""
641
665
def process_input(self, source, condition):
642
666
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
667
translations = { "ctrl n": "down", # Emacs
668
"ctrl p": "up", # Emacs
669
"ctrl v": "page down", # Emacs
670
"meta v": "page up", # Emacs
671
" ": "page down", # less
672
"f": "page down", # less
673
"b": "page up", # less
656
680
except KeyError: # :-)
659
if key == u"q" or key == u"Q":
683
if key == "q" or key == "Q":
662
elif key == u"window resize":
686
elif key == "window resize":
663
687
self.size = self.screen.get_cols_rows()
665
elif key == u"\f": # Ctrl-L
689
elif key == "\f": # Ctrl-L
667
elif key == u"l" or key == u"D":
691
elif key == "l" or key == "D":
668
692
self.toggle_log_display()
670
elif key == u"w" or key == u"i":
694
elif key == "w" or key == "i":
671
695
self.change_log_display()
673
elif key == u"?" or key == u"f1" or key == u"esc":
697
elif key == "?" or key == "f1" or key == "esc":
674
698
if not self.log_visible:
675
699
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",
701
self.log_message_raw(("bold",
705
"l: Log window toggle",
706
"TAB: Switch window",
708
self.log_message_raw(("bold",
714
"s: Start new checker",
697
721
if self.topwidget.get_focus() is self.logbox:
698
722
self.topwidget.set_focus(0)
700
724
self.topwidget.set_focus(self.logbox)
702
#elif (key == u"end" or key == u"meta >" or key == u"G"
726
#elif (key == "end" or key == "meta >" or key == "G"
704
728
# pass # xxx end-of-buffer
705
#elif (key == u"home" or key == u"meta <" or key == u"g"
729
#elif (key == "home" or key == "meta <" or key == "g"
707
731
# pass # xxx beginning-of-buffer
708
#elif key == u"ctrl e" or key == u"$":
732
#elif key == "ctrl e" or key == "$":
709
733
# pass # xxx move-end-of-line
710
#elif key == u"ctrl a" or key == u"^":
734
#elif key == "ctrl a" or key == "^":
711
735
# pass # xxx move-beginning-of-line
712
#elif key == u"ctrl b" or key == u"meta (" or key == u"h":
736
#elif key == "ctrl b" or key == "meta (" or key == "h":
713
737
# pass # xxx left
714
#elif key == u"ctrl f" or key == u"meta )" or key == u"l":
738
#elif key == "ctrl f" or key == "meta )" or key == "l":
715
739
# pass # xxx right
717
741
# pass # scroll up log
719
743
# pass # scroll down log
720
744
elif self.topwidget.selectable():
721
745
self.topwidget.keypress(self.size, key)