/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to mandos-monitor

  • Committer: Teddy Hogeborn
  • Date: 2012-01-01 20:45:53 UTC
  • Revision ID: teddy@recompile.se-20120101204553-f1zlty873gswtf5n
* README: Hint that the intro(8mandos) manual page is in the server
          package.

Show diffs side-by-side

added added

removed removed

Lines of Context:
3
3
4
4
# Mandos Monitor - Control and monitor the Mandos server
5
5
6
 
# Copyright © 2009,2010 Teddy Hogeborn
7
 
# Copyright © 2009,2010 Björn Påhlsson
 
6
# Copyright © 2009-2012 Teddy Hogeborn
 
7
# Copyright © 2009-2012 Björn Påhlsson
8
8
9
9
# This program is free software: you can redistribute it and/or modify
10
10
# it under the terms of the GNU General Public License as published by
19
19
# You should have received a copy of the GNU General Public License
20
20
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
21
22
 
# Contact the authors at <mandos@fukt.bsnet.se>.
 
22
# Contact the authors at <mandos@recompile.se>.
23
23
24
24
 
25
 
from __future__ import division, absolute_import, with_statement
 
25
from __future__ import (division, absolute_import, print_function,
 
26
                        unicode_literals)
26
27
 
27
28
import sys
28
29
import os
42
43
 
43
44
import locale
44
45
 
45
 
locale.setlocale(locale.LC_ALL, u'')
 
46
locale.setlocale(locale.LC_ALL, '')
46
47
 
47
48
import logging
48
 
logging.getLogger(u'dbus.proxies').setLevel(logging.CRITICAL)
 
49
logging.getLogger('dbus.proxies').setLevel(logging.CRITICAL)
49
50
 
50
51
# Some useful constants
51
 
domain = u'se.bsnet.fukt'
52
 
server_interface = domain + u'.Mandos'
53
 
client_interface = domain + u'.Mandos.Client'
54
 
version = u"1.2.3"
 
52
domain = 'se.recompile'
 
53
server_interface = domain + '.Mandos'
 
54
client_interface = domain + '.Mandos.Client'
 
55
version = "1.5.0"
55
56
 
56
57
# Always run in monochrome mode
57
58
urwid.curses_display.curses.has_colors = lambda : False
65
66
    "Parse an ISO 8601 date string to a datetime.datetime()"
66
67
    if not iso:
67
68
        return None
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),
73
74
                             int(month),
86
87
        self.proxy = proxy_object # Mandos Client proxy object
87
88
        
88
89
        self.properties = dict()
89
 
        self.proxy.connect_to_signal(u"PropertyChanged",
90
 
                                     self.property_changed,
91
 
                                     client_interface,
92
 
                                     byte_arrays=True)
 
90
        self.property_changed_match = (
 
91
            self.proxy.connect_to_signal("PropertyChanged",
 
92
                                         self.property_changed,
 
93
                                         client_interface,
 
94
                                         byte_arrays=True))
93
95
        
94
96
        self.properties.update(
95
97
            self.proxy.GetAll(client_interface,
96
98
                              dbus_interface = dbus.PROPERTIES_IFACE))
97
99
 
98
 
        #XXX This break good super behaviour!
 
100
        #XXX This breaks good super behaviour
99
101
#        super(MandosClientPropertyCache, self).__init__(
100
102
#            *args, **kwargs)
101
103
    
