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)