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)