66
65
    "Parse an ISO 8601 date string to a datetime.datetime()"
 
69
 
    d, t = iso.split("T", 1)
 
70
 
    year, month, day = d.split("-", 2)
 
71
 
    hour, minute, second = t.split(":", 2)
 
 
68
    d, t = iso.split(u"T", 1)
 
 
69
    year, month, day = d.split(u"-", 2)
 
 
70
    hour, minute, second = t.split(u":", 2)
 
72
71
    second, fraction = divmod(float(second), 1)
 
73
72
    return datetime.datetime(int(year),
 
 
87
86
        self.proxy = proxy_object # Mandos Client proxy object
 
89
88
        self.properties = dict()
 
90
 
        self.property_changed_match = (
 
91
 
            self.proxy.connect_to_signal("PropertyChanged",
 
92
 
                                         self.property_changed,
 
 
89
        self.proxy.connect_to_signal(u"PropertyChanged",
 
 
90
                                     self.property_changed,
 
96
94
        self.properties.update(
 
97
95
            self.proxy.GetAll(client_interface,
 
98
96
                              dbus_interface = dbus.PROPERTIES_IFACE))
 
100
 
        #XXX This breaks good super behaviour
 
 
98
        #XXX This break good super behaviour!
 
101
99
#        super(MandosClientPropertyCache, self).__init__(
 
102
100
#            *args, **kwargs)
 
 
161
154
        if self.need_approval:
 
162
155
            self.using_timer(True)
 
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"]))
 
 
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",
 
187
178
    def property_changed(self, property=None, value=None):
 
188
179
        super(self, MandosClientWidget).property_changed(property,
 
190
 
        if property == "ApprovalPending":
 
 
181
        if property == u"ApprovalPending":
 
191
182
            using_timer(bool(value))
 
193
184
    def using_timer(self, flag):
 
 
223
213
            self.last_checker_failed = True
 
224
214
            self.using_timer(True)
 
225
215
        if os.WIFEXITED(condition):
 
226
 
            self.logger('Checker for client %s (command "%s")'
 
227
 
                        ' failed with exit code %s'
 
228
 
                        % (self.properties["Name"], command,
 
 
216
            self.logger(u'Checker for client %s (command "%s")'
 
 
217
                        u' failed with exit code %s'
 
 
218
                        % (self.properties[u"Name"], command,
 
229
219
                           os.WEXITSTATUS(condition)))
 
230
220
        elif os.WIFSIGNALED(condition):
 
231
 
            self.logger('Checker for client %s (command "%s")'
 
232
 
                        ' was killed by signal %s'
 
233
 
                        % (self.properties["Name"], command,
 
 
221
            self.logger(u'Checker for client %s (command "%s")'
 
 
222
                        u' was killed by signal %s'
 
 
223
                        % (self.properties[u"Name"], command,
 
234
224
                           os.WTERMSIG(condition)))
 
235
225
        elif os.WCOREDUMP(condition):
 
236
 
            self.logger('Checker for client %s (command "%s")'
 
238
 
                        % (self.properties["Name"], command))
 
 
226
            self.logger(u'Checker for client %s (command "%s")'
 
 
228
                        % (self.properties[u"Name"], command))
 
240
 
            self.logger('Checker for client %s completed'
 
 
230
            self.logger(u'Checker for client %s completed'
 
244
234
    def checker_started(self, command):
 
245
 
        """Server signals that a checker started. This could be useful
 
246
 
           to log in the future. """
 
247
 
        #self.logger('Client %s started checker "%s"'
 
248
 
        #            % (self.properties["Name"], unicode(command)))
 
 
235
        #self.logger(u'Client %s started checker "%s"'
 
 
236
        #            % (self.properties[u"Name"], unicode(command)))
 
251
239
    def got_secret(self):
 
252
240
        self.last_checker_failed = False
 
253
 
        self.logger('Client %s received its secret'
 
254
 
                    % self.properties["Name"])
 
 
241
        self.logger(u'Client %s received its secret'
 
 
242
                    % self.properties[u"Name"])
 
256
244
    def need_approval(self, timeout, default):
 
258
 
            message = 'Client %s needs approval within %s seconds'
 
 
246
            message = u'Client %s needs approval within %s seconds'
 
260
 
            message = 'Client %s will get its secret in %s seconds'
 
 
248
            message = u'Client %s will get its secret in %s seconds'
 
261
249
        self.logger(message
 
262
 
                    % (self.properties["Name"], timeout/1000))
 
 
250
                    % (self.properties[u"Name"], timeout/1000))
 
263
251
        self.using_timer(True)
 
265
253
    def rejected(self, reason):
 
266
 
        self.logger('Client %s was rejected; reason: %s'
 
267
 
                    % (self.properties["Name"], reason))
 
 
254
        self.logger(u'Client %s was rejected; reason: %s'
 
 
255
                    % (self.properties[u"Name"], reason))
 
269
257
    def selectable(self):
 
270
258
        """Make this a "selectable" widget.
 
271
259
        This overrides the method from urwid.FlowWidget."""
 
274
 
    def rows(self, maxcolrow, focus=False):
 
 
262
    def rows(self, (maxcol,), focus=False):
 
275
263
        """How many rows this widget will occupy might depend on
 
276
264
        whether we have focus or not.
 
277
265
        This overrides the method from urwid.FlowWidget"""
 
278
 
        return self.current_widget(focus).rows(maxcolrow, focus=focus)
 
 
266
        return self.current_widget(focus).rows((maxcol,), focus=focus)
 
280
268
    def current_widget(self, focus=False):
 
281
269
        if focus or self.opened:
 
 
285
273
    def update(self):
 
286
274
        "Called when what is visible on the screen should be updated."
 
287
275
        # How to add standout mode to a style
 
288
 
        with_standout = { "normal": "standout",
 
289
 
                          "bold": "bold-standout",
 
291
 
                              "underline-blink-standout",
 
292
 
                          "bold-underline-blink":
 
293
 
                              "bold-underline-blink-standout",
 
 
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",
 
296
284
        # Rebuild focus and non-focus widgets using current properties
 
298
286
        # Base part of a client. Name!
 
300
 
                      % {"name": self.properties["Name"]})
 
301
 
        if not self.properties["Enabled"]:
 
303
 
        elif self.properties["ApprovalPending"]:
 
 
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"]:
 
304
292
            timeout = datetime.timedelta(milliseconds
 
305
293
                                         = self.properties
 
307
295
            last_approval_request = isoformat_to_datetime(
 
308
 
                self.properties["LastApprovalRequest"])
 
 
296
                self.properties[u"LastApprovalRequest"])
 
309
297
            if last_approval_request is not None:
 
310
298
                timer = timeout - (datetime.datetime.utcnow()
 
311
299
                                   - last_approval_request)
 
313
301
                timer = datetime.timedelta()
 
314
 
            if self.properties["ApprovedByDefault"]:
 
315
 
                message = "Approval in %s. (d)eny?"
 
 
302
            if self.properties[u"ApprovedByDefault"]:
 
 
303
                message = u"Approval in %s. (d)eny?"
 
317
 
                message = "Denial in %s. (a)pprove?"
 
 
305
                message = u"Denial in %s. (a)pprove?"
 
318
306
            message = message % unicode(timer).rsplit(".", 1)[0]
 
319
307
        elif self.last_checker_failed:
 
320
 
            # When checker has failed, print a timer until client expires
 
321
 
            expires = self.properties["Expires"]
 
323
 
                timer = datetime.timedelta(0)
 
325
 
                expires = datetime.datetime.strptime(expires,
 
326
 
                                                     '%Y-%m-%dT%H:%M:%S.%f')
 
327
 
                timer = expires - datetime.datetime.utcnow()
 
328
 
            message = ('A checker has failed! Time until client'
 
 
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'
 
330
318
                           % unicode(timer).rsplit(".", 1)[0])
 
333
321
        self._text = "%s%s" % (base, message)
 
335
323
        if not urwid.supports_unicode():
 
336
324
            self._text = self._text.encode("ascii", "replace")
 
337
 
        textlist = [("normal", self._text)]
 
 
325
        textlist = [(u"normal", self._text)]
 
338
326
        self._text_widget.set_text(textlist)
 
339
327
        self._focus_text_widget.set_text([(with_standout[text[0]],
 
 
349
337
            self.update_hook()
 
351
339
    def update_timer(self):
 
352
 
        """called by gobject. Will indefinitely loop until
 
353
 
        gobject.source_remove() on tag is called"""
 
355
342
        return True             # Keep calling this
 
357
 
    def delete(self, *args, **kwargs):
 
358
345
        if self._update_timer_callback_tag is not None:
 
359
346
            gobject.source_remove(self._update_timer_callback_tag)
 
360
347
            self._update_timer_callback_tag = None
 
361
 
        for match in self.match_objects:
 
363
 
        self.match_objects = ()
 
364
348
        if self.delete_hook is not None:
 
365
349
            self.delete_hook(self)
 
366
 
        return super(MandosClientWidget, self).delete(*args, **kwargs)
 
368
 
    def render(self, maxcolrow, focus=False):
 
 
351
    def render(self, (maxcol,), focus=False):
 
369
352
        """Render differently if we have focus.
 
370
353
        This overrides the method from urwid.FlowWidget"""
 
371
 
        return self.current_widget(focus).render(maxcolrow,
 
 
354
        return self.current_widget(focus).render((maxcol,),
 
374
 
    def keypress(self, maxcolrow, key):
 
 
357
    def keypress(self, (maxcol,), key):
 
376
359
        This overrides the method from urwid.FlowWidget"""
 
378
 
            self.proxy.Enable(dbus_interface = client_interface,
 
381
 
            self.proxy.Disable(dbus_interface = client_interface,
 
 
361
            self.proxy.Enable(dbus_interface = client_interface)
 
 
363
            self.proxy.Disable(dbus_interface = client_interface)
 
384
365
            self.proxy.Approve(dbus.Boolean(True, variant_level=1),
 
385
 
                               dbus_interface = client_interface,
 
 
366
                               dbus_interface = client_interface)
 
388
368
            self.proxy.Approve(dbus.Boolean(False, variant_level=1),
 
389
 
                                  dbus_interface = client_interface,
 
391
 
        elif key == "R" or key == "_" or key == "ctrl k":
 
 
369
                                  dbus_interface = client_interface)
 
 
370
        elif key == u"R" or key == u"_" or key == u"ctrl k":
 
392
371
            self.server_proxy_object.RemoveClient(self.proxy
 
396
 
            self.proxy.StartChecker(dbus_interface = client_interface,
 
399
 
            self.proxy.StopChecker(dbus_interface = client_interface,
 
402
 
            self.proxy.CheckedOK(dbus_interface = client_interface,
 
 
374
            self.proxy.StartChecker(dbus_interface = client_interface)
 
 
376
            self.proxy.StopChecker(dbus_interface = client_interface)
 
 
378
            self.proxy.CheckedOK(dbus_interface = client_interface)
 
405
 
#         elif key == "p" or key == "=":
 
 
380
#         elif key == u"p" or key == "=":
 
406
381
#             self.proxy.pause()
 
407
 
#         elif key == "u" or key == ":":
 
 
382
#         elif key == u"u" or key == ":":
 
408
383
#             self.proxy.unpause()
 
 
384
#         elif key == u"RET":
 
 
428
403
    "down" key presses, thus not allowing any containing widgets to
 
429
404
    use them as an excuse to shift focus away from this widget.
 
431
 
    def keypress(self, maxcolrow, key):
 
432
 
        ret = super(ConstrainedListBox, self).keypress(maxcolrow, key)
 
433
 
        if ret in ("up", "down"):
 
 
406
    def keypress(self, (maxcol, maxrow), key):
 
 
407
        ret = super(ConstrainedListBox, self).keypress((maxcol,
 
 
409
        if ret in (u"up", u"down"):
 
 
445
421
        self.screen = urwid.curses_display.Screen()
 
447
423
        self.screen.register_palette((
 
449
 
                 "default", "default", None),
 
451
 
                 "default", "default", "bold"),
 
453
 
                 "default", "default", "underline"),
 
455
 
                 "default", "default", "standout"),
 
456
 
                ("bold-underline-blink",
 
457
 
                 "default", "default", ("bold", "underline")),
 
459
 
                 "default", "default", ("bold", "standout")),
 
460
 
                ("underline-blink-standout",
 
461
 
                 "default", "default", ("underline", "standout")),
 
462
 
                ("bold-underline-blink-standout",
 
463
 
                 "default", "default", ("bold", "underline",
 
 
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",
 
467
443
        if urwid.supports_unicode():
 
468
 
            self.divider = "─" # \u2500
 
469
 
            #self.divider = "━" # \u2501
 
 
444
            self.divider = u"─" # \u2500
 
 
445
            #self.divider = u"━" # \u2501
 
471
 
            #self.divider = "-" # \u002d
 
472
 
            self.divider = "_" # \u005f
 
 
447
            #self.divider = u"-" # \u002d
 
 
448
            self.divider = u"_" # \u005f
 
474
450
        self.screen.start()
 
 
489
465
        # This keeps track of whether self.uilist currently has
 
490
466
        # self.logbox in it or not
 
491
467
        self.log_visible = True
 
492
 
        self.log_wrap = "any"
 
 
468
        self.log_wrap = u"any"
 
495
 
        self.log_message_raw(("bold",
 
496
 
                              "Mandos Monitor version " + version))
 
497
 
        self.log_message_raw(("bold",
 
 
471
        self.log_message_raw((u"bold",
 
 
472
                              u"Mandos Monitor version " + version))
 
 
473
        self.log_message_raw((u"bold",
 
500
476
        self.busname = domain + '.Mandos'
 
501
477
        self.main_loop = gobject.MainLoop()
 
 
478
        self.bus = dbus.SystemBus()
 
 
479
        mandos_dbus_objc = self.bus.get_object(
 
 
480
            self.busname, u"/", follow_name_owner_changes=True)
 
 
481
        self.mandos_serv = dbus.Interface(mandos_dbus_objc,
 
 
485
            mandos_clients = (self.mandos_serv
 
 
486
                              .GetAllClientsWithProperties())
 
 
487
        except dbus.exceptions.DBusException:
 
 
488
            mandos_clients = dbus.Dictionary()
 
 
491
         .connect_to_signal(u"ClientRemoved",
 
 
492
                            self.find_and_remove_client,
 
 
493
                            dbus_interface=server_interface,
 
 
496
         .connect_to_signal(u"ClientAdded",
 
 
498
                            dbus_interface=server_interface,
 
 
501
         .connect_to_signal(u"ClientNotFound",
 
 
502
                            self.client_not_found,
 
 
503
                            dbus_interface=server_interface,
 
 
505
        for path, client in mandos_clients.iteritems():
 
 
506
            client_proxy_object = self.bus.get_object(self.busname,
 
 
508
            self.add_client(MandosClientWidget(server_proxy_object
 
 
511
                                               =client_proxy_object,
 
503
521
    def client_not_found(self, fingerprint, address):
 
504
 
        self.log_message(("Client with address %s and fingerprint %s"
 
505
 
                          " could not be found" % (address,
 
 
522
        self.log_message((u"Client with address %s and fingerprint %s"
 
 
523
                          u" could not be found" % (address,
 
508
526
    def rebuild(self):
 
 
532
551
            and len(self.log) > self.max_log_length):
 
533
552
            del self.log[0:len(self.log)-self.max_log_length-1]
 
534
553
        self.logbox.set_focus(len(self.logbox.body.contents),
 
 
554
                              coming_from=u"above")
 
538
557
    def toggle_log_display(self):
 
539
558
        """Toggle visibility of the log buffer."""
 
540
559
        self.log_visible = not self.log_visible
 
542
 
        #self.log_message("Log visibility changed to: "
 
 
561
        #self.log_message(u"Log visibility changed to: "
 
543
562
        #                 + unicode(self.log_visible))
 
545
564
    def change_log_display(self):
 
546
565
        """Change type of log display.
 
547
566
        Currently, this toggles wrapping of text lines."""
 
548
 
        if self.log_wrap == "clip":
 
549
 
            self.log_wrap = "any"
 
 
567
        if self.log_wrap == u"clip":
 
 
568
            self.log_wrap = u"any"
 
551
 
            self.log_wrap = "clip"
 
 
570
            self.log_wrap = u"clip"
 
552
571
        for textwidget in self.log:
 
553
572
            textwidget.set_wrap_mode(self.log_wrap)
 
554
 
        #self.log_message("Wrap mode: " + self.log_wrap)
 
 
573
        #self.log_message(u"Wrap mode: " + self.log_wrap)
 
556
575
    def find_and_remove_client(self, path, name):
 
557
 
        """Find a client by its object path and remove it.
 
 
576
        """Find an client from its object path and remove it.
 
559
578
        This is connected to the ClientRemoved signal from the
 
560
579
        Mandos server object."""
 
 
610
627
        """Start the main loop and exit when it's done."""
 
611
 
        self.bus = dbus.SystemBus()
 
612
 
        mandos_dbus_objc = self.bus.get_object(
 
613
 
            self.busname, "/", follow_name_owner_changes=True)
 
614
 
        self.mandos_serv = dbus.Interface(mandos_dbus_objc,
 
618
 
            mandos_clients = (self.mandos_serv
 
619
 
                              .GetAllClientsWithProperties())
 
620
 
        except dbus.exceptions.DBusException:
 
621
 
            mandos_clients = dbus.Dictionary()
 
624
 
         .connect_to_signal("ClientRemoved",
 
625
 
                            self.find_and_remove_client,
 
626
 
                            dbus_interface=server_interface,
 
629
 
         .connect_to_signal("ClientAdded",
 
631
 
                            dbus_interface=server_interface,
 
634
 
         .connect_to_signal("ClientNotFound",
 
635
 
                            self.client_not_found,
 
636
 
                            dbus_interface=server_interface,
 
638
 
        for path, client in mandos_clients.iteritems():
 
639
 
            client_proxy_object = self.bus.get_object(self.busname,
 
641
 
            self.add_client(MandosClientWidget(server_proxy_object
 
644
 
                                               =client_proxy_object,
 
655
629
        self._input_callback_tag = (gobject.io_add_watch
 
656
630
                                    (sys.stdin.fileno(),
 
 
667
641
    def process_input(self, source, condition):
 
668
642
        keys = self.screen.get_input()
 
669
 
        translations = { "ctrl n": "down",      # Emacs
 
670
 
                         "ctrl p": "up",        # Emacs
 
671
 
                         "ctrl v": "page down", # Emacs
 
672
 
                         "meta v": "page up",   # Emacs
 
673
 
                         " ": "page down",      # less
 
674
 
                         "f": "page down",      # less
 
675
 
                         "b": "page up",        # less
 
 
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
 
 
682
656
            except KeyError:    # :-)
 
685
 
            if key == "q" or key == "Q":
 
 
659
            if key == u"q" or key == u"Q":
 
688
 
            elif key == "window resize":
 
 
662
            elif key == u"window resize":
 
689
663
                self.size = self.screen.get_cols_rows()
 
691
 
            elif key == "\f":  # Ctrl-L
 
 
665
            elif key == u"\f":  # Ctrl-L
 
693
 
            elif key == "l" or key == "D":
 
 
667
            elif key == u"l" or key == u"D":
 
694
668
                self.toggle_log_display()
 
696
 
            elif key == "w" or key == "i":
 
 
670
            elif key == u"w" or key == u"i":
 
697
671
                self.change_log_display()
 
699
 
            elif key == "?" or key == "f1" or key == "esc":
 
 
673
            elif key == u"?" or key == u"f1" or key == u"esc":
 
700
674
                if not self.log_visible:
 
701
675
                    self.log_visible = True
 
703
 
                self.log_message_raw(("bold",
 
707
 
                                            "l: Log window toggle",
 
708
 
                                            "TAB: Switch window",
 
710
 
                self.log_message_raw(("bold",
 
716
 
                                             "s: Start new checker",
 
 
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",
 
723
697
                if self.topwidget.get_focus() is self.logbox:
 
724
698
                    self.topwidget.set_focus(0)
 
726
700
                    self.topwidget.set_focus(self.logbox)
 
728
 
            #elif (key == "end" or key == "meta >" or key == "G"
 
 
702
            #elif (key == u"end" or key == u"meta >" or key == u"G"
 
730
704
            #    pass            # xxx end-of-buffer
 
731
 
            #elif (key == "home" or key == "meta <" or key == "g"
 
 
705
            #elif (key == u"home" or key == u"meta <" or key == u"g"
 
733
707
            #    pass            # xxx beginning-of-buffer
 
734
 
            #elif key == "ctrl e" or key == "$":
 
 
708
            #elif key == u"ctrl e" or key == u"$":
 
735
709
            #    pass            # xxx move-end-of-line
 
736
 
            #elif key == "ctrl a" or key == "^":
 
 
710
            #elif key == u"ctrl a" or key == u"^":
 
737
711
            #    pass            # xxx move-beginning-of-line
 
738
 
            #elif key == "ctrl b" or key == "meta (" or key == "h":
 
 
712
            #elif key == u"ctrl b" or key == u"meta (" or key == u"h":
 
739
713
            #    pass            # xxx left
 
740
 
            #elif key == "ctrl f" or key == "meta )" or key == "l":
 
 
714
            #elif key == u"ctrl f" or key == u"meta )" or key == u"l":
 
741
715
            #    pass            # xxx right
 
743
717
            #    pass            # scroll up log
 
745
719
            #    pass            # scroll down log
 
746
720
            elif self.topwidget.selectable():
 
747
721
                self.topwidget.keypress(self.size, key)