85
82
    properties and calls a hook function when any of them are
 
88
 
    def __init__(self, proxy_object=None, properties=None, **kwargs):
 
 
85
    def __init__(self, proxy_object=None, *args, **kwargs):
 
89
86
        self.proxy = proxy_object # Mandos Client proxy object
 
90
 
        self.properties = dict() if properties is None else properties
 
91
 
        self.property_changed_match = (
 
92
 
            self.proxy.connect_to_signal("PropertiesChanged",
 
93
 
                                         self.properties_changed,
 
94
 
                                         dbus.PROPERTIES_IFACE,
 
97
 
        if properties is None:
 
98
 
            self.properties.update(
 
99
 
                self.proxy.GetAll(client_interface,
 
101
 
                                  = dbus.PROPERTIES_IFACE))
 
103
 
        super(MandosClientPropertyCache, self).__init__(**kwargs)
 
 
88
        self.properties = dict()
 
 
89
        self.proxy.connect_to_signal("PropertyChanged",
 
 
90
                                     self.property_changed,
 
 
94
        self.properties.update(
 
 
95
            self.proxy.GetAll(client_interface,
 
 
96
                              dbus_interface = dbus.PROPERTIES_IFACE))
 
 
98
        #XXX This break good super behaviour!
 
 
99
#        super(MandosClientPropertyCache, self).__init__(
 
105
 
    def properties_changed(self, interface, properties, invalidated):
 
106
 
        """This is called whenever we get a PropertiesChanged signal
 
107
 
        It updates the changed properties in the "properties" dict.
 
 
102
    def property_changed(self, property=None, value=None):
 
 
103
        """This is called whenever we get a PropertyChanged signal
 
 
104
        It updates the changed property in the "properties" dict.
 
109
106
        # Update properties dict with new value
 
110
 
        if interface == client_interface:
 
111
 
            self.properties.update(properties)
 
114
 
        self.property_changed_match.remove()
 
 
107
        self.properties[property] = value
 
117
110
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
 
 
130
123
        self.logger = logger
 
132
125
        self._update_timer_callback_tag = None
 
 
126
        self._update_timer_callback_lock = 0
 
 
127
        self.last_checker_failed = False
 
134
129
        # The widget shown normally
 
135
130
        self._text_widget = urwid.Text("")
 
136
131
        # The widget shown when we have focus
 
137
132
        self._focus_text_widget = urwid.Text("")
 
138
 
        super(MandosClientWidget, self).__init__(**kwargs)
 
 
133
        super(MandosClientWidget, self).__init__(
 
 
134
            update_hook=update_hook, delete_hook=delete_hook,
 
140
137
        self.opened = False
 
142
 
        self.match_objects = (
 
143
 
            self.proxy.connect_to_signal("CheckerCompleted",
 
144
 
                                         self.checker_completed,
 
147
 
            self.proxy.connect_to_signal("CheckerStarted",
 
148
 
                                         self.checker_started,
 
151
 
            self.proxy.connect_to_signal("GotSecret",
 
155
 
            self.proxy.connect_to_signal("NeedApproval",
 
159
 
            self.proxy.connect_to_signal("Rejected",
 
163
 
        self.logger('Created client {}'
 
164
 
                    .format(self.properties["Name"]), level=0)
 
 
139
        last_checked_ok = isoformat_to_datetime(self.properties
 
 
141
        if last_checked_ok is None:
 
 
142
            self.last_checker_failed = True
 
 
144
            self.last_checker_failed = ((datetime.datetime.utcnow()
 
 
151
        if self.last_checker_failed:
 
 
152
            self.using_timer(True)
 
 
154
        if self.need_approval:
 
 
155
            self.using_timer(True)
 
 
157
        self.proxy.connect_to_signal("CheckerCompleted",
 
 
158
                                     self.checker_completed,
 
 
161
        self.proxy.connect_to_signal("CheckerStarted",
 
 
162
                                     self.checker_started,
 
 
165
        self.proxy.connect_to_signal("GotSecret",
 
 
169
        self.proxy.connect_to_signal("NeedApproval",
 
 
173
        self.proxy.connect_to_signal("Rejected",
 
 
178
    def property_changed(self, property=None, value=None):
 
 
179
        super(self, MandosClientWidget).property_changed(property,
 
 
181
        if property == "ApprovalPending":
 
 
182
            using_timer(bool(value))
 
166
184
    def using_timer(self, flag):
 
167
185
        """Call this method with True or False when timer should be
 
168
186
        activated or deactivated.
 
170
 
        if flag and self._update_timer_callback_tag is None:
 
171
 
            # Will update the shown timer value every second
 
172
 
            self._update_timer_callback_tag = (GLib.timeout_add
 
 
188
        old = self._update_timer_callback_lock
 
 
190
            self._update_timer_callback_lock += 1
 
 
192
            self._update_timer_callback_lock -= 1
 
 
193
        if old == 0 and self._update_timer_callback_lock:
 
 
194
            self._update_timer_callback_tag = (gobject.timeout_add
 
174
196
                                                self.update_timer))
 
175
 
        elif not (flag or self._update_timer_callback_tag is None):
 
176
 
            GLib.source_remove(self._update_timer_callback_tag)
 
 
197
        elif old and self._update_timer_callback_lock == 0:
 
 
198
            gobject.source_remove(self._update_timer_callback_tag)
 
177
199
            self._update_timer_callback_tag = None
 
179
201
    def checker_completed(self, exitstatus, condition, command):
 
180
202
        if exitstatus == 0:
 
181
 
            self.logger('Checker for client {} (command "{}")'
 
182
 
                        ' succeeded'.format(self.properties["Name"],
 
 
203
            if self.last_checker_failed:
 
 
204
                self.last_checker_failed = False
 
 
205
                self.using_timer(False)
 
 
206
            #self.logger('Checker for client %s (command "%s")'
 
 
208
            #            % (self.properties["Name"], command))
 
 
212
        if not self.last_checker_failed:
 
 
213
            self.last_checker_failed = True
 
 
214
            self.using_timer(True)
 
187
215
        if os.WIFEXITED(condition):
 
188
 
            self.logger('Checker for client {} (command "{}") failed'
 
190
 
                        .format(self.properties["Name"], command,
 
191
 
                                os.WEXITSTATUS(condition)))
 
 
216
            self.logger('Checker for client %s (command "%s")'
 
 
217
                        ' failed with exit code %s'
 
 
218
                        % (self.properties["Name"], command,
 
 
219
                           os.WEXITSTATUS(condition)))
 
192
220
        elif os.WIFSIGNALED(condition):
 
193
 
            self.logger('Checker for client {} (command "{}") was'
 
194
 
                        ' killed by signal {}'
 
195
 
                        .format(self.properties["Name"], command,
 
196
 
                                os.WTERMSIG(condition)))
 
 
221
            self.logger('Checker for client %s (command "%s")'
 
 
222
                        ' was killed by signal %s'
 
 
223
                        % (self.properties["Name"], command,
 
 
224
                           os.WTERMSIG(condition)))
 
 
225
        elif os.WCOREDUMP(condition):
 
 
226
            self.logger('Checker for client %s (command "%s")'
 
 
228
                        % (self.properties["Name"], command))
 
 
230
            self.logger('Checker for client %s completed'
 
199
234
    def checker_started(self, command):
 
200
 
        """Server signals that a checker started."""
 
201
 
        self.logger('Client {} started checker "{}"'
 
202
 
                    .format(self.properties["Name"],
 
 
235
        #self.logger('Client %s started checker "%s"'
 
 
236
        #            % (self.properties["Name"], unicode(command)))
 
205
239
    def got_secret(self):
 
206
 
        self.logger('Client {} received its secret'
 
207
 
                    .format(self.properties["Name"]))
 
 
240
        self.last_checker_failed = False
 
 
241
        self.logger('Client %s received its secret'
 
 
242
                    % self.properties["Name"])
 
209
244
    def need_approval(self, timeout, default):
 
211
 
            message = 'Client {} needs approval within {} seconds'
 
 
246
            message = 'Client %s needs approval within %s seconds'
 
213
 
            message = 'Client {} will get its secret in {} seconds'
 
214
 
        self.logger(message.format(self.properties["Name"],
 
 
248
            message = 'Client %s will get its secret in %s seconds'
 
 
250
                    % (self.properties["Name"], timeout/1000))
 
 
251
        self.using_timer(True)
 
217
253
    def rejected(self, reason):
 
218
 
        self.logger('Client {} was rejected; reason: {}'
 
219
 
                    .format(self.properties["Name"], reason))
 
 
254
        self.logger('Client %s was rejected; reason: %s'
 
 
255
                    % (self.properties["Name"], reason))
 
221
257
    def selectable(self):
 
222
258
        """Make this a "selectable" widget.
 
 
259
295
            last_approval_request = isoformat_to_datetime(
 
260
296
                self.properties["LastApprovalRequest"])
 
261
297
            if last_approval_request is not None:
 
262
 
                timer = max(timeout - (datetime.datetime.utcnow()
 
263
 
                                       - last_approval_request),
 
264
 
                            datetime.timedelta())
 
 
298
                timer = timeout - (datetime.datetime.utcnow()
 
 
299
                                   - last_approval_request)
 
266
301
                timer = datetime.timedelta()
 
267
302
            if self.properties["ApprovedByDefault"]:
 
268
 
                message = "Approval in {}. (d)eny?"
 
270
 
                message = "Denial in {}. (a)pprove?"
 
271
 
            message = message.format(str(timer).rsplit(".", 1)[0])
 
272
 
            self.using_timer(True)
 
273
 
        elif self.properties["LastCheckerStatus"] != 0:
 
274
 
            # When checker has failed, show timer until client expires
 
275
 
            expires = self.properties["Expires"]
 
277
 
                timer = datetime.timedelta(0)
 
279
 
                expires = (datetime.datetime.strptime
 
280
 
                           (expires, '%Y-%m-%dT%H:%M:%S.%f'))
 
281
 
                timer = max(expires - datetime.datetime.utcnow(),
 
282
 
                            datetime.timedelta())
 
 
303
                message = "Approval in %s. (d)eny?"
 
 
305
                message = "Denial in %s. (a)pprove?"
 
 
306
            message = message % unicode(timer).rsplit(".", 1)[0]
 
 
307
        elif self.last_checker_failed:
 
 
308
            timeout = datetime.timedelta(milliseconds
 
 
311
            last_ok = isoformat_to_datetime(
 
 
312
                max((self.properties["LastCheckedOK"]
 
 
313
                     or self.properties["Created"]),
 
 
314
                    self.properties["LastEnabled"]))
 
 
315
            timer = timeout - (datetime.datetime.utcnow() - last_ok)
 
283
316
            message = ('A checker has failed! Time until client'
 
285
 
                       .format(str(timer).rsplit(".", 1)[0]))
 
286
 
            self.using_timer(True)
 
 
318
                           % unicode(timer).rsplit(".", 1)[0])
 
288
320
            message = "enabled"
 
289
 
            self.using_timer(False)
 
290
 
        self._text = "{}{}".format(base, message)
 
 
321
        self._text = "%s%s" % (base, message)
 
292
323
        if not urwid.supports_unicode():
 
293
324
            self._text = self._text.encode("ascii", "replace")
 
294
325
        textlist = [("normal", self._text)]
 
 
376
 
    def properties_changed(self, interface, properties, invalidated):
 
377
 
        """Call self.update() if any properties changed.
 
 
397
    def property_changed(self, property=None, value=None,
 
 
399
        """Call self.update() if old value is not new value.
 
378
400
        This overrides the method from MandosClientPropertyCache"""
 
379
 
        old_values = { key: self.properties.get(key)
 
380
 
                       for key in properties.keys() }
 
381
 
        super(MandosClientWidget, self).properties_changed(
 
382
 
            interface, properties, invalidated)
 
383
 
        if any(old_values[key] != self.properties.get(key)
 
384
 
               for key in old_values):
 
 
401
        property_name = unicode(property)
 
 
402
        old_value = self.properties.get(property_name)
 
 
403
        super(MandosClientWidget, self).property_changed(
 
 
404
            property=property, value=value, *args, **kwargs)
 
 
405
        if self.properties.get(property_name) != old_value:
 
 
411
432
                 "default", "default", None),
 
413
 
                 "bold", "default", "bold"),
 
 
434
                 "default", "default", "bold"),
 
414
435
                ("underline-blink",
 
415
 
                 "underline,blink", "default", "underline,blink"),
 
 
436
                 "default", "default", "underline"),
 
417
 
                 "standout", "default", "standout"),
 
 
438
                 "default", "default", "standout"),
 
418
439
                ("bold-underline-blink",
 
419
 
                 "bold,underline,blink", "default", "bold,underline,blink"),
 
 
440
                 "default", "default", ("bold", "underline")),
 
420
441
                ("bold-standout",
 
421
 
                 "bold,standout", "default", "bold,standout"),
 
 
442
                 "default", "default", ("bold", "standout")),
 
422
443
                ("underline-blink-standout",
 
423
 
                 "underline,blink,standout", "default",
 
424
 
                 "underline,blink,standout"),
 
 
444
                 "default", "default", ("underline", "standout")),
 
425
445
                ("bold-underline-blink-standout",
 
426
 
                 "bold,underline,blink,standout", "default",
 
427
 
                 "bold,underline,blink,standout"),
 
 
446
                 "default", "default", ("bold", "underline",
 
430
450
        if urwid.supports_unicode():
 
 
463
481
                              "q: Quit  ?: Help"))
 
465
483
        self.busname = domain + '.Mandos'
 
466
 
        self.main_loop = GLib.MainLoop()
 
 
484
        self.main_loop = gobject.MainLoop()
 
 
485
        self.bus = dbus.SystemBus()
 
 
486
        mandos_dbus_objc = self.bus.get_object(
 
 
487
            self.busname, "/", follow_name_owner_changes=True)
 
 
488
        self.mandos_serv = dbus.Interface(mandos_dbus_objc,
 
 
492
            mandos_clients = (self.mandos_serv
 
 
493
                              .GetAllClientsWithProperties())
 
 
494
        except dbus.exceptions.DBusException:
 
 
495
            mandos_clients = dbus.Dictionary()
 
 
498
         .connect_to_signal("ClientRemoved",
 
 
499
                            self.find_and_remove_client,
 
 
500
                            dbus_interface=server_interface,
 
 
503
         .connect_to_signal("ClientAdded",
 
 
505
                            dbus_interface=server_interface,
 
 
508
         .connect_to_signal("ClientNotFound",
 
 
509
                            self.client_not_found,
 
 
510
                            dbus_interface=server_interface,
 
 
512
        for path, client in mandos_clients.iteritems():
 
 
513
            client_proxy_object = self.bus.get_object(self.busname,
 
 
515
            self.add_client(MandosClientWidget(server_proxy_object
 
 
518
                                               =client_proxy_object,
 
468
528
    def client_not_found(self, fingerprint, address):
 
469
 
        self.log_message("Client with address {} and fingerprint {}"
 
470
 
                         " could not be found"
 
471
 
                         .format(address, fingerprint))
 
 
529
        self.log_message(("Client with address %s and fingerprint %s"
 
 
530
                          " could not be found" % (address,
 
473
533
    def rebuild(self):
 
474
534
        """This rebuilds the User Interface.
 
 
522
577
            self.log_wrap = "clip"
 
523
578
        for textwidget in self.log:
 
524
579
            textwidget.set_wrap_mode(self.log_wrap)
 
525
 
        self.log_message("Wrap mode: {}".format(self.log_wrap),
 
 
580
        #self.log_message("Wrap mode: " + self.log_wrap)
 
528
 
    def find_and_remove_client(self, path, interfaces):
 
529
 
        """Find a client by its object path and remove it.
 
 
582
    def find_and_remove_client(self, path, name):
 
 
583
        """Find an client from its object path and remove it.
 
531
 
        This is connected to the InterfacesRemoved signal from the
 
 
585
        This is connected to the ClientRemoved signal from the
 
532
586
        Mandos server object."""
 
533
 
        if client_interface not in interfaces:
 
534
 
            # Not a Mandos client object; ignore
 
537
588
            client = self.clients_dict[path]
 
540
 
            self.log_message("Unknown client {!r} removed"
 
 
592
        self.remove_client(client, path)
 
545
 
    def add_new_client(self, path, ifs_and_props):
 
546
 
        """Find a client by its object path and remove it.
 
548
 
        This is connected to the InterfacesAdded signal from the
 
549
 
        Mandos server object.
 
551
 
        if client_interface not in ifs_and_props:
 
552
 
            # Not a Mandos client object; ignore
 
 
594
    def add_new_client(self, path):
 
554
595
        client_proxy_object = self.bus.get_object(self.busname, path)
 
555
596
        self.add_client(MandosClientWidget(server_proxy_object
 
556
597
                                           =self.mandos_serv,
 
 
591
634
        """Start the main loop and exit when it's done."""
 
592
 
        self.bus = dbus.SystemBus()
 
593
 
        mandos_dbus_objc = self.bus.get_object(
 
594
 
            self.busname, "/", follow_name_owner_changes=True)
 
595
 
        self.mandos_serv = dbus.Interface(mandos_dbus_objc,
 
599
 
            mandos_clients = (self.mandos_serv
 
600
 
                              .GetAllClientsWithProperties())
 
601
 
            if not mandos_clients:
 
602
 
                self.log_message_raw(("bold", "Note: Server has no clients."))
 
603
 
        except dbus.exceptions.DBusException:
 
604
 
            self.log_message_raw(("bold", "Note: No Mandos server running."))
 
605
 
            mandos_clients = dbus.Dictionary()
 
608
 
         .connect_to_signal("InterfacesRemoved",
 
609
 
                            self.find_and_remove_client,
 
611
 
                            = dbus.OBJECT_MANAGER_IFACE,
 
614
 
         .connect_to_signal("InterfacesAdded",
 
617
 
                            = dbus.OBJECT_MANAGER_IFACE,
 
620
 
         .connect_to_signal("ClientNotFound",
 
621
 
                            self.client_not_found,
 
622
 
                            dbus_interface=server_interface,
 
624
 
        for path, client in mandos_clients.items():
 
625
 
            client_proxy_object = self.bus.get_object(self.busname,
 
627
 
            self.add_client(MandosClientWidget(server_proxy_object
 
630
 
                                               =client_proxy_object,
 
641
 
        self._input_callback_tag = (GLib.io_add_watch
 
 
636
        self._input_callback_tag = (gobject.io_add_watch
 
642
637
                                    (sys.stdin.fileno(),
 
644
639
                                     self.process_input))
 
645
640
        self.main_loop.run()
 
646
641
        # Main loop has finished, we should close everything now
 
647
 
        GLib.source_remove(self._input_callback_tag)
 
 
642
        gobject.source_remove(self._input_callback_tag)
 
648
643
        self.screen.stop()