/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
1
#!/usr/bin/python
2
# -*- mode: python; coding: utf-8 -*-
3
4
from __future__ import division, absolute_import, with_statement
5
6
import sys
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
7
import os
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
8
import signal
9
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
10
import datetime
11
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
12
import urwid.curses_display
13
import urwid
14
15
from dbus.mainloop.glib import DBusGMainLoop
16
import gobject
17
18
import dbus
19
20
import UserList
21
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
22
import locale
23
24
locale.setlocale(locale.LC_ALL, u'')
25
24.1.153 by Björn Påhlsson
early commit to ease todays coding
26
import logging
27
logging.getLogger('dbus.proxies').setLevel(logging.CRITICAL)
28
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
29
# Some useful constants
30
domain = 'se.bsnet.fukt'
31
server_interface = domain + '.Mandos'
32
client_interface = domain + '.Mandos.Client'
24.1.153 by Björn Påhlsson
early commit to ease todays coding
33
version = "1.0.15"
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
34
35
# Always run in monochrome mode
36
urwid.curses_display.curses.has_colors = lambda : False
37
38
# Urwid doesn't support blinking, but we want it.  Since we have no
39
# use for underline on its own, we make underline also always blink.
40
urwid.curses_display.curses.A_UNDERLINE |= (
41
    urwid.curses_display.curses.A_BLINK)
42
417 by Teddy Hogeborn
* mandos (AvahiService.entry_group_state_changed): Better debug log
43
def isoformat_to_datetime(iso):
44
    "Parse an ISO 8601 date string to a datetime.datetime()"
45
    if not iso:
46
        return None
47
    d, t = iso.split(u"T", 1)
48
    year, month, day = d.split(u"-", 2)
49
    hour, minute, second = t.split(u":", 2)
50
    second, fraction = divmod(float(second), 1)
51
    return datetime.datetime(int(year),
52
                             int(month),
53
                             int(day),
54
                             int(hour),
55
                             int(minute),
56
                             int(second),           # Whole seconds
57
                             int(fraction*1000000)) # Microseconds
58
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
59
class MandosClientPropertyCache(object):
60
    """This wraps a Mandos Client D-Bus proxy object, caches the
61
    properties and calls a hook function when any of them are
62
    changed.
63
    """
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
64
    def __init__(self, proxy_object=None, *args, **kwargs):
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
65
        self.proxy = proxy_object # Mandos Client proxy object
66
        
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
67
        self.properties = dict()
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
68
        self.proxy.connect_to_signal(u"PropertyChanged",
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
69
                                     self.property_changed,
70
                                     client_interface,
71
                                     byte_arrays=True)
24.1.153 by Björn Påhlsson
early commit to ease todays coding
72
        
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
73
        self.properties.update(
74
            self.proxy.GetAll(client_interface,
75
                              dbus_interface = dbus.PROPERTIES_IFACE))
24.1.154 by Björn Påhlsson
merge
76
77
        #XXX This break good super behaviour!
78
#        super(MandosClientPropertyCache, self).__init__(
79
#            *args, **kwargs)
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
80
    
81
    def property_changed(self, property=None, value=None):
82
        """This is called whenever we get a PropertyChanged signal
83
        It updates the changed property in the "properties" dict.
84
        """
85
        # Update properties dict with new value
405 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
86
        self.properties[property] = value
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
87
88
89
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
90
    """A Mandos Client which is visible on the screen.
91
    """
92
    
93
    def __init__(self, server_proxy_object=None, update_hook=None,
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
94
                 delete_hook=None, logger=None, *args, **kwargs):
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
95
        # Called on update
96
        self.update_hook = update_hook
97
        # Called on delete
98
        self.delete_hook = delete_hook
99
        # Mandos Server proxy object
100
        self.server_proxy_object = server_proxy_object
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
101
        # Logger
102
        self.logger = logger
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
103
        
417 by Teddy Hogeborn
* mandos (AvahiService.entry_group_state_changed): Better debug log
104
        self._update_timer_callback_tag = None
105
        self.last_checker_failed = False
106
        
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
107
        # The widget shown normally
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
108
        self._text_widget = urwid.Text(u"")
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
109
        # The widget shown when we have focus
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
110
        self._focus_text_widget = urwid.Text(u"")
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
111
        super(MandosClientWidget, self).__init__(
112
            update_hook=update_hook, delete_hook=delete_hook,
113
            *args, **kwargs)
