/mandos/release

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

« back to all changes in this revision

Viewing changes to mandos-monitor

* dbus-mandos.conf: New; to be copied to
                    "/etc/dbus-1/system.d/mandos.conf".

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