/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: 2009-02-18 13:19:26 UTC
  • Revision ID: teddy@fukt.bsnet.se-20090218131926-af671ydvxl3wt4w6
* initramfs-tools-hook: Changed to use "cut" instead of depending on
                        "awk" being GNU awk.

* mandos.xml (BUGS): Improved grammar.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python
2
 
# -*- mode: python; coding: utf-8 -*-
3
 
 
4
 
from __future__ import division, absolute_import, with_statement
5
 
 
6
 
import sys
7
 
import signal
8
 
 
9
 
import urwid.curses_display
10
 
import urwid
11
 
 
12
 
from dbus.mainloop.glib import DBusGMainLoop
13
 
import gobject
14
 
 
15
 
import dbus
16
 
 
17
 
import UserList
18
 
 
19
 
# Some useful constants
20
 
domain = 'se.bsnet.fukt'
21
 
server_interface = domain + '.Mandos'
22
 
client_interface = domain + '.Mandos.Client'
23
 
version = "1.0.14"
24
 
 
25
 
# Always run in monochrome mode
26
 
urwid.curses_display.curses.has_colors = lambda : False
27
 
 
28
 
# Urwid doesn't support blinking, but we want it.  Since we have no
29
 
# use for underline on its own, we make underline also always blink.
30
 
urwid.curses_display.curses.A_UNDERLINE |= (
31
 
    urwid.curses_display.curses.A_BLINK)
32
 
 
33
 
class MandosClientPropertyCache(object):
34
 
    """This wraps a Mandos Client D-Bus proxy object, caches the
35
 
    properties and calls a hook function when any of them are
36
 
    changed.
37
 
    """
38
 
    def __init__(self, proxy_object=None, properties=None, *args,
39
 
                 **kwargs):
40
 
        # Type conversion mapping
41
 
        self.type_map = {
42
 
            dbus.ObjectPath: unicode,
43
 
            dbus.ByteArray: str,
44
 
            dbus.Signature: unicode,
45
 
            dbus.Byte: chr,
46
 
            dbus.Int16: int,
47
 
            dbus.UInt16: int,
48
 
            dbus.Int32: int,
49
 
            dbus.UInt32: int,
50
 
            dbus.Int64: int,
51
 
            dbus.UInt64: int,
52
 
            dbus.Dictionary: dict,
53
 
            dbus.Array: list,
54
 
            dbus.String: unicode,
55
 
            dbus.Boolean: bool,
56
 
            dbus.Double: float,
57
 
            dbus.Struct: tuple,
58
 
            }
59
 
        self.proxy = proxy_object # Mandos Client proxy object
60
 
        
61
 
        if properties is None:
62
 
            self.properties = dict()
63
 
        else:
64
 
            self.properties = dict(self.convert_property(prop, val)
65
 
                                   for prop, val in
66
 
                                   properties.iteritems())
67
 
        self.proxy.connect_to_signal("PropertyChanged",
68
 
                                     self.property_changed,
69
 
                                     client_interface,
70
 
                                     byte_arrays=True)
71
 
        
72
 
        if properties is None:
73
 
            self.properties.update(
74
 
                self.convert_property(prop, val)
75
 
                for prop, val in
76
 
                self.proxy.GetAll(client_interface,
77
 
                                  dbus_interface =
78
 
                                  dbus.PROPERTIES_IFACE).iteritems())
79
 
        super(MandosClientPropertyCache, self).__init__(
80
 
            proxy_object=proxy_object,
81
 
            properties=properties, *args, **kwargs)
82
 
    
83
 
    def convert_property(self, property, value):
84
 
        """This converts the arguments from a D-Bus signal, which are
85
 
        D-Bus types, into normal Python types, using a conversion
86
 
        function from "self.type_map".
87
 
        """
88
 
        property_name = unicode(property) # Always a dbus.String
89
 
        if isinstance(value, dbus.UTF8String):
90
 
            # Should not happen, but prepare for it anyway
91
 
            value = dbus.String(str(value).decode("utf-8"))
92
 
        try:
93
 
            convfunc = self.type_map[type(value)]