105
107
        """
106
108
        # Update properties dict with new value
107
109
        self.properties[property] = value
 
110
    
 
111
    def delete(self, *args, **kwargs):
 
112
        self.property_changed_match.remove()
 
113
        super(MandosClientPropertyCache, self).__init__(
 
114
            *args, **kwargs)
108
115
 
109
116
 
110
117
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
127
134
        self.last_checker_failed = False
128
135
        
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,
135
142
            *args, **kwargs)
137
144
        self.opened = False
138
145
        
139
146
        last_checked_ok = isoformat_to_datetime(self.properties
140
 
                                                [u"LastCheckedOK"])
 
147
                                                ["LastCheckedOK"])
141
148
        if last_checked_ok is None:
142
149
            self.last_checker_failed = True
143
150
        else:
146
153
                                        > datetime.timedelta
147
154
                                        (milliseconds=
148
155
                                         self.properties
149
 
                                         [u"Interval"]))
 
156
                                         ["Interval"]))
150
157
        
151
158
        if self.last_checker_failed:
152
159
            self.using_timer(True)
154
161
        if self.need_approval:
155
162
            self.using_timer(True)
156
163
        
157
 
        self.proxy.connect_to_signal(u"CheckerCompleted",
158
 
                                     self.checker_completed,
159
 
                                     client_interface,
160
 
                                     byte_arrays=True)
161
 
        self.proxy.connect_to_signal(u"CheckerStarted",
162
 
                                     self.checker_started,
163
 
                                     client_interface,
164
 
                                     byte_arrays=True)
165
 
        self.proxy.connect_to_signal(u"GotSecret",
166
 
                                     self.got_secret,
167
 
                                     client_interface,
168
 
                                     byte_arrays=True)
169
 
        self.proxy.connect_to_signal(u"NeedApproval",
170
 
                                     self.need_approval,
171
 
                                     client_interface,
172
 
                                     byte_arrays=True)
173
 
        self.proxy.connect_to_signal(u"Rejected",
174
 
                                     self.rejected,
175
 
                                     client_interface,
176
 
                                     byte_arrays=True)
 
164
        self.match_objects = (
 
165
            self.proxy.connect_to_signal("CheckerCompleted",
 
166
                                         self.checker_completed,
 
167
                                         client_interface,
 
168
                                         byte_arrays=True),
 
169
            self.proxy.connect_to_signal("CheckerStarted",
 
170
                                         self.checker_started,
 
171
                                         client_interface,
 
172
                                         byte_arrays=True),
 
173
            self.proxy.connect_to_signal("GotSecret",
 
174
                                         self.got_secret,
 
175
                                         client_interface,
 
176
                                         byte_arrays=True),
 
177
            self.proxy.connect_to_signal("NeedApproval",
 
178
                                         self.need_approval,
 
179
                                         client_interface,
 
180
                                         byte_arrays=True),
 
181
            self.proxy.connect_to_signal("Rejected",
 
182
                                         self.rejected,
 
183
                                         client_interface,
 
184
                                         byte_arrays=True))
 
185
        #self.logger('Created client %s' % (self.properties["Name"]))
177
186
    
178
187
    def property_changed(self, property=None, value=None):
179
188
        super(self, MandosClientWidget).property_changed(property,
180
189
                                                         value)
181
 
        if property == u"ApprovalPending":
 
190
        if property == "ApprovalPending":
182
191
            using_timer(bool(value))
183
192
        
184
193
    def using_timer(self, flag):
191
200
        else:
192
201
            self._update_timer_callback_lock -= 1
193
202
        if old == 0 and self._update_timer_callback_lock:
 
203
            # Will update the shown timer value every second
194
204
            self._update_timer_callback_tag = (gobject.timeout_add
195
205
                                               (1000,
196
206
                                                self.update_timer))
203
213
            if self.last_checker_failed:
204
214
                self.last_checker_failed = False
205
215
                self.using_timer(False)
206
 
            #self.logger(u'Checker for client %s (command "%s")'
207
 
            #            u' was successful'
208
 
            #            % (self.properties[u"Name"], command))
 
216
            #self.logger('Checker for client %s (command "%s")'
 
217
            #            ' was successful'
 
218
            #            % (self.properties["Name"], command))
209
219
            self.update()
210
220
            return
211
221
        # Checker failed
213
223
            self.last_checker_failed = True
214
224
            self.using_timer(True)
215
225
        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,
 
226
            self.logger('Checker for client %s (command "%s")'
 
227
                        ' failed with exit code %s'
 
228
                        % (self.properties["Name"], command,
219
229
                           os.WEXITSTATUS(condition)))
220
230
        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,
 
231
            self.logger('Checker for client %s (command "%s")'
 
232
                        ' was killed by signal %s'
 
233
                        % (self.properties["Name"], command,
224
234
                           os.WTERMSIG(condition)))
225
235
        elif os.WCOREDUMP(condition):
226
 
            self.logger(u'Checker for client %s (command "%s")'
227
 
                        u' dumped core'
228
 
                        % (self.properties[u"Name"], command))
 
236
            self.logger('Checker for client %s (command "%s")'
 
237
                        ' dumped core'
 
238
                        % (self.properties["Name"], command))
229
239
        else:
230
 
            self.logger(u'Checker for client %s completed'
231
 
                        u' mysteriously')
 
240
            self.logger('Checker for client %s completed'
 
241
                        ' mysteriously')
232
242
        self.update()
233
243
    
234
244
    def checker_started(self, command):
235
 
        #self.logger(u'Client %s started checker "%s"'
236
 
        #            % (self.properties[u"Name"], unicode(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)))
237
249
        pass
238
250
    
239
251
    def got_secret(self):
240
252
        self.last_checker_failed = False
241
 
        self.logger(u'Client %s received its secret'
242
 
                    % self.properties[u"Name"])
 
253
        self.logger('Client %s received its secret'
 
254
                    % self.properties["Name"])
243
255
    
244
256
    def need_approval(self, timeout, default):
245
257
        if not default:
246
 
            message = u'Client %s needs approval within %s seconds'
 
258
            message = 'Client %s needs approval within %s seconds'
247
259
        else:
248
 
            message = u'Client %s will get its secret in %s seconds'
 
260
            message = 'Client %s will get its secret in %s seconds'
249
261
        self.logger(message
250
 
                    % (self.properties[u"Name"], timeout/1000))
 
262
                    % (self.properties["Name"], timeout/1000))
251
263
        self.using_timer(True)
252
264
    
253
265
    def rejected(self, reason):
254
 
        self.logger(u'Client %s was rejected; reason: %s'
255
 
                    % (self.properties[u"Name"], reason))
 
266
        self.logger('Client %s was rejected; reason: %s'
 
267
                    % (self.properties["Name"], reason))
256
268
    
257
269
    def selectable(self):
258
270
        """Make this a "selectable" widget.