114
        self.update()
115
        self.opened = False
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
116
        self.proxy.connect_to_signal(u"CheckerCompleted",
117
                                     self.checker_completed,
118
                                     client_interface,
119
                                     byte_arrays=True)
120
        self.proxy.connect_to_signal(u"CheckerStarted",
121
                                     self.checker_started,
122
                                     client_interface,
123
                                     byte_arrays=True)
124
        self.proxy.connect_to_signal(u"GotSecret",
125
                                     self.got_secret,
126
                                     client_interface,
127
                                     byte_arrays=True)
24.1.153 by Björn Påhlsson
early commit to ease todays coding
128
        self.proxy.connect_to_signal(u"NeedApproval",
129
                                     self.need_approval,
130
                                     client_interface,
131
                                     byte_arrays=True)
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
132
        self.proxy.connect_to_signal(u"Rejected",
133
                                     self.rejected,
134
                                     client_interface,
135
                                     byte_arrays=True)
417 by Teddy Hogeborn
* mandos (AvahiService.entry_group_state_changed): Better debug log
136
        last_checked_ok = isoformat_to_datetime(self.properties
137
                                                ["last_checked_ok"])
138
        if last_checked_ok is None:
139
            self.last_checker_failed = True
140
        else:
141
            self.last_checker_failed = ((datetime.datetime.utcnow()
142
                                         - last_checked_ok)
143
                                        > datetime.timedelta
144
                                        (milliseconds=
145
                                         self.properties["interval"]))
146
        if self.last_checker_failed:
147
            self._update_timer_callback_tag = (gobject.timeout_add
148
                                               (1000,
149
                                                self.update_timer))
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
150
    
151
    def checker_completed(self, exitstatus, condition, command):
152
        if exitstatus == 0:
417 by Teddy Hogeborn
* mandos (AvahiService.entry_group_state_changed): Better debug log
153
            if self.last_checker_failed:
154
                self.last_checker_failed = False
155
                gobject.source_remove(self._update_timer_callback_tag)
156
                self._update_timer_callback_tag = None
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
157
            self.logger(u'Checker for client %s (command "%s")'
158
                        u' was successful'
159
                        % (self.properties[u"name"], command))
417 by Teddy Hogeborn
* mandos (AvahiService.entry_group_state_changed): Better debug log
160
            self.update()
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
161
            return
417 by Teddy Hogeborn
* mandos (AvahiService.entry_group_state_changed): Better debug log
162
        # Checker failed
163
        if not self.last_checker_failed:
164
            self.last_checker_failed = True
165
            self._update_timer_callback_tag = (gobject.timeout_add
166
                                               (1000,
167
                                                self.update_timer))
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
168
        if os.WIFEXITED(condition):
169
            self.logger(u'Checker for client %s (command "%s")'
170
                        u' failed with exit code %s'
171
                        % (self.properties[u"name"], command,
172
                           os.WEXITSTATUS(condition)))
417 by Teddy Hogeborn
* mandos (AvahiService.entry_group_state_changed): Better debug log
173
        elif os.WIFSIGNALED(condition):
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
174
            self.logger(u'Checker for client %s (command "%s")'
175
                        u' was killed by signal %s'
176
                        % (self.properties[u"name"], command,
177
                           os.WTERMSIG(condition)))
417 by Teddy Hogeborn
* mandos (AvahiService.entry_group_state_changed): Better debug log
178
        elif os.WCOREDUMP(condition):
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
179
            self.logger(u'Checker for client %s (command "%s")'
180
                        u' dumped core'
181
                        % (self.properties[u"name"], command))
417 by Teddy Hogeborn
* mandos (AvahiService.entry_group_state_changed): Better debug log
182
        else:
183
            self.logger(u'Checker for client %s completed mysteriously')
184
        self.update()
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
185
    
186
    def checker_started(self, command):
24.1.153 by Björn Påhlsson
early commit to ease todays coding
187
        #self.logger(u'Client %s started checker "%s"'
188
        #            % (self.properties[u"name"], unicode(command)))
189
        pass
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
190
    
191
    def got_secret(self):