94
 
        except KeyError:
95
 
            # Unknown type, return unmodified
96
 
            return property_name, value
97
 
        return property_name, convfunc(value)
98
 
    def property_changed(self, property=None, value=None):
99
 
        """This is called whenever we get a PropertyChanged signal
100
 
        It updates the changed property in the "properties" dict.
101
 
        """
102
 
        # Convert name and value
103
 
        property_name, cvalue = self.convert_property(property, value)
104
 
        # Update properties dict with new value
105
 
        self.properties[property_name] = cvalue
106
 
 
107
 
 
108
 
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
109
 
    """A Mandos Client which is visible on the screen.
110
 
    """
111
 
    
112
 
    def __init__(self, server_proxy_object=None, update_hook=None,
113
 
                 delete_hook=None, *args, **kwargs):
114
 
        # Called on update
115
 
        self.update_hook = update_hook
116
 
        # Called on delete
117
 
        self.delete_hook = delete_hook
118
 
        # Mandos Server proxy object
119
 
        self.server_proxy_object = server_proxy_object
120
 
        
121
 
        # The widget shown normally
122
 
        self._text_widget = urwid.Text("")
123
 
        # The widget shown when we have focus
124
 
        self._focus_text_widget = urwid.Text("")
125
 
        super(MandosClientWidget, self).__init__(
126
 
            update_hook=update_hook, delete_hook=delete_hook,
127
 
            *args, **kwargs)
128
 
        self.update()
129
 
        self.opened = False
130
 
    
131
 
    def selectable(self):
132
 
        """Make this a "selectable" widget.
133
 
        This overrides the method from urwid.FlowWidget."""
134
 
        return True
135
 
    
136
 
    def rows(self, (maxcol,), focus=False):
137
 
        """How many rows this widget will occupy might depend on
138
 
        whether we have focus or not.
139
 
        This overrides the method from urwid.FlowWidget"""
140
 
        return self.current_widget(focus).rows((maxcol,), focus=focus)
141
 
    
142
 
    def current_widget(self, focus=False):
143
 
        if focus or self.opened:
144
 
            return self._focus_widget
145
 
        return self._widget
146
 
    
147
 
    def update(self):
148
 
        "Called when what is visible on the screen should be updated."
149
 
        # How to add standout mode to a style
150
 
        with_standout = { u"normal": u"standout",
151
 
                          u"bold": u"bold-standout",
152
 
                          u"underline-blink":
153
 
                              u"underline-blink-standout",
154
 
                          u"bold-underline-blink":
155
 
                              u"bold-underline-blink-standout",
156
 
                          }
157
 
        
158
 
        # Rebuild focus and non-focus widgets using current properties
159
 
        self._text = (u'name="%(name)s", enabled=%(enabled)s'
160
 
                      % self.properties)
161
 
        if not urwid.supports_unicode():
162
 
            self._text = self._text.encode("ascii", "replace")
163
 
        textlist = [(u"normal", u"BLÄRGH: "), (u"bold", self._text)]
164
 
        self._text_widget.set_text(textlist)
165
 
        self._focus_text_widget.set_text([(with_standout[text[0]],
166
 
                                           text[1])
167
 
                                          if isinstance(text, tuple)
168
 
                                          else text
169
 
                                          for text in textlist])
170
 
        self._widget = self._text_widget
171
 
        self._focus_widget = urwid.AttrWrap(self._focus_text_widget,
172
 
                                            "standout")
173
 
        # Run update hook, if any
174
 
        if self.update_hook is not None:
175
 
            self.update_hook()
176
 
    
177
 
    def delete(self):
178
 
        if self.delete_hook is not None:
179
 
            self.delete_hook(self)
180
 
    
181
 
    def render(self, (maxcol,), focus=False):
182
 
        """Render differently if we have focus.
183
 
        This overrides the method from urwid.FlowWidget"""
184
 
        return self.current_widget(focus).render((maxcol,),
185
 
                                                 focus=focus)
186
 
    
187
 
    def keypress(self, (maxcol,), key):
188
 
        """Handle keys.
189
 
        This overrides the method from urwid.FlowWidget"""