273
285
    def update(self):
274
286
        "Called when what is visible on the screen should be updated."
275
287
        # How to add standout mode to a style
276
 
        with_standout = { u"normal": u"standout",
277
 
                          u"bold": u"bold-standout",
278
 
                          u"underline-blink":
279
 
                              u"underline-blink-standout",
280
 
                          u"bold-underline-blink":
281
 
                              u"bold-underline-blink-standout",
 
288
        with_standout = { "normal": "standout",
 
289
                          "bold": "bold-standout",
 
290
                          "underline-blink":
 
291
                              "underline-blink-standout",
 
292
                          "bold-underline-blink":
 
293
                              "bold-underline-blink-standout",
282
294
                          }
283
295
 
284
296
        # Rebuild focus and non-focus widgets using current properties
285
297
 
286
298
        # 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"]:
 
299
        base = ('%(name)s: '
 
300
                      % {"name": self.properties["Name"]})
 
301
        if not self.properties["Enabled"]:
 
302
            message = "DISABLED"
 
303
        elif self.properties["ApprovalPending"]:
292
304
            timeout = datetime.timedelta(milliseconds
293
305
                                         = self.properties
294
 
                                         [u"ApprovalDelay"])
 
306
                                         ["ApprovalDelay"])
295
307
            last_approval_request = isoformat_to_datetime(
296
 
                self.properties[u"LastApprovalRequest"])
 
308
                self.properties["LastApprovalRequest"])
297
309
            if last_approval_request is not None:
298
310
                timer = timeout - (datetime.datetime.utcnow()
299
311
                                   - last_approval_request)
300
312
            else:
301
313
                timer = datetime.timedelta()
302
 
            if self.properties[u"ApprovedByDefault"]:
303
 
                message = u"Approval in %s. (d)eny?"
 
314
            if self.properties["ApprovedByDefault"]:
 
315
                message = "Approval in %s. (d)eny?"
304
316
            else:
305
 
                message = u"Denial in %s. (a)pprove?"
 
317
                message = "Denial in %s. (a)pprove?"
306
318
            message = message % unicode(timer).rsplit(".", 1)[0]
307
319
        elif self.last_checker_failed:
308
 
            timeout = datetime.timedelta(milliseconds
309
 
                                         = self.properties
310
 
                                         [u"Timeout"])
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'
 