192
        self.logger(u'Client %s received its secret'
193
                    % self.properties[u"name"])
194
    
24.1.153 by Björn Påhlsson
early commit to ease todays coding
195
    def need_approval(self, timeout, default):
196
        if not default:
197
            message = u'Client %s needs approval within %s seconds'
198
        else:
199
            message = u'Client %s will get its secret in %s seconds'
200
        self.logger(message
201
                    % (self.properties[u"name"], timeout/1000))
202
    
203
    def rejected(self, reason):
204
        self.logger(u'Client %s was rejected; reason: %s'
205
                    % (self.properties[u"name"], reason))
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
206
    
207
    def selectable(self):
208
        """Make this a "selectable" widget.
209
        This overrides the method from urwid.FlowWidget."""
210
        return True
211
    
212
    def rows(self, (maxcol,), focus=False):
213
        """How many rows this widget will occupy might depend on
214
        whether we have focus or not.
215
        This overrides the method from urwid.FlowWidget"""
216
        return self.current_widget(focus).rows((maxcol,), focus=focus)
217
    
218
    def current_widget(self, focus=False):
219
        if focus or self.opened:
220
            return self._focus_widget
221
        return self._widget
222
    
223
    def update(self):
224
        "Called when what is visible on the screen should be updated."
225
        # How to add standout mode to a style
226
        with_standout = { u"normal": u"standout",
227
                          u"bold": u"bold-standout",
228
                          u"underline-blink":
229
                              u"underline-blink-standout",
230
                          u"bold-underline-blink":
231
                              u"bold-underline-blink-standout",
232
                          }
24.1.154 by Björn Påhlsson
merge
233
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
234
        # Rebuild focus and non-focus widgets using current properties
24.1.154 by Björn Påhlsson
merge
235
236
        # Base part of a client. Name!
24.1.156 by Björn Påhlsson
merge
237
        base = (u'%(name)s: '
24.1.154 by Björn Påhlsson
merge
238
                      % {u"name": self.properties[u"name"]})
24.1.156 by Björn Påhlsson
merge
239
        if not self.properties[u"enabled"]:
240
            message = u"DISABLED"
241
        elif self.last_checker_failed:
242
            timeout = datetime.timedelta(milliseconds
243
                                         = self.properties[u"timeout"])
244
            last_ok = isoformat_to_datetime(
245
                max((self.properties["last_checked_ok"]
246
                     or self.properties["created"]),
247
                    self.properties[u"last_enabled"]))
248
            timer = timeout - (datetime.datetime.utcnow() - last_ok)
249
            message = (u'A checker has failed! Time until client gets diabled: %s'
24.1.158 by Björn Påhlsson
mandos-monitor: removed milisecondsseconds from countdown.
250
                           % unicode(timer).rsplit(".", 1)[0])
24.1.156 by Björn Påhlsson
merge
251
        elif self.properties[u"approved_pending"]:
24.1.154 by Björn Påhlsson
merge
252
            if self.properties[u"approved_by_default"]:
24.1.156 by Björn Påhlsson
merge
253
                message = u"Connection established to client. (d)eny?"
24.1.154 by Björn Påhlsson
merge
254
            else:
24.1.156 by Björn Påhlsson
merge
255
                message = u"Seeks approval to send secret. (a)pprove?"
24.1.154 by Björn Påhlsson
merge
256
        else:
24.1.156 by Björn Påhlsson
merge
257
            message = u"enabled"
258
        self._text = "%s%s" % (base, message)
259
            
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
260
        if not urwid.supports_unicode():
261
            self._text = self._text.encode("ascii", "replace")
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
262
        textlist = [(u"normal", self._text)]
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
263
        self._text_widget.set_text(textlist)
264
        self._focus_text_widget.set_text([(with_standout[text[0]],
265
                                           text[1])
266
                                          if isinstance(text, tuple)
267
                                          else text
268
                                          for text in textlist])
269
        self._widget = self._text_widget
270
        self._focus_widget = urwid.AttrWrap(self._focus_text_widget,
271
                                            "standout")
272
        # Run update hook, if any
273
        if self.update_hook is not None:
274
            self.update_hook()
275
    
417 by Teddy Hogeborn
* mandos (AvahiService.entry_group_state_changed): Better debug log
276
    def update_timer(self):