190
 
        if key == u"e" or key == u"+":
191
 
            self.proxy.Enable()
192
 
        elif key == u"d" or key == u"-":
193
 
            self.proxy.Disable()
194
 
        elif key == u"r" or key == u"_":
195
 
            self.server_proxy_object.RemoveClient(self.proxy
196
 
                                                  .object_path)
197
 
        elif key == u"s":
198
 
            self.proxy.StartChecker()
199
 
        elif key == u"c":
200
 
            self.proxy.StopChecker()
201
 
        elif key == u"S":
202
 
            self.proxy.CheckedOK()
203
 
        # xxx
204
 
#         elif key == u"p" or key == "=":
205
 
#             self.proxy.pause()
206
 
#         elif key == u"u" or key == ":":
207
 
#             self.proxy.unpause()
208
 
#         elif key == u"RET":
209
 
#             self.open()
210
 
        else:
211
 
            return key
212
 
    
213
 
    def property_changed(self, property=None, value=None,
214
 
                         *args, **kwargs):
215
 
        """Call self.update() if old value is not new value.
216
 
        This overrides the method from MandosClientPropertyCache"""
217
 
        property_name = unicode(property)
218
 
        old_value = self.properties.get(property_name)
219
 
        super(MandosClientWidget, self).property_changed(
220
 
            property=property, value=value, *args, **kwargs)
221
 
        if self.properties.get(property_name) != old_value:
222
 
            self.update()
223
 
 
224
 
 
225
 
class UserInterface(object):
226
 
    """This is the entire user interface - the whole screen
227
 
    with boxes, lists of client widgets, etc.
228
 
    """
229
 
    def __init__(self):
230
 
        DBusGMainLoop(set_as_default=True )
231
 
        
232
 
        self.screen = urwid.curses_display.Screen()
233
 
        
234
 
        self.screen.register_palette((
235
 
                (u"normal",
236
 
                 u"default", u"default", None),
237
 
                (u"bold",
238
 
                 u"default", u"default", u"bold"),
239
 
                (u"underline-blink",
240
 
                 u"default", u"default", u"underline"),
241
 
                (u"standout",
242
 
                 u"default", u"default", u"standout"),
243
 
                (u"bold-underline-blink",
244
 
                 u"default", u"default", (u"bold", u"underline")),
245
 
                (u"bold-standout",
246
 
                 u"default", u"default", (u"bold", u"standout")),
247
 
                (u"underline-blink-standout",
248
 
                 u"default", u"default", (u"underline", u"standout")),
249
 
                (u"bold-underline-blink-standout",
250
 
                 u"default", u"default", (u"bold", u"underline",
251
 
                                          u"standout")),
252
 
                ))
253
 
        
254
 
        self.screen.start()
255
 
        
256
 
        self.size = self.screen.get_cols_rows()
257
 
        
258
 
        self.clients = urwid.SimpleListWalker([])
259
 
        self.clients_dict = {}
260
 
        self.topwidget = urwid.LineBox(urwid.ListBox(self.clients))
261
 
        #self.topwidget = urwid.ListBox(clients)
262
 
        
263
 
        self.busname = domain + '.Mandos'
264
 
        self.main_loop = gobject.MainLoop()
265
 
        self.bus = dbus.SystemBus()
266
 
        mandos_dbus_objc = self.bus.get_object(
267
 
            self.busname, u"/", follow_name_owner_changes=True)
268
 
        self.mandos_serv = dbus.Interface(mandos_dbus_objc,
269
 
                                          dbus_interface
270
 
                                          = server_interface)
271
 
        try:
272
 
            mandos_clients = (self.mandos_serv
273
 
                              .GetAllClientsWithProperties())
274
 
        except dbus.exceptions.DBusException:
275
 
            mandos_clients = dbus.Dictionary()
276
 
        
277
 
        (self.mandos_serv
278
 
         .connect_to_signal("ClientRemoved",
279
 
                            self.find_and_remove_client,
280
 
                            dbus_interface=server_interface,
281
 
                            byte_arrays=True))