320
            # When checker has failed, print a timer until client expires
 
321
            expires = self.properties["Expires"]
 
322
            if expires == "":
 
323
                timer = datetime.timedelta(0)
 
324
            else:
 
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'
 
329
                       ' gets disabled: %s'
318
330
                           % unicode(timer).rsplit(".", 1)[0])
319
331
        else:
320
 
            message = u"enabled"
321
 
        self._text = u"%s%s" % (base, message)
 
332
            message = "enabled"
 
333
        self._text = "%s%s" % (base, message)
322
334
            
323
335
        if not urwid.supports_unicode():
324
 
            self._text = self._text.encode(u"ascii", u"replace")
325
 
        textlist = [(u"normal", self._text)]
 
336
            self._text = self._text.encode("ascii", "replace")
 
337
        textlist = [("normal", self._text)]
326
338
        self._text_widget.set_text(textlist)
327
339
        self._focus_text_widget.set_text([(with_standout[text[0]],
328
340
                                           text[1])
331
343
                                          for text in textlist])
332
344
        self._widget = self._text_widget
333
345
        self._focus_widget = urwid.AttrWrap(self._focus_text_widget,
334
 
                                            u"standout")
 
346
                                            "standout")
335
347
        # Run update hook, if any
336
348
        if self.update_hook is not None:
337
349
            self.update_hook()
338
350
    
339
351
    def update_timer(self):
340
 
        "called by gobject"
 
352
        """called by gobject. Will indefinitely loop until
 
353
        gobject.source_remove() on tag is called"""
341
354
        self.update()
342
355
        return True             # Keep calling this
343
356
    
344
 
    def delete(self):
 
357
    def delete(self, *args, **kwargs):
345
358
        if self._update_timer_callback_tag is not None:
346
359
            gobject.source_remove(self._update_timer_callback_tag)
347
360
            self._update_timer_callback_tag = None
 
361
        for match in self.match_objects:
 
362
            match.remove()
 
363
        self.match_objects = ()
348
364
        if self.delete_hook is not None:
349
365
            self.delete_hook(self)
 
366
        return super(MandosClientWidget, self).delete(*args, **kwargs)
350
367
    
351
368
    def render(self, maxcolrow, focus=False):
352
369
        """Render differently if we have focus.
357
374
    def keypress(self, maxcolrow, key):
358
375
        """Handle keys.
359
376
        This overrides the method from urwid.FlowWidget"""
360
 
        if key == u"+":
361
 
            self.proxy.Enable(dbus_interface = client_interface)
362
 
        elif key == u"-":
363
 
            self.proxy.Disable(dbus_interface = client_interface)
364
 
        elif key == u"a":
 
377
        if key == "+":
 
378
            self.proxy.Enable(dbus_interface = client_interface,
 
379
                              ignore_reply=True)
 
380
        elif key == "-":
 
381
            self.proxy.Disable(dbus_interface = client_interface,
 
382
                               ignore_reply=True)
 
383
        elif key == "a":
365
384
            self.proxy.Approve(dbus.Boolean(True, variant_level=1),
366
 
                               dbus_interface = client_interface)
367
 
        elif key == u"d":
 
385
                               dbus_interface = client_interface,
 
386
                               ignore_reply=True)
 
387
        elif key == "d":
368
388
            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":
 
389
                                  dbus_interface = client_interface,
 
390
                               ignore_reply=True)
 
391
        elif key == "R" or key == "_" or key == "ctrl k":
371
392
            self.server_proxy_object.RemoveClient(self.proxy
372
 
                                                  .object_path)
373
 
        elif key == u"s":
374
 
            self.proxy.StartChecker(dbus_interface = client_interface)
375
 
        elif key == u"S":
376
 
            self.proxy.StopChecker(dbus_interface = client_interface)
377
 
        elif key == u"C":
378
 
            self.proxy.CheckedOK(dbus_interface = client_interface)
 
393
                                                  .object_path,
 
394
                                                  ignore_reply=True)
 
395
        elif key == "s":
 
396
            self.proxy.StartChecker(dbus_interface = client_interface,
 
397
                                    ignore_reply=True)
 
398
        elif key == "S":
 
399
            self.proxy.StopChecker(dbus_interface = client_interface,
 
400
                                   ignore_reply=True)
 
401
        elif key == "C":
 
402
            self.proxy.CheckedOK(dbus_interface = client_interface,
 
403
                                 ignore_reply=True)
379
404
        # xxx
380
 
#         elif key == u"p" or key == "=":
 
405
#         elif key == "p" or key == "=":
381
406
#             self.proxy.pause()
382
 
#         elif key == u"u" or key == ":":
 
407
#         elif key == "u" or key == ":":
383
408
#             self.proxy.unpause()
384
 
#         elif key == u"RET":
 
409
#         elif key == "RET":
385
410
#             self.open()
386
411
        else:
387
412
            return key
405
430
    """