277
        "called by gobject"
278
        self.update()
279
        return True             # Keep calling this
280
    
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
281
    def delete(self):
417 by Teddy Hogeborn
* mandos (AvahiService.entry_group_state_changed): Better debug log
282
        if self._update_timer_callback_tag is not None:
283
            gobject.source_remove(self._update_timer_callback_tag)
284
            self._update_timer_callback_tag = None
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
285
        if self.delete_hook is not None:
286
            self.delete_hook(self)
287
    
288
    def render(self, (maxcol,), focus=False):
289
        """Render differently if we have focus.
290
        This overrides the method from urwid.FlowWidget"""
291
        return self.current_widget(focus).render((maxcol,),
292
                                                 focus=focus)
293
    
294
    def keypress(self, (maxcol,), key):
295
        """Handle keys.
296
        This overrides the method from urwid.FlowWidget"""
24.1.154 by Björn Påhlsson
merge
297
        if key == u"+":
298
            self.proxy.Enable(dbus_interface = client_interface)
299
        elif key == u"-":
300
            self.proxy.Disable(dbus_interface = client_interface)
301
        elif key == u"a":
302
            self.proxy.Approve(dbus.Boolean(True, variant_level=1),
303
                               dbus_interface = client_interface)
304
        elif key == u"d":
305
            self.proxy.Approve(dbus.Boolean(False, variant_level=1),
306
                                  dbus_interface = client_interface)
408 by Teddy Hogeborn
* debian/rules: Only set BROKEN_PIE if binutils is a specific range of
307
        elif key == u"r" or key == u"_" or key == u"ctrl k":
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
308
            self.server_proxy_object.RemoveClient(self.proxy
309
                                                  .object_path)
310
        elif key == u"s":
24.1.154 by Björn Påhlsson
merge
311
            self.proxy.StartChecker(dbus_interface = client_interface)
407 by Teddy Hogeborn
* mandos-monitor (MandosClientWidget): Change "StopChecker" key to "S"
312
        elif key == u"S":
24.1.154 by Björn Påhlsson
merge
313
            self.proxy.StopChecker(dbus_interface = client_interface)
407 by Teddy Hogeborn
* mandos-monitor (MandosClientWidget): Change "StopChecker" key to "S"
314
        elif key == u"C":
24.1.154 by Björn Påhlsson
merge
315
            self.proxy.CheckedOK(dbus_interface = client_interface)
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
316
        # xxx
317
#         elif key == u"p" or key == "=":
318
#             self.proxy.pause()
319
#         elif key == u"u" or key == ":":
320
#             self.proxy.unpause()
321
#         elif key == u"RET":
322
#             self.open()
24.1.154 by Björn Påhlsson
merge
323
#        elif key == u"+":
324
#            self.proxy.Approve(True)
325
#        elif key == u"-":
326
#            self.proxy.Approve(False)
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
327
        else:
328
            return key
329
    
330
    def property_changed(self, property=None, value=None,
331
                         *args, **kwargs):
332
        """Call self.update() if old value is not new value.
333
        This overrides the method from MandosClientPropertyCache"""
334
        property_name = unicode(property)
335
        old_value = self.properties.get(property_name)
336
        super(MandosClientWidget, self).property_changed(
337
            property=property, value=value, *args, **kwargs)
338
        if self.properties.get(property_name) != old_value:
339
            self.update()
340
341
405 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
342
class ConstrainedListBox(urwid.ListBox):
343
    """Like a normal urwid.ListBox, but will consume all "up" or
344
    "down" key presses, thus not allowing any containing widgets to
345
    use them as an excuse to shift focus away from this widget.
346
    """
347
    def keypress(self, (maxcol, maxrow), key):
348
        ret = super(ConstrainedListBox, self).keypress((maxcol, maxrow), key)
349
        if ret in (u"up", u"down"):
350
            return
351
        return ret
352
353
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
354
class UserInterface(object):
355
    """This is the entire user interface - the whole screen
356
    with boxes, lists of client widgets, etc.
357
    """
405 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
358
    def __init__(self, max_log_length=1000):
359
        DBusGMainLoop(set_as_default=True)
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
360
        
361
        self.screen = urwid.curses_display.Screen()
362
        