282
 
        (self.mandos_serv
283
 
         .connect_to_signal("ClientAdded",
284
 
                            self.add_new_client,
285
 
                            dbus_interface=server_interface,
286
 
                            byte_arrays=True))
287
 
        for path, client in (mandos_clients.iteritems()):
288
 
            client_proxy_object = self.bus.get_object(self.busname,
289
 
                                                      path)
290
 
            self.add_client(MandosClientWidget(server_proxy_object
291
 
                                               =self.mandos_serv,
292
 
                                               proxy_object
293
 
                                               =client_proxy_object,
294
 
                                               properties=client,
295
 
                                               update_hook
296
 
                                               =self.refresh,
297
 
                                               delete_hook
298
 
                                               =self.remove_client),
299
 
                            path=path)
300
 
    
301
 
    def find_and_remove_client(self, path, name):
302
 
        """Find an client from its object path and remove it.
303
 
        
304
 
        This is connected to the ClientRemoved signal from the
305
 
        Mandos server object."""
306
 
        try:
307
 
            client = self.clients_dict[path]
308
 
        except KeyError:
309
 
            # not found?
310
 
            return
311
 
        self.remove_client(client, path)
312
 
    
313
 
    def add_new_client(self, path, properties):
314
 
        client_proxy_object = self.bus.get_object(self.busname, path)
315
 
        self.add_client(MandosClientWidget(server_proxy_object
316
 
                                           =self.mandos_serv,
317
 
                                           proxy_object
318
 
                                           =client_proxy_object,
319
 
                                           properties=properties,
320
 
                                           update_hook
321
 
                                           =self.refresh,
322
 
                                           delete_hook
323
 
                                           =self.remove_client),
324
 
                        path=path)
325
 
    
326
 
    def add_client(self, client, path=None):
327
 
        self.clients.append(client)
328
 
        if path is None:
329
 
            path = client.proxy.object_path
330
 
        self.clients_dict[path] = client
331
 
        self.clients.sort(None, lambda c: c.properties[u"name"])
332
 
        self.refresh()
333
 
    
334
 
    def remove_client(self, client, path=None):
335
 
        self.clients.remove(client)
336
 
        if path is None:
337
 
            path = client.proxy.object_path
338
 
        del self.clients_dict[path]
339
 
        self.refresh()
340
 
    
341
 
    def refresh(self):
342
 
        """Redraw the screen"""
343
 
        canvas = self.topwidget.render(self.size, focus=True)
344
 
        self.screen.draw_screen(self.size, canvas)
345
 
    
346
 
    def run(self):
347
 
        """Start the main loop and exit when it's done."""
348
 
        self.refresh()
349
 
        self._input_callback_tag = (gobject.io_add_watch
350
 
                                    (sys.stdin.fileno(),
351
 
                                     gobject.IO_IN,
352
 
                                     self.process_input))
353
 
        self.main_loop.run()
354
 
        # Main loop has finished, we should close everything now
355
 
        gobject.source_remove(self._input_callback_tag)
356
 
        self.screen.stop()
357
 
    
358
 
    def stop(self):
359
 
        self.main_loop.quit()
360
 
    
361
 
    def process_input(self, source, condition):
362
 
        keys = self.screen.get_input()
363
 
        translations = { u"j": u"down",
364
 
                         u"k": u"up",
365
 
                         }
366
 
        for key in keys:
367
 
            try:
368
 
                key = translations[key]
369
 
            except KeyError:    # :-)
370
 
                pass
371
 
            
372
 
            if key == u"q" or key == u"Q":
373
 
                self.stop()
374
 
                break
375
 
            elif key == u"window resize":
376
 
                self.size = self.screen.get_cols_rows()
377
 
                self.refresh()
378
 
            elif key == " ":
379
 
                self.refresh()
380
 
            elif self.topwidget.selectable():
381
 
                self.topwidget.keypress(self.size, key)
382
 
                self.refresh()
383
 
        return True
384
 
 
385
 
ui = UserInterface()
386
 
try:
387
 
    ui.run()
388
 
except:
389
 
    ui.screen.stop()
390
 
    raise