406
431
    def keypress(self, maxcolrow, key):
407
432
        ret = super(ConstrainedListBox, self).keypress(maxcolrow, key)
408
 
        if ret in (u"up", u"down"):
 
433
        if ret in ("up", "down"):
409
434
            return
410
435
        return ret
411
436
 
420
445
        self.screen = urwid.curses_display.Screen()
421
446
        
422
447
        self.screen.register_palette((
423
 
                (u"normal",
424
 
                 u"default", u"default", None),
425
 
                (u"bold",
426
 
                 u"default", u"default", u"bold"),
427
 
                (u"underline-blink",
428
 
                 u"default", u"default", u"underline"),
429
 
                (u"standout",
430
 
                 u"default", u"default", u"standout"),
431
 
                (u"bold-underline-blink",
432
 
                 u"default", u"default", (u"bold", u"underline")),
433
 
                (u"bold-standout",
434
 
                 u"default", u"default", (u"bold", u"standout")),
435
 
                (u"underline-blink-standout",
436
 
                 u"default", u"default", (u"underline", u"standout")),
437
 
                (u"bold-underline-blink-standout",
438
 
                 u"default", u"default", (u"bold", u"underline",
439
 
                                          u"standout")),
 
448
                ("normal",
 
449
                 "default", "default", None),
 
450
                ("bold",
 
451
                 "default", "default", "bold"),
 
452
                ("underline-blink",
 
453
                 "default", "default", "underline"),
 
454
                ("standout",
 
455
                 "default", "default", "standout"),
 
456
                ("bold-underline-blink",
 
457
                 "default", "default", ("bold", "underline")),
 
458
                ("bold-standout",
 
459
                 "default", "default", ("bold", "standout")),
 
460
                ("underline-blink-standout",
 
461
                 "default", "default", ("underline", "standout")),
 
462
                ("bold-underline-blink-standout",
 
463
                 "default", "default", ("bold", "underline",
 
464
                                          "standout")),
440
465
                ))
441
466
        
442
467
        if urwid.supports_unicode():
443
 
            self.divider = u"─" # \u2500
444
 
            #self.divider = u"━" # \u2501
 
468
            self.divider = "─" # \u2500
 
469
            #self.divider = "━" # \u2501
445
470
        else:
446
 
            #self.divider = u"-" # \u002d
447
 
            self.divider = u"_" # \u005f
 
471
            #self.divider = "-" # \u002d
 
472
            self.divider = "_" # \u005f
448
473
        
449
474
        self.screen.start()
450
475
        
464
489
        # This keeps track of whether self.uilist currently has
465
490
        # self.logbox in it or not
466
491
        self.log_visible = True
467
 
        self.log_wrap = u"any"
 
492
        self.log_wrap = "any"
468
493
        
469
494
        self.rebuild()
470
 
        self.log_message_raw((u"bold",
471
 
                              u"Mandos Monitor version " + version))
472
 
        self.log_message_raw((u"bold",
473
 
                              u"q: Quit  ?: Help"))
 
495
        self.log_message_raw(("bold",
 
496
                              "Mandos Monitor version " + version))
 
497
        self.log_message_raw(("bold",
 
498
                              "q: Quit  ?: Help"))
474
499
        
475
500
        self.busname = domain + '.Mandos'
476
501
        self.main_loop = gobject.MainLoop()
477
 
        self.bus = dbus.SystemBus()