363
        self.screen.register_palette((
364
                (u"normal",
365
                 u"default", u"default", None),
366
                (u"bold",
367
                 u"default", u"default", u"bold"),
368
                (u"underline-blink",
369
                 u"default", u"default", u"underline"),
370
                (u"standout",
371
                 u"default", u"default", u"standout"),
372
                (u"bold-underline-blink",
373
                 u"default", u"default", (u"bold", u"underline")),
374
                (u"bold-standout",
375
                 u"default", u"default", (u"bold", u"standout")),
376
                (u"underline-blink-standout",
377
                 u"default", u"default", (u"underline", u"standout")),
378
                (u"bold-underline-blink-standout",
379
                 u"default", u"default", (u"bold", u"underline",
380
                                          u"standout")),
381
                ))
382
        
405 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
383
        if urwid.supports_unicode():
408 by Teddy Hogeborn
* debian/rules: Only set BROKEN_PIE if binutils is a specific range of
384
            self.divider = u"─" # \u2500
385
            #self.divider = u"━" # \u2501
405 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
386
        else:
387
            #self.divider = u"-" # \u002d
388
            self.divider = u"_" # \u005f
389
        
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
390
        self.screen.start()
391
        
392
        self.size = self.screen.get_cols_rows()
393
        
394
        self.clients = urwid.SimpleListWalker([])
395
        self.clients_dict = {}
405 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
396
        
397
        # We will add Text widgets to this list
398
        self.log = []
399
        self.max_log_length = max_log_length
400
        
401
        # We keep a reference to the log widget so we can remove it
402
        # from the ListWalker without it getting destroyed
403
        self.logbox = ConstrainedListBox(self.log)
404
        
405
        # This keeps track of whether self.uilist currently has
406
        # self.logbox in it or not
407
        self.log_visible = True
408
        self.log_wrap = u"any"
409
        
410
        self.rebuild()
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
411
        self.log_message_raw((u"bold",
412
                              u"Mandos Monitor version " + version))
413
        self.log_message_raw((u"bold",
414
                              u"q: Quit  ?: Help"))
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
415
        
416
        self.busname = domain + '.Mandos'
417
        self.main_loop = gobject.MainLoop()
418
        self.bus = dbus.SystemBus()
419
        mandos_dbus_objc = self.bus.get_object(
420
            self.busname, u"/", follow_name_owner_changes=True)
421
        self.mandos_serv = dbus.Interface(mandos_dbus_objc,
422
                                          dbus_interface
423
                                          = server_interface)
424
        try:
425
            mandos_clients = (self.mandos_serv
426
                              .GetAllClientsWithProperties())
427
        except dbus.exceptions.DBusException:
428
            mandos_clients = dbus.Dictionary()
429
        
430
        (self.mandos_serv
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
431
         .connect_to_signal(u"ClientRemoved",
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
432
                            self.find_and_remove_client,
433
                            dbus_interface=server_interface,
434
                            byte_arrays=True))
435
        (self.mandos_serv
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
436
         .connect_to_signal(u"ClientAdded",
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
437
                            self.add_new_client,
438
                            dbus_interface=server_interface,
439
                            byte_arrays=True))
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
440
        (self.mandos_serv
441
         .connect_to_signal(u"ClientNotFound",
442
                            self.client_not_found,
443
                            dbus_interface=server_interface,
444
                            byte_arrays=True))
405 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
445
        for path, client in mandos_clients.iteritems():
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
446
            client_proxy_object = self.bus.get_object(self.busname,
447
                                                      path)
448
            self.add_client(MandosClientWidget(server_proxy_object
449
                                               =self.mandos_serv,
450
                                               proxy_object
451
                                               =client_proxy_object,
452
                                               properties=client,
453
                                               update_hook
454
                                               =self.refresh,
455
                                               delete_hook
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
456
                                               =self.remove_client,
457
                                               logger
458
                                               =self.log_message),
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
459
                            path=path)
460
    
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
461
    def client_not_found(self, fingerprint, address):
462
        self.log_message((u"Client with address %s and fingerprint %s"
463
                          u" could not be found" % (address,
464
                                                    fingerprint)))
465
    
405 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
466
    def rebuild(self):
467
        """This rebuilds the User Interface.
468
        Call this when the widget layout needs to change"""
