101
106
        super(MandosClientPropertyCache, self).__init__(**kwargs)
 
103
 
    def _property_changed(self, property, value):
 
104
 
        """Helper which takes positional arguments"""
 
105
 
        return self.property_changed(property=property, value=value)
 
107
 
    def property_changed(self, property=None, value=None):
 
108
 
        """This is called whenever we get a PropertyChanged signal
 
109
 
        It updates the changed property in the "properties" dict.
 
 
108
    def properties_changed(self, interface, properties, invalidated):
 
 
109
        """This is called whenever we get a PropertiesChanged signal
 
 
110
        It updates the changed properties in the "properties" dict.
 
111
112
        # Update properties dict with new value
 
112
 
        self.properties[property] = value
 
 
113
        if interface == client_interface:
 
 
114
            self.properties.update(properties)
 
114
116
    def delete(self):
 
115
117
        self.property_changed_match.remove()
 
 
171
173
        if flag and self._update_timer_callback_tag is None:
 
172
174
            # Will update the shown timer value every second
 
173
 
            self._update_timer_callback_tag = (gobject.timeout_add
 
 
175
            self._update_timer_callback_tag = (GObject.timeout_add
 
175
177
                                                self.update_timer))
 
176
178
        elif not (flag or self._update_timer_callback_tag is None):
 
177
 
            gobject.source_remove(self._update_timer_callback_tag)
 
 
179
            GObject.source_remove(self._update_timer_callback_tag)
 
178
180
            self._update_timer_callback_tag = None
 
180
182
    def checker_completed(self, exitstatus, condition, command):
 
181
183
        if exitstatus == 0:
 
 
