/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: Björn Påhlsson
  • Date: 2008-07-20 02:52:20 UTC
  • Revision ID: belorn@braxen-20080720025220-r5u0388uy9iu23h6
Added following support:
Pluginbased client handler
rewritten Mandos client
       Avahi instead of udp server discovery
       openpgp encrypted key support
Passprompt stand alone application for direct console input
Added logging for Mandos server

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