83
88
    properties and calls a hook function when any of them are
 
86
 
    def __init__(self, proxy_object=None, *args, **kwargs):
 
 
91
    def __init__(self, proxy_object=None, properties=None, **kwargs):
 
87
92
        self.proxy = proxy_object # Mandos Client proxy object
 
89
 
        self.properties = dict()
 
 
93
        self.properties = dict() if properties is None else properties
 
90
94
        self.property_changed_match = (
 
91
 
            self.proxy.connect_to_signal("PropertyChanged",
 
92
 
                                         self.property_changed,
 
 
95
            self.proxy.connect_to_signal("PropertiesChanged",
 
 
96
                                         self.properties_changed,
 
 
97
                                         dbus.PROPERTIES_IFACE,
 
96
 
        self.properties.update(
 
97
 
            self.proxy.GetAll(client_interface,
 
98
 
                              dbus_interface = dbus.PROPERTIES_IFACE))
 
100
 
        #XXX This breaks good super behaviour
 
101
 
#        super(MandosClientPropertyCache, self).__init__(
 
 
100
        if properties is None:
 
 
101
            self.properties.update(
 
 
102
                self.proxy.GetAll(client_interface,
 
 
104
                                  = dbus.PROPERTIES_IFACE))
 
 
106
        super(MandosClientPropertyCache, self).__init__(**kwargs)
 
104
 
    def property_changed(self, property=None, value=None):
 
105
 
        """This is called whenever we get a PropertyChanged signal
 
106
 
        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.
 
108
112
        # Update properties dict with new value
 
109
 
        self.properties[property] = value
 
 
113
        if interface == client_interface:
 
 
114
            self.properties.update(properties)
 
111
 
    def delete(self, *args, **kwargs):
 
112
117
        self.property_changed_match.remove()
 
113
 
        super(MandosClientPropertyCache, self).__init__(
 
117
120
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
 
 
130
133
        self.logger = logger
 
132
135
        self._update_timer_callback_tag = None
 
133
 
        self._update_timer_callback_lock = 0
 
134
 
        self.last_checker_failed = False
 
136
137
        # The widget shown normally
 
137
138
        self._text_widget = urwid.Text("")
 
138
139
        # The widget shown when we have focus
 
139
140
        self._focus_text_widget = urwid.Text("")
 
140
 
        super(MandosClientWidget, self).__init__(
 
141
 
            update_hook=update_hook, delete_hook=delete_hook,
 
 
141
        super(MandosClientWidget, self).__init__(**kwargs)
 
144
143
        self.opened = False
 
146
 
        last_checked_ok = isoformat_to_datetime(self.properties
 
148
 
        if last_checked_ok is None:
 
149
 
            self.last_checker_failed = True
 
151
 
            self.last_checker_failed = ((datetime.datetime.utcnow()
 
158
 
        if self.last_checker_failed:
 
159
 
            self.using_timer(True)
 
161
 
        if self.need_approval:
 
162
 
            self.using_timer(True)
 
164
145
        self.match_objects = (
 
165
146
            self.proxy.connect_to_signal("CheckerCompleted",
 
166
147
                                         self.checker_completed,
 
 
183
164
                                         client_interface,
 
184
165
                                         byte_arrays=True))
 
185
 
        #self.logger('Created client %s' % (self.properties["Name"]))
 
 
166
        self.logger('Created client {}'
 
 
167
                    .format(self.properties["Name"]), level=0)
 
187
 
    def property_changed(self, property=None, value=None):
 
188
 
        super(self, MandosClientWidget).property_changed(property,
 
190
 
        if property == "ApprovalPending":
 
191
 
            using_timer(bool(value))
 
193
169
    def using_timer(self, flag):
 
194
170
        """Call this method with True or False when timer should be
 
195
171
        activated or deactivated.
 
197
 
        old = self._update_timer_callback_lock
 
199
 
            self._update_timer_callback_lock += 1
 
201
 
            self._update_timer_callback_lock -= 1
 
202
 
        if old == 0 and self._update_timer_callback_lock:
 
 
173
        if flag and self._update_timer_callback_tag is None:
 
203
174
            # Will update the shown timer value every second
 
204
175
            self._update_timer_callback_tag = (gobject.timeout_add
 
206
177
                                                self.update_timer))
 
207
 
        elif old and self._update_timer_callback_lock == 0:
 
 
178
        elif not (flag or self._update_timer_callback_tag is None):
 
208
179
            gobject.source_remove(self._update_timer_callback_tag)
 
209
180
            self._update_timer_callback_tag = None
 
211
182
    def checker_completed(self, exitstatus, condition, command):
 
212
183
        if exitstatus == 0:
 
213
 
            if self.last_checker_failed:
 
214
 
                self.last_checker_failed = False
 
215
 
                self.using_timer(False)
 
216
 
            #self.logger('Checker for client %s (command "%s")'
 
218
 
            #            % (self.properties["Name"], command))
 
 
184
            self.logger('Checker for client {} (command "{}")'
 
 
185
                        ' succeeded'.format(self.properties["Name"],
 
222
 
        if not self.last_checker_failed:
 
223
 
            self.last_checker_failed = True
 
224
 
            self.using_timer(True)
 
225
190
        if os.WIFEXITED(condition):
 
226
 
            self.logger('Checker for client %s (command "%s")'
 
227
 
                        ' failed with exit code %s'
 
228
 
                        % (self.properties["Name"], command,
 
229
 
                           os.WEXITSTATUS(condition)))
 
 
191
            self.logger('Checker for client {} (command "{}") failed'
 
 
193
                        .format(self.properties["Name"], command,
 
 
194
                                os.WEXITSTATUS(condition)))
 
230
195
        elif os.WIFSIGNALED(condition):
 
231
 
            self.logger('Checker for client %s (command "%s")'
 
232
 
                        ' was killed by signal %s'
 
233
 
                        % (self.properties["Name"], command,
 
234
 
                           os.WTERMSIG(condition)))
 
235
 
        elif os.WCOREDUMP(condition):
 
236
 
            self.logger('Checker for client %s (command "%s")'
 
238
 
                        % (self.properties["Name"], command))
 
240
 
            self.logger('Checker for client %s completed'
 
 
196
            self.logger('Checker for client {} (command "{}") was'
 
 
197
                        ' killed by signal {}'
 
 
198
                        .format(self.properties["Name"], command,
 
 
199
                                os.WTERMSIG(condition)))
 
244
202
    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)))
 
 
203
        """Server signals that a checker started."""
 
 
204
        self.logger('Client {} started checker "{}"'
 
 
205
                    .format(self.properties["Name"],
 
251
208
    def got_secret(self):
 
252
 
        self.last_checker_failed = False
 
253
 
        self.logger('Client %s received its secret'
 
254
 
                    % self.properties["Name"])
 
 
209
        self.logger('Client {} received its secret'
 
 
210
                    .format(self.properties["Name"]))
 
256
212
    def need_approval(self, timeout, default):
 
258
 
            message = 'Client %s needs approval within %s seconds'
 
 
214
            message = 'Client {} needs approval within {} seconds'
 
260
 
            message = 'Client %s will get its secret in %s seconds'
 
262
 
                    % (self.properties["Name"], timeout/1000))
 
263
 
        self.using_timer(True)
 
 
216
            message = 'Client {} will get its secret in {} seconds'
 
 
217
        self.logger(message.format(self.properties["Name"],
 
265
220
    def rejected(self, reason):
 
266
 
        self.logger('Client %s was rejected; reason: %s'
 
267
 
                    % (self.properties["Name"], reason))
 
 
221
        self.logger('Client {} was rejected; reason: {}'
 
 
222
                    .format(self.properties["Name"], reason))
 
269
224
    def selectable(self):
 
270
225
        """Make this a "selectable" widget.
 
 
307
262
            last_approval_request = isoformat_to_datetime(
 
308
263
                self.properties["LastApprovalRequest"])
 
309
264
            if last_approval_request is not None:
 
310
 
                timer = timeout - (datetime.datetime.utcnow()
 
311
 
                                   - last_approval_request)
 
 
265
                timer = max(timeout - (datetime.datetime.utcnow()
 
 
266
                                       - last_approval_request),
 
 
267
                            datetime.timedelta())
 
313
269
                timer = datetime.timedelta()
 
314
270
            if self.properties["ApprovedByDefault"]:
 
315
 
                message = "Approval in %s. (d)eny?"
 
 
271
                message = "Approval in {}. (d)eny?"
 
317
 
                message = "Denial in %s. (a)pprove?"
 
318
 
            message = message % unicode(timer).rsplit(".", 1)[0]
 
319
 
        elif self.last_checker_failed:
 
320
 
            # When checker has failed, print a timer until client expires
 
 
273
                message = "Denial in {}. (a)pprove?"
 
 
274
            message = message.format(str(timer).rsplit(".", 1)[0])
 
 
275
            self.using_timer(True)
 
 
276
        elif self.properties["LastCheckerStatus"] != 0:
 
 
277
            # When checker has failed, show timer until client expires
 
321
278
            expires = self.properties["Expires"]
 
322
279
            if expires == "":
 
323
280
                timer = datetime.timedelta(0)
 
325
 
                expires = datetime.datetime.strptime(expires,
 
326
 
                                                     '%Y-%m-%dT%H:%M:%S.%f')
 
327
 
                timer = expires - datetime.datetime.utcnow()
 
 
282
                expires = (datetime.datetime.strptime
 
 
283
                           (expires, '%Y-%m-%dT%H:%M:%S.%f'))
 
 
284
                timer = max(expires - datetime.datetime.utcnow(),
 
 
285
                            datetime.timedelta())
 
328
286
            message = ('A checker has failed! Time until client'
 
330
 
                           % unicode(timer).rsplit(".", 1)[0])
 
 
288
                       .format(str(timer).rsplit(".", 1)[0]))
 
 
289
            self.using_timer(True)
 
332
291
            message = "enabled"
 
333
 
        self._text = "%s%s" % (base, message)
 
 
292
            self.using_timer(False)
 
 
293
        self._text = "{}{}".format(base, message)
 
335
295
        if not urwid.supports_unicode():
 
336
296
            self._text = self._text.encode("ascii", "replace")
 
337
297
        textlist = [("normal", self._text)]
 
 
376
336
        This overrides the method from urwid.FlowWidget"""
 
378
 
            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)
 
381
 
            self.proxy.Disable(dbus_interface = client_interface,
 
 
342
            self.proxy.Set(client_interface, "Enabled", False,
 
 
344
                           dbus_interface = dbus.PROPERTIES_IFACE)
 
384
346
            self.proxy.Approve(dbus.Boolean(True, variant_level=1),
 
385
347
                               dbus_interface = client_interface,
 
 
414
 
    def property_changed(self, property=None, value=None,
 
416
 
        """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.
 
417
380
        This overrides the method from MandosClientPropertyCache"""
 
418
 
        property_name = unicode(property)
 
419
 
        old_value = self.properties.get(property_name)
 
420
 
        super(MandosClientWidget, self).property_changed(
 
421
 
            property=property, value=value, *args, **kwargs)
 
422
 
        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):
 
 
449
413
                 "default", "default", None),
 
451
 
                 "default", "default", "bold"),
 
 
415
                 "bold", "default", "bold"),
 
452
416
                ("underline-blink",
 
453
 
                 "default", "default", "underline"),
 
 
417
                 "underline,blink", "default", "underline,blink"),
 
455
 
                 "default", "default", "standout"),
 
 
419
                 "standout", "default", "standout"),
 