478
 
        mandos_dbus_objc = self.bus.get_object(
479
 
            self.busname, u"/", follow_name_owner_changes=True)
480
 
        self.mandos_serv = dbus.Interface(mandos_dbus_objc,
481
 
                                          dbus_interface
482
 
                                          = server_interface)
483
 
        try:
484
 
            mandos_clients = (self.mandos_serv
485
 
                              .GetAllClientsWithProperties())
486
 
        except dbus.exceptions.DBusException:
487
 
            mandos_clients = dbus.Dictionary()
488
 
        
489
 
        (self.mandos_serv
490
 
         .connect_to_signal(u"ClientRemoved",
491
 
                            self.find_and_remove_client,
492
 
                            dbus_interface=server_interface,
493
 
                            byte_arrays=True))
494
 
        (self.mandos_serv
495
 
         .connect_to_signal(u"ClientAdded",
496
 
                            self.add_new_client,
497
 
                            dbus_interface=server_interface,
498
 
                            byte_arrays=True))
499
 
        (self.mandos_serv
500
 
         .connect_to_signal(u"ClientNotFound",
501
 
                            self.client_not_found,
502
 
                            dbus_interface=server_interface,
503
 
                            byte_arrays=True))
504
 
        for path, client in mandos_clients.iteritems():
505
 
            client_proxy_object = self.bus.get_object(self.busname,
506
 
                                                      path)
507
 
            self.add_client(MandosClientWidget(server_proxy_object
508
 
                                               =self.mandos_serv,
509
 
                                               proxy_object
510
 
                                               =client_proxy_object,
511
 
                                               properties=client,
512
 
                                               update_hook
513
 
                                               =self.refresh,
514
 
                                               delete_hook
515
 
                                               =self.remove_client,
516
 
                                               logger
517
 
                                               =self.log_message),
518
 
                            path=path)
519
502
    
520
503
    def client_not_found(self, fingerprint, address):
521
 
        self.log_message((u"Client with address %s and fingerprint %s"
522
 
                          u" could not be found" % (address,
 
504
        self.log_message(("Client with address %s and fingerprint %s"
 
505
                          " could not be found" % (address,
523
506
                                                    fingerprint)))
524
507
    
525
508
    def rebuild(self):
536
519
                                                     self.divider)))
537
520
        if self.log_visible:
538
521
            self.uilist.append(self.logbox)
539
 
            pass
540
522
        self.topwidget = urwid.Pile(self.uilist)
541
523
    
542
524
    def log_message(self, message):
543
525
        timestamp = datetime.datetime.now().isoformat()
544
 
        self.log_message_raw(timestamp + u": " + message)
 
526
        self.log_message_raw(timestamp + ": " + message)
545
527
    
546
528
    def log_message_raw(self, markup):
547
529
        """Add a log message to the log buffer."""
550
532
            and len(self.log) > self.max_log_length):
551
533
            del self.log[0:len(self.log)-self.max_log_length-1]
552
534
        self.logbox.set_focus(len(self.logbox.body.contents),
553
 
                              coming_from=u"above")
 
535
                              coming_from="above")
554
536
        self.refresh()
555
537
    
556
538
    def toggle_log_display(self):
557
539
        """Toggle visibility of the log buffer."""
558
540
        self.log_visible = not self.log_visible
559
541
        self.rebuild()
560
 
        #self.log_message(u"Log visibility changed to: "
 
542
        #self.log_message("Log visibility changed to: "
561
543
        #                 + unicode(self.log_visible))
562
544
    
563
545
    def change_log_display(self):
564
546
        """Change type of log display.
565
547
        Currently, this toggles wrapping of text lines."""
566
 
        if self.log_wrap == u"clip":
567
 
            self.log_wrap = u"any"
 
548
        if self.log_wrap == "clip":
 
549
            self.log_wrap = "any"
568
550
        else:
569
 
            self.log_wrap = u"clip"
 
551
            self.log_wrap = "clip"
570
552
        for textwidget in self.log:
571
553
            textwidget.set_wrap_mode(self.log_wrap)
572
 
        #self.log_message(u"Wrap mode: " + self.log_wrap)
 
554
        #self.log_message("Wrap mode: " + self.log_wrap)