184
            self.logger('Checker for client {} (command "{}")'
 
 
185
                        ' succeeded'.format(self.properties["Name"],
 
185
190
        if os.WIFEXITED(condition):
 
186
 
            self.logger('Checker for client {0} (command "{1}")'
 
187
 
                        ' failed with exit code {2}'
 
 
191
            self.logger('Checker for client {} (command "{}") failed'
 
188
193
                        .format(self.properties["Name"], command,
 
189
194
                                os.WEXITSTATUS(condition)))
 
190
195
        elif os.WIFSIGNALED(condition):
 
191
 
            self.logger('Checker for client {0} (command "{1}") was'
 
192
 
                        ' killed by signal {2}'
 
 
196
            self.logger('Checker for client {} (command "{}") was'
 
 
197
                        ' killed by signal {}'
 
193
198
                        .format(self.properties["Name"], command,
 
194
199
                                os.WTERMSIG(condition)))
 
195
 
        elif os.WCOREDUMP(condition):
 
196
 
            self.logger('Checker for client {0} (command "{1}")'
 
198
 
                        .format(self.properties["Name"], command))
 
200
 
            self.logger('Checker for client {0} completed'
 
202
 
                        .format(self.properties["Name"]))
 
205
202
    def checker_started(self, command):
 
206
 
        """Server signals that a checker started. This could be useful
 
207
 
           to log in the future. """
 
208
 
        #self.logger('Client {0} started checker "{1}"'
 
209
 
        #            .format(self.properties["Name"],
 
 
203
        """Server signals that a checker started."""
 
 
204
        self.logger('Client {} started checker "{}"'
 
 
205
                    .format(self.properties["Name"],
 
213
208
    def got_secret(self):
 
214
 
        self.logger('Client {0} received its secret'
 
 
209
        self.logger('Client {} received its secret'
 
215
210
                    .format(self.properties["Name"]))
 
217
212
    def need_approval(self, timeout, default):
 
219
 
            message = 'Client {0} needs approval within {1} seconds'
 
 
214
            message = 'Client {} needs approval within {} seconds'
 
221
 
            message = 'Client {0} will get its secret in {1} seconds'
 
 
216
            message = 'Client {} will get its secret in {} seconds'
 
222
217
        self.logger(message.format(self.properties["Name"],
 
225
220
    def rejected(self, reason):
 
226
 
        self.logger('Client {0} was rejected; reason: {1}'
 
 
221
        self.logger('Client {} was rejected; reason: {}'
 
227
222
                    .format(self.properties["Name"], reason))
 
229
224
    def selectable(self):
 
 
274
269
                timer = datetime.timedelta()
 
275
270
            if self.properties["ApprovedByDefault"]:
 
276
 
                message = "Approval in {0}. (d)eny?"
 
 
271
                message = "Approval in {}. (d)eny?"
 
278
 
                message = "Denial in {0}. (a)pprove?"
 
 
273
                message = "Denial in {}. (a)pprove?"
 
279
274
            message = message.format(str(timer).rsplit(".", 1)[0])
 
280
275
            self.using_timer(True)
 
281
276
        elif self.properties["LastCheckerStatus"] != 0:
 
 
289
284
                timer = max(expires - datetime.datetime.utcnow(),
 
290
285
                            datetime.timedelta())
 
291
286
            message = ('A checker has failed! Time until client'
 
292
 
                       ' gets disabled: {0}'
 
293
288
                       .format(str(timer).rsplit(".", 1)[0]))
 
294
289
            self.using_timer(True)
 
296
291
            message = "enabled"
 
297
292
            self.using_timer(False)
 
298
 
        self._text = "{0}{1}".format(base, message)
 
 
293
        self._text = "{}{}".format(base, message)
 
300
295
        if not urwid.supports_unicode():
 
301
296
            self._text = self._text.encode("ascii", "replace")
 
 
314
309
            self.update_hook()
 
316
311
    def update_timer(self):
 
317
 
        """called by gobject. Will indefinitely loop until
 
318
 
        gobject.source_remove() on tag is called"""
 
 
312
        """called by GObject. Will indefinitely loop until
 
 
313
        GObject.source_remove() on tag is called"""
 
320
315
        return True             # Keep calling this
 
322
317
    def delete(self, **kwargs):
 
323
318
        if self._update_timer_callback_tag is not None:
 
324
 
            gobject.source_remove(self._update_timer_callback_tag)
 
 
319
            GObject.source_remove(self._update_timer_callback_tag)
 
325
320
            self._update_timer_callback_tag = None
 
326
321
        for match in self.match_objects:
 
 
341
336
        This overrides the method from urwid.FlowWidget"""
 
343
 
            self.proxy.Enable(dbus_interface = client_interface,
 
 
338
            self.proxy.Set(client_interface, "Enabled",
 
 
339
                           dbus.Boolean(True), ignore_reply = True,
 
 
340
                           dbus_interface = dbus.PROPERTIES_IFACE)
 
346
 
            self.proxy.Disable(dbus_interface = client_interface,
 
 
342
            self.proxy.Set(client_interface, "Enabled", False,
 
 
344
                           dbus_interface = dbus.PROPERTIES_IFACE)
 
349
346
            self.proxy.Approve(dbus.Boolean(True, variant_level=1),
 
350
347
                               dbus_interface = client_interface,
 
 
359
356
                                                  ignore_reply=True)
 
361
 
            self.proxy.StartChecker(dbus_interface = client_interface,
 
 
358
            self.proxy.Set(client_interface, "CheckerRunning",
 
 
359
                           dbus.Boolean(True), ignore_reply = True,
 
 
360
                           dbus_interface = dbus.PROPERTIES_IFACE)
 
364
 
            self.proxy.StopChecker(dbus_interface = client_interface,
 
 
362
            self.proxy.Set(client_interface, "CheckerRunning",
 
 
363
                           dbus.Boolean(False), ignore_reply = True,
 
 
364
                           dbus_interface = dbus.PROPERTIES_IFACE)
 
367
366
            self.proxy.CheckedOK(dbus_interface = client_interface,
 
368
367
                                 ignore_reply=True)
 
 
379
 
    def property_changed(self, property=None, **kwargs):
 
380
 
        """Call self.update() if old value is not new value.
 
 
378
    def properties_changed(self, interface, properties, invalidated):
 
 
379
        """Call self.update() if any properties changed.
 
381
380
        This overrides the method from MandosClientPropertyCache"""
 
382
 
        property_name = str(property)
 
383
 
        old_value = self.properties.get(property_name)
 
384
 
        super(MandosClientWidget, self).property_changed(
 
385
 
            property=property, **kwargs)
 
386
 
        if self.properties.get(property_name) != old_value:
 
 
381
        old_values = { key: self.properties.get(key)
 
 
382
                       for key in properties.keys() }
 
 
383
        super(MandosClientWidget, self).properties_changed(
 
 
384
            interface, properties, invalidated)
 
 
385
        if any(old_values[key] != self.properties.get(key)
 
 
386
               for key in old_values):
 
 
403
403
    """This is the entire user interface - the whole screen
 
404
404
    with boxes, lists of client widgets, etc.
 
406
 
    def __init__(self, max_log_length=1000):
 
 
406
    def __init__(self, max_log_length=1000, log_level=1):
 
407
407
        DBusGMainLoop(set_as_default=True)
 
409
409
        self.screen = urwid.curses_display.Screen()
 
 
463
465
                              "q: Quit  ?: Help"))
 
465
467
        self.busname = domain + '.Mandos'
 
466
 
        self.main_loop = gobject.MainLoop()
 
 
468
        self.main_loop = GObject.MainLoop()
 
468
470
    def client_not_found(self, fingerprint, address):
 
469
 
        self.log_message("Client with address {0} and fingerprint"
 
470
 
                         " {1} could not be found"
 
 
471
        self.log_message("Client with address {} and fingerprint {}"
 
 
472
                         " could not be found"
 
471
473
                         .format(address, fingerprint))
 
473
475
    def rebuild(self):
 
 
486
488
            self.uilist.append(self.logbox)
 
487
489
        self.topwidget = urwid.Pile(self.uilist)
 
489
 
    def log_message(self, message):
 
 
491
    def log_message(self, message, level=1):
 
490
492
        """Log message formatted with timestamp"""
 
 
493
        if level < self.log_level:
 
491
495
        timestamp = datetime.datetime.now().isoformat()
 
492
 
        self.log_message_raw(timestamp + ": " + message)
 
 
496
        self.log_message_raw("{}: {}".format(timestamp, message),
 
494
 
    def log_message_raw(self, markup):
 
 
499
    def log_message_raw(self, markup, level=1):
 
495
500
        """Add a log message to the log buffer."""
 
 
501
        if level < self.log_level:
 
496
503
        self.log.append(urwid.Text(markup, wrap=self.log_wrap))
 
497
504
        if (self.max_log_length
 
498
505
            and len(self.log) > self.max_log_length):
 
 
505
512
        """Toggle visibility of the log buffer."""
 
506
513
        self.log_visible = not self.log_visible
 
508
 
        #self.log_message("Log visibility changed to: "
 
509
 
        #                 + str(self.log_visible))
 
 
515
        self.log_message("Log visibility changed to: {}"
 
 
516
                         .format(self.log_visible), level=0)
 
511
518
    def change_log_display(self):
 
512
519
        """Change type of log display.
 
 
517
524
            self.log_wrap = "clip"
 
518
525
        for textwidget in self.log:
 
519
526
            textwidget.set_wrap_mode(self.log_wrap)
 
520
 
        #self.log_message("Wrap mode: " + self.log_wrap)
 
 
527
        self.log_message("Wrap mode: {}".format(self.log_wrap),
 
522
 
    def find_and_remove_client(self, path, name):
 
 
530
    def find_and_remove_client(self, path, interfaces):
 
523
531
        """Find a client by its object path and remove it.
 
525
 
        This is connected to the ClientRemoved signal from the
 
 
533
        This is connected to the InterfacesRemoved signal from the
 
526
534
        Mandos server object."""
 
 
535
        if client_interface not in interfaces:
 
 
536
            # Not a Mandos client object; ignore
 
528
539
            client = self.clients_dict[path]
 
531
 
            self.log_message("Unknown client {0!r} ({1!r}) removed"
 
 
542
            self.log_message("Unknown client {!r} removed"
 
536
 
    def add_new_client(self, path):
 
 
547
    def add_new_client(self, path, ifs_and_props):
 
 
548
        """Find a client by its object path and remove it.
 
 
550
        This is connected to the InterfacesAdded signal from the
 
 
551
        Mandos server object.
 
 
553
        if client_interface not in ifs_and_props:
 
 
554
            # Not a Mandos client object; ignore
 
537
556
        client_proxy_object = self.bus.get_object(self.busname, path)
 
538
557
        self.add_client(MandosClientWidget(server_proxy_object
 
539
558
                                           =self.mandos_serv,
 
 
585
607
            mandos_clients = dbus.Dictionary()
 
587
609
        (self.mandos_serv
 
588
 
         .connect_to_signal("ClientRemoved",
 
 
610
         .connect_to_signal("InterfacesRemoved",
 
589
611
                            self.find_and_remove_client,
 
590
 
                            dbus_interface=server_interface,
 
 
613
                            = dbus.OBJECT_MANAGER_IFACE,
 
591
614
                            byte_arrays=True))
 
592
615
        (self.mandos_serv
 
593
 
         .connect_to_signal("ClientAdded",
 
 
616
         .connect_to_signal("InterfacesAdded",
 
594
617
                            self.add_new_client,
 
595
 
                            dbus_interface=server_interface,
 
 
619
                            = dbus.OBJECT_MANAGER_IFACE,
 
596
620
                            byte_arrays=True))
 
597
621
        (self.mandos_serv
 
598
622
         .connect_to_signal("ClientNotFound",