456
420
                ("bold-underline-blink",
 
457
 
                 "default", "default", ("bold", "underline")),
 
 
421
                 "bold,underline,blink", "default", "bold,underline,blink"),
 
458
422
                ("bold-standout",
 
459
 
                 "default", "default", ("bold", "standout")),
 
 
423
                 "bold,standout", "default", "bold,standout"),
 
460
424
                ("underline-blink-standout",
 
461
 
                 "default", "default", ("underline", "standout")),
 
 
425
                 "underline,blink,standout", "default",
 
 
426
                 "underline,blink,standout"),
 
462
427
                ("bold-underline-blink-standout",
 
463
 
                 "default", "default", ("bold", "underline",
 
 
428
                 "bold,underline,blink,standout", "default",
 
 
429
                 "bold,underline,blink,standout"),
 
467
432
        if urwid.supports_unicode():
 
 
521
488
            self.uilist.append(self.logbox)
 
522
489
        self.topwidget = urwid.Pile(self.uilist)
 
524
 
    def log_message(self, message):
 
 
491
    def log_message(self, message, level=1):
 
 
492
        """Log message formatted with timestamp"""
 
 
493
        if level < self.log_level:
 
525
495
        timestamp = datetime.datetime.now().isoformat()
 
526
 
        self.log_message_raw(timestamp + ": " + message)
 
 
496
        self.log_message_raw("{}: {}".format(timestamp, message),
 
528
 
    def log_message_raw(self, markup):
 
 
499
    def log_message_raw(self, markup, level=1):
 
529
500
        """Add a log message to the log buffer."""
 
 
501
        if level < self.log_level:
 
530
503
        self.log.append(urwid.Text(markup, wrap=self.log_wrap))
 
531
504
        if (self.max_log_length
 
532
505
            and len(self.log) > self.max_log_length):
 
 
551
524
            self.log_wrap = "clip"
 
552
525
        for textwidget in self.log:
 
553
526
            textwidget.set_wrap_mode(self.log_wrap)
 
554
 
        #self.log_message("Wrap mode: " + self.log_wrap)
 
 
527
        self.log_message("Wrap mode: {}".format(self.log_wrap),
 
556
 
    def find_and_remove_client(self, path, name):
 
 
530
    def find_and_remove_client(self, path, interfaces):
 
557
531
        """Find a client by its object path and remove it.
 
559
 
        This is connected to the ClientRemoved signal from the
 
 
533
        This is connected to the InterfacesRemoved signal from the
 
560
534
        Mandos server object."""
 
 
535
        if client_interface not in interfaces:
 
 
536
            # Not a Mandos client object; ignore
 
562
539
            client = self.clients_dict[path]
 
565
 
            self.log_message("Unknown client %r (%r) removed", name,
 
 
542
            self.log_message("Unknown client {!r} removed"
 
570
 
    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
 
571
556
        client_proxy_object = self.bus.get_object(self.busname, path)
 
572
557
        self.add_client(MandosClientWidget(server_proxy_object
 
573
558
                                           =self.mandos_serv,
 
 
618
601
            mandos_clients = (self.mandos_serv
 
619
602
                              .GetAllClientsWithProperties())
 
 
603
            if not mandos_clients:
 
 
604
                self.log_message_raw(("bold", "Note: Server has no clients."))
 
620
605
        except dbus.exceptions.DBusException:
 
 
606
            self.log_message_raw(("bold", "Note: No Mandos server running."))
 
621
607
            mandos_clients = dbus.Dictionary()
 
623
609
        (self.mandos_serv
 
624
 
         .connect_to_signal("ClientRemoved",
 
 
610
         .connect_to_signal("InterfacesRemoved",
 
625
611
                            self.find_and_remove_client,
 
626
 
                            dbus_interface=server_interface,
 
 
613
                            = dbus.OBJECT_MANAGER_IFACE,
 
627
614
                            byte_arrays=True))
 
628
615
        (self.mandos_serv
 
629
 
         .connect_to_signal("ClientAdded",
 
 
616
         .connect_to_signal("InterfacesAdded",
 
630
617
                            self.add_new_client,
 
631
 
                            dbus_interface=server_interface,
 
 
619
                            = dbus.OBJECT_MANAGER_IFACE,
 
632
620
                            byte_arrays=True))
 
633
621
        (self.mandos_serv
 
634
622
         .connect_to_signal("ClientNotFound",
 
635
623
                            self.client_not_found,
 
636
624
                            dbus_interface=server_interface,
 
637
625
                            byte_arrays=True))
 
638
 
        for path, client in mandos_clients.iteritems():
 
 
626
        for path, client in mandos_clients.items():
 
639
627
            client_proxy_object = self.bus.get_object(self.busname,
 
641
629
            self.add_client(MandosClientWidget(server_proxy_object