573
555
    
574
556
    def find_and_remove_client(self, path, name):
575
 
        """Find an client from its object path and remove it.
 
557
        """Find a client by its object path and remove it.
576
558
        
577
559
        This is connected to the ClientRemoved signal from the
578
560
        Mandos server object."""
580
562
            client = self.clients_dict[path]
581
563
        except KeyError:
582
564
            # not found?
 
565
            self.log_message("Unknown client %r (%r) removed", name,
 
566
                             path)
583
567
            return
584
 
        self.remove_client(client, path)
 
568
        client.delete()
585
569
    
586
570
    def add_new_client(self, path):
587
571
        client_proxy_object = self.bus.get_object(self.busname, path)
602
586
        if path is None:
603
587
            path = client.proxy.object_path
604
588
        self.clients_dict[path] = client
605
 
        self.clients.sort(None, lambda c: c.properties[u"Name"])
 
589
        self.clients.sort(None, lambda c: c.properties["Name"])
606
590
        self.refresh()
607
591
    
608
592
    def remove_client(self, client, path=None):
624
608
    
625
609
    def run(self):
626
610
        """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,
 
615
                                          dbus_interface
 
616
                                          = server_interface)
 
617
        try:
 
618
            mandos_clients = (self.mandos_serv
 
619
                              .GetAllClientsWithProperties())
 
620
        except dbus.exceptions.DBusException:
 
621
            mandos_clients = dbus.Dictionary()
 
622
        
 
623
        (self.mandos_serv
 
624
         .connect_to_signal("ClientRemoved",
 
625
                            self.find_and_remove_client,
 
626
                            dbus_interface=server_interface,
 
627
                            byte_arrays=True))
 
628
        (self.mandos_serv
 
629
         .connect_to_signal("ClientAdded",
 
630
                            self.add_new_client,
 
631
                            dbus_interface=server_interface,
 
632
                            byte_arrays=True))
 
633
        (self.mandos_serv
 
634
         .connect_to_signal("ClientNotFound",
 
635
                            self.client_not_found,
 
636
                            dbus_interface=server_interface,
 
637
                            byte_arrays=True))
 
638
        for path, client in mandos_clients.iteritems():
 
639
            client_proxy_object = self.bus.get_object(self.busname,
 
640
                                                      path)
 
641
            self.add_client(MandosClientWidget(server_proxy_object
 
642
                                               =self.mandos_serv,
 
643
                                               proxy_object
 
644
                                               =client_proxy_object,
 
645
                                               properties=client,
 
646
                                               update_hook
 
647
                                               =self.refresh,
 
648
                                               delete_hook
 
649
                                               =self.remove_client,
 
650
                                               logger
 
651
                                               =self.log_message),
 
652
                            path=path)
 
653
 
627
654
        self.refresh()