469
        self.uilist = []
470
        #self.uilist.append(urwid.ListBox(self.clients))
471
        self.uilist.append(urwid.Frame(ConstrainedListBox(self.clients),
472
                                       #header=urwid.Divider(),
473
                                       header=None,
474
                                       footer=urwid.Divider(div_char=self.divider)))
475
        if self.log_visible:
476
            self.uilist.append(self.logbox)
477
            pass
478
        self.topwidget = urwid.Pile(self.uilist)
479
    
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
480
    def log_message(self, message):
481
        timestamp = datetime.datetime.now().isoformat()
482
        self.log_message_raw(timestamp + u": " + message)
483
    
484
    def log_message_raw(self, markup):
405 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
485
        """Add a log message to the log buffer."""
486
        self.log.append(urwid.Text(markup, wrap=self.log_wrap))
487
        if (self.max_log_length
488
            and len(self.log) > self.max_log_length):
489
            del self.log[0:len(self.log)-self.max_log_length-1]
408 by Teddy Hogeborn
* debian/rules: Only set BROKEN_PIE if binutils is a specific range of
490
        self.logbox.set_focus(len(self.logbox.body.contents),
491
                              coming_from=u"above")
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
492
        self.refresh()
405 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
493
    
494
    def toggle_log_display(self):
495
        """Toggle visibility of the log buffer."""
496
        self.log_visible = not self.log_visible
497
        self.rebuild()
498
        self.log_message(u"Log visibility changed to: "
499
                         + unicode(self.log_visible))
500
    
501
    def change_log_display(self):
502
        """Change type of log display.
503
        Currently, this toggles wrapping of text lines."""
504
        if self.log_wrap == u"clip":
505
            self.log_wrap = u"any"
506
        else:
507
            self.log_wrap = u"clip"
508
        for textwidget in self.log:
509
            textwidget.set_wrap_mode(self.log_wrap)
510
        self.log_message(u"Wrap mode: " + self.log_wrap)
511
    
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
512
    def find_and_remove_client(self, path, name):
513
        """Find an client from its object path and remove it.
514
        
515
        This is connected to the ClientRemoved signal from the
516
        Mandos server object."""
517
        try:
518
            client = self.clients_dict[path]
519
        except KeyError:
520
            # not found?
521
            return
522
        self.remove_client(client, path)
523
    
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
524
    def add_new_client(self, path):
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
525
        client_proxy_object = self.bus.get_object(self.busname, path)
526
        self.add_client(MandosClientWidget(server_proxy_object
527
                                           =self.mandos_serv,
528
                                           proxy_object
529
                                           =client_proxy_object,
530
                                           update_hook
531
                                           =self.refresh,
532
                                           delete_hook
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
533
                                           =self.remove_client,
534
                                           logger
535
                                           =self.log_message),
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
536
                        path=path)
537
    
538
    def add_client(self, client, path=None):
539
        self.clients.append(client)
540
        if path is None:
541
            path = client.proxy.object_path
542
        self.clients_dict[path] = client
543
        self.clients.sort(None, lambda c: c.properties[u"name"])
544
        self.refresh()
545
    
546
    def remove_client(self, client, path=None):
547
        self.clients.remove(client)
548
        if path is None:
549
            path = client.proxy.object_path
550
        del self.clients_dict[path]
405 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
551
        if not self.clients_dict:
552
            # Work around bug in Urwid 0.9.8.3 - if a SimpleListWalker
553
            # is completely emptied, we need to recreate it.
554
            self.clients = urwid.SimpleListWalker([])
555
            self.rebuild()
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
556
        self.refresh()
557
    
558
    def refresh(self):
559
        """Redraw the screen"""
560
        canvas = self.topwidget.render(self.size, focus=True)
561
        self.screen.draw_screen(self.size, canvas)
562
    
563
    def run(self):
564
        """Start the main loop and exit when it's done."""
565
        self.refresh()
566
        self._input_callback_tag = (gobject.io_add_watch
567
                                    (sys.stdin.fileno(),
568
                                     gobject.IO_IN,
569
                                     self.process_input))
570
        self.main_loop.run()
571
        # Main loop has finished, we should close everything now
572
        gobject.source_remove(self._input_callback_tag)
573
        self.screen.stop()
574
    
