/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: 2010-09-26 17:44:43 UTC
  • Revision ID: teddy@fukt.bsnet.se-20100926174443-452k363uuvczuuzg
* mandos-ctl: Also show "LastApprovalRequest" property.

Show diffs side-by-side

added added

removed removed

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