628
655
        self._input_callback_tag = (gobject.io_add_watch
629
656
                                    (sys.stdin.fileno(),
639
666
    
640
667
    def process_input(self, source, condition):
641
668
        keys = self.screen.get_input()
642
 
        translations = { u"ctrl n": u"down",      # Emacs
643
 
                         u"ctrl p": u"up",        # Emacs
644
 
                         u"ctrl v": u"page down", # Emacs
645
 
                         u"meta v": u"page up",   # Emacs
646
 
                         u" ": u"page down",      # less
647
 
                         u"f": u"page down",      # less
648
 
                         u"b": u"page up",        # less
649
 
                         u"j": u"down",           # vi
650
 
                         u"k": u"up",             # vi
 
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
 
676
                         "j": "down",           # vi
 
677
                         "k": "up",             # vi
651
678
                         }
652
679
        for key in keys:
653
680
            try:
655
682
            except KeyError:    # :-)
656
683
                pass
657
684
            
658
 
            if key == u"q" or key == u"Q":
 
685
            if key == "q" or key == "Q":
659
686
                self.stop()
660
687
                break
661
 
            elif key == u"window resize":
 
688
            elif key == "window resize":
662
689
                self.size = self.screen.get_cols_rows()
663
690
                self.refresh()
664
 
            elif key == u"\f":  # Ctrl-L
 
691
            elif key == "\f":  # Ctrl-L
665
692
                self.refresh()
666
 
            elif key == u"l" or key == u"D":
 
693
            elif key == "l" or key == "D":
667
694
                self.toggle_log_display()
668
695
                self.refresh()
669
 
            elif key == u"w" or key == u"i":
 
696
            elif key == "w" or key == "i":
670
697
                self.change_log_display()
671
698
                self.refresh()
672
 
            elif key == u"?" or key == u"f1" or key == u"esc":
 
699
            elif key == "?" or key == "f1" or key == "esc":
673
700
                if not self.log_visible:
674
701
                    self.log_visible = True
675
702
                    self.rebuild()
676
 
                self.log_message_raw((u"bold",
677
 
                                      u"  ".
678
 
                                      join((u"q: Quit",
679
 
                                            u"?: Help",
680
 
                                            u"l: Log window toggle",
681
 
                                            u"TAB: Switch window",
682
 
                                            u"w: Wrap (log)"))))
683
 
                self.log_message_raw((u"bold",
684
 
                                      u"  "
685
 
                                      .join((u"Clients:",
686
 
                                             u"+: Enable",
687
 
                                             u"-: Disable",
688
 
                                             u"R: Remove",
689
 
                                             u"s: Start new checker",
690
 
                                             u"S: Stop checker",
691
 
                                             u"C: Checker OK",
692
 
                                             u"a: Approve",
693
 
                                             u"d: Deny"))))
 
703
                self.log_message_raw(("bold",
 
704
                                      "  ".
 
705
                                      join(("q: Quit",
 
706
                                            "?: Help",
 
707
                                            "l: Log window toggle",
 
708
                                            "TAB: Switch window",
 
709
                                            "w: Wrap (log)"))))
 
710
                self.log_message_raw(("bold",
 
711
                                      "  "
 
712
                                      .join(("Clients:",
 
713
                                             "+: Enable",
 
714
                                             "-: Disable",
 
715
                                             "R: Remove",
 
716
                                             "s: Start new checker",
 
717
                                             "S: Stop checker",
 
718
                                             "C: Checker OK",
 
719
                                             "a: Approve",
 
720
                                             "d: Deny"))))
694
721
                self.refresh()
695
 
            elif key == u"tab":
 
722
            elif key == "tab":
696
723
                if self.topwidget.get_focus() is self.logbox:
697
724
                    self.topwidget.set_focus(0)
698
725
                else:
699
726
                    self.topwidget.set_focus(self.logbox)
700
727
                self.refresh()
701
 
            #elif (key == u"end" or key == u"meta >" or key == u"G"
702
 
            #      or key == u">"):
 
728
            #elif (key == "end" or key == "meta >" or key == "G"
 
729
            #      or key == ">"):
703
730
            #    pass            # xxx end-of-buffer
704
 
            #elif (key == u"home" or key == u"meta <" or key == u"g"
705
 
            #      or key == u"<"):
 
731
            #elif (key == "home" or key == "meta <" or key == "g"
 
732
            #      or key == "<"):
706
733
            #    pass            # xxx beginning-of-buffer
707
 
            #elif key == u"ctrl e" or key == u"$":
 
734
            #elif key == "ctrl e" or key == "$":
708
735
            #    pass            # xxx move-end-of-line
709
 
            #elif key == u"ctrl a" or key == u"^":
 
736
            #elif key == "ctrl a" or key == "^":
710
737
            #    pass            # xxx move-beginning-of-line
711
 
            #elif key == u"ctrl b" or key == u"meta (" or key == u"h":
 
738
            #elif key == "ctrl b" or key == "meta (" or key == "h":
712
739
            #    pass            # xxx left
713
 
            #elif key == u"ctrl f" or key == u"meta )" or key == u"l":
 
740
            #elif key == "ctrl f" or key == "meta )" or key == "l":
714
741
            #    pass            # xxx right
715
 
            #elif key == u"a":
 
742
            #elif key == "a":
716
743
            #    pass            # scroll up log
717
 
            #elif key == u"z":
 
744
            #elif key == "z":
718
745
            #    pass            # scroll down log
719
746
            elif self.topwidget.selectable():
720
747
                self.topwidget.keypress(self.size, key)