575
    def stop(self):
576
        self.main_loop.quit()
577
    
578
    def process_input(self, source, condition):
579
        keys = self.screen.get_input()
405 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
580
        translations = { u"ctrl n": u"down",      # Emacs
581
                         u"ctrl p": u"up",        # Emacs
582
                         u"ctrl v": u"page down", # Emacs
583
                         u"meta v": u"page up",   # Emacs
584
                         u" ": u"page down",      # less
585
                         u"f": u"page down",      # less
586
                         u"b": u"page up",        # less
587
                         u"j": u"down",           # vi
588
                         u"k": u"up",             # vi
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
589
                         }
590
        for key in keys:
591
            try:
592
                key = translations[key]
593
            except KeyError:    # :-)
594
                pass
595
            
596
            if key == u"q" or key == u"Q":
597
                self.stop()
598
                break
599
            elif key == u"window resize":
600
                self.size = self.screen.get_cols_rows()
601
                self.refresh()
405 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
602
            elif key == u"\f":  # Ctrl-L
603
                self.refresh()
604
            elif key == u"l" or key == u"D":
605
                self.toggle_log_display()
606
                self.refresh()
607
            elif key == u"w" or key == u"i":
608
                self.change_log_display()
609
                self.refresh()
408 by Teddy Hogeborn
* debian/rules: Only set BROKEN_PIE if binutils is a specific range of
610
            elif key == u"?" or key == u"f1" or key == u"esc":
407 by Teddy Hogeborn
* mandos-monitor (MandosClientWidget): Change "StopChecker" key to "S"
611
                if not self.log_visible:
612
                    self.log_visible = True
613
                    self.rebuild()
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
614
                self.log_message_raw((u"bold",
615
                                      u"  ".
616
                                      join((u"q: Quit",
617
                                            u"?: Help",
618
                                            u"l: Log window toggle",
619
                                            u"TAB: Switch window",
620
                                            u"w: Wrap (log)"))))
621
                self.log_message_raw((u"bold",
622
                                      u"  "
623
                                      .join((u"Clients:",
24.1.155 by Björn Påhlsson
mandos server: Added debuglevel that adjust at what level information
624
                                             u"+: Enable",
625
                                             u"-: Disable",
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
626
                                             u"r: Remove",
627
                                             u"s: Start new checker",
628
                                             u"S: Stop checker",
24.1.154 by Björn Påhlsson
merge
629
                                             u"C: Checker OK",
24.1.155 by Björn Påhlsson
mandos server: Added debuglevel that adjust at what level information
630
                                             u"a: Approve",
631
                                             u"d: Deny"))))
405 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
632
                self.refresh()
633
            elif key == u"tab":
634
                if self.topwidget.get_focus() is self.logbox:
635
                    self.topwidget.set_focus(0)
636
                else:
637
                    self.topwidget.set_focus(self.logbox)
638
                self.refresh()
407 by Teddy Hogeborn
* mandos-monitor (MandosClientWidget): Change "StopChecker" key to "S"
639
            #elif (key == u"end" or key == u"meta >" or key == u"G"
640
            #      or key == u">"):
641
            #    pass            # xxx end-of-buffer
642
            #elif (key == u"home" or key == u"meta <" or key == u"g"
643
            #      or key == u"<"):
644
            #    pass            # xxx beginning-of-buffer
645
            #elif key == u"ctrl e" or key == u"$":
646
            #    pass            # xxx move-end-of-line
647
            #elif key == u"ctrl a" or key == u"^":
648
            #    pass            # xxx move-beginning-of-line
649
            #elif key == u"ctrl b" or key == u"meta (" or key == u"h":
650
            #    pass            # xxx left
651
            #elif key == u"ctrl f" or key == u"meta )" or key == u"l":
652
            #    pass            # xxx right
653
            #elif key == u"a":
654
            #    pass            # scroll up log
655
            #elif key == u"z":
656
            #    pass            # scroll down log
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
657
            elif self.topwidget.selectable():
658
                self.topwidget.keypress(self.size, key)
659
                self.refresh()
660
        return True
661
662
ui = UserInterface()
663
try:
664
    ui.run()
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
665
except Exception, e:
666
    ui.log_message(unicode(e))
404 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
667
    ui.screen.stop()
668
    raise