/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos-monitor

  • Committer: Teddy Hogeborn
  • Date: 2010-04-04 23:51:07 UTC
  • mfrom: (24.1.147 mandos)
  • Revision ID: teddy@fukt.bsnet.se-20100404235107-mjnuq6gjdeq030w7
MergeĀ fromĀ Belorn.

Show diffs side-by-side

added added

removed removed

Lines of Context:
4
4
from __future__ import division, absolute_import, with_statement
5
5
 
6
6
import sys
 
7
import os
7
8
import signal
8
9
 
 
10
import datetime
 
11
 
9
12
import urwid.curses_display
10
13
import urwid
11
14
 
16
19
 
17
20
import UserList
18
21
 
 
22
import locale
 
23
 
 
24
locale.setlocale(locale.LC_ALL, u'')
 
25
 
19
26
# Some useful constants
20
27
domain = 'se.bsnet.fukt'
21
28
server_interface = domain + '.Mandos'
35
42
    properties and calls a hook function when any of them are
36
43
    changed.
37
44
    """
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
 
            }
 
45
    def __init__(self, proxy_object=None, *args, **kwargs):
59
46
        self.proxy = proxy_object # Mandos Client proxy object
60
47
        
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",
 
48
        self.properties = dict()
 
49
        self.proxy.connect_to_signal(u"PropertyChanged",
68
50
                                     self.property_changed,
69
51
                                     client_interface,
70
52
                                     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())
 
53
 
 
54
        self.properties.update(
 
55
            self.proxy.GetAll(client_interface,
 
56
                              dbus_interface = dbus.PROPERTIES_IFACE))
79
57
        super(MandosClientPropertyCache, self).__init__(
80
 
            proxy_object=proxy_object,
81
 
            properties=properties, *args, **kwargs)
 
58
            proxy_object=proxy_object, *args, **kwargs)
82
59
    
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
60
    def property_changed(self, property=None, value=None):
99
61
        """This is called whenever we get a PropertyChanged signal
100
62
        It updates the changed property in the "properties" dict.
101
63
        """
102
 
        # Convert name and value
103
 
        property_name, cvalue = self.convert_property(property, value)
104
64
        # Update properties dict with new value
105
 
        self.properties[property_name] = cvalue
 
65
        self.properties[property] = value
106
66
 
107
67
 
108
68
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
110
70
    """
111
71
    
112
72
    def __init__(self, server_proxy_object=None, update_hook=None,
113
 
                 delete_hook=None, *args, **kwargs):
 
73
                 delete_hook=None, logger=None, *args, **kwargs):
114
74
        # Called on update
115
75
        self.update_hook = update_hook
116
76
        # Called on delete
117
77
        self.delete_hook = delete_hook
118
78
        # Mandos Server proxy object
119
79
        self.server_proxy_object = server_proxy_object
 
80
        # Logger
 
81
        self.logger = logger
120
82
        
121
83
        # The widget shown normally
122
 
        self._text_widget = urwid.Text("")
 
84
        self._text_widget = urwid.Text(u"")
123
85
        # The widget shown when we have focus
124
 
        self._focus_text_widget = urwid.Text("")
 
86
        self._focus_text_widget = urwid.Text(u"")
125
87
        super(MandosClientWidget, self).__init__(
126
88
            update_hook=update_hook, delete_hook=delete_hook,
127
89
            *args, **kwargs)
128
90
        self.update()
129
91
        self.opened = False
 
92
        self.proxy.connect_to_signal(u"CheckerCompleted",
 
93
                                     self.checker_completed,
 
94
                                     client_interface,
 
95
                                     byte_arrays=True)
 
96
        self.proxy.connect_to_signal(u"CheckerStarted",
 
97
                                     self.checker_started,
 
98
                                     client_interface,
 
99
                                     byte_arrays=True)
 
100
        self.proxy.connect_to_signal(u"GotSecret",
 
101
                                     self.got_secret,
 
102
                                     client_interface,
 
103
                                     byte_arrays=True)
 
104
        self.proxy.connect_to_signal(u"Rejected",
 
105
                                     self.rejected,
 
106
                                     client_interface,
 
107
                                     byte_arrays=True)
 
108
    
 
109
    def checker_completed(self, exitstatus, condition, command):
 
110
        if exitstatus == 0:
 
111
            self.logger(u'Checker for client %s (command "%s")'
 
112
                        u' was successful'
 
113
                        % (self.properties[u"name"], command))
 
114
            return
 
115
        if os.WIFEXITED(condition):
 
116
            self.logger(u'Checker for client %s (command "%s")'
 
117
                        u' failed with exit code %s'
 
118
                        % (self.properties[u"name"], command,
 
119
                           os.WEXITSTATUS(condition)))
 
120
            return
 
121
        if os.WIFSIGNALED(condition):
 
122
            self.logger(u'Checker for client %s (command "%s")'
 
123
                        u' was killed by signal %s'
 
124
                        % (self.properties[u"name"], command,
 
125
                           os.WTERMSIG(condition)))
 
126
            return
 
127
        if os.WCOREDUMP(condition):
 
128
            self.logger(u'Checker for client %s (command "%s")'
 
129
                        u' dumped core'
 
130
                        % (self.properties[u"name"], command))
 
131
        self.logger(u'Checker for client %s completed mysteriously')
 
132
    
 
133
    def checker_started(self, command):
 
134
        self.logger(u'Client %s started checker "%s"'
 
135
                    % (self.properties[u"name"], unicode(command)))
 
136
    
 
137
    def got_secret(self):
 
138
        self.logger(u'Client %s received its secret'
 
139
                    % self.properties[u"name"])
 
140
    
 
141
    def rejected(self):
 
142
        self.logger(u'Client %s was rejected'
 
143
                    % self.properties[u"name"])
130
144
    
131
145
    def selectable(self):
132
146
        """Make this a "selectable" widget.
156
170
                          }
157
171
        
158
172
        # Rebuild focus and non-focus widgets using current properties
159
 
        self._text = (u'name="%(name)s", enabled=%(enabled)s'
160
 
                      % self.properties)
 
173
        self._text = (u'%(name)s: %(enabled)s'
 
174
                      % { u"name": self.properties[u"name"],
 
175
                          u"enabled":
 
176
                              (u"enabled"
 
177
                               if self.properties[u"enabled"]
 
178
                               else u"DISABLED")})
161
179
        if not urwid.supports_unicode():
162
180
            self._text = self._text.encode("ascii", "replace")
163
 
        textlist = [(u"normal", u"BLƄRGH: "), (u"bold", self._text)]
 
181
        textlist = [(u"normal", self._text)]
164
182
        self._text_widget.set_text(textlist)
165
183
        self._focus_text_widget.set_text([(with_standout[text[0]],
166
184
                                           text[1])
191
209
            self.proxy.Enable()
192
210
        elif key == u"d" or key == u"-":
193
211
            self.proxy.Disable()
194
 
        elif key == u"r" or key == u"_":
 
212
        elif key == u"r" or key == u"_" or key == u"ctrl k":
195
213
            self.server_proxy_object.RemoveClient(self.proxy
196
214
                                                  .object_path)
197
215
        elif key == u"s":
198
216
            self.proxy.StartChecker()
199
 
        elif key == u"c":
 
217
        elif key == u"S":
200
218
            self.proxy.StopChecker()
201
 
        elif key == u"S":
 
219
        elif key == u"C":
202
220
            self.proxy.CheckedOK()
203
221
        # xxx
204
222
#         elif key == u"p" or key == "=":
222
240
            self.update()
223
241
 
224
242
 
 
243
class ConstrainedListBox(urwid.ListBox):
 
244
    """Like a normal urwid.ListBox, but will consume all "up" or
 
245
    "down" key presses, thus not allowing any containing widgets to
 
246
    use them as an excuse to shift focus away from this widget.
 
247
    """
 
248
    def keypress(self, (maxcol, maxrow), key):
 
249
        ret = super(ConstrainedListBox, self).keypress((maxcol, maxrow), key)
 
250
        if ret in (u"up", u"down"):
 
251
            return
 
252
        return ret
 
253
 
 
254
 
225
255
class UserInterface(object):
226
256
    """This is the entire user interface - the whole screen
227
257
    with boxes, lists of client widgets, etc.
228
258
    """
229
 
    def __init__(self):
230
 
        DBusGMainLoop(set_as_default=True )
 
259
    def __init__(self, max_log_length=1000):
 
260
        DBusGMainLoop(set_as_default=True)
231
261
        
232
262
        self.screen = urwid.curses_display.Screen()
233
263
        
251
281
                                          u"standout")),
252
282
                ))
253
283
        
 
284
        if urwid.supports_unicode():
 
285
            self.divider = u"ā”€" # \u2500
 
286
            #self.divider = u"ā”" # \u2501
 
287
        else:
 
288
            #self.divider = u"-" # \u002d
 
289
            self.divider = u"_" # \u005f
 
290
        
254
291
        self.screen.start()
255
292
        
256
293
        self.size = self.screen.get_cols_rows()
257
294
        
258
295
        self.clients = urwid.SimpleListWalker([])
259
296
        self.clients_dict = {}
260
 
        self.topwidget = urwid.LineBox(urwid.ListBox(self.clients))
261
 
        #self.topwidget = urwid.ListBox(clients)
 
297
        
 
298
        # We will add Text widgets to this list
 
299
        self.log = []
 
300
        self.max_log_length = max_log_length
 
301
        
 
302
        # We keep a reference to the log widget so we can remove it
 
303
        # from the ListWalker without it getting destroyed
 
304
        self.logbox = ConstrainedListBox(self.log)
 
305
        
 
306
        # This keeps track of whether self.uilist currently has
 
307
        # self.logbox in it or not
 
308
        self.log_visible = True
 
309
        self.log_wrap = u"any"
 
310
        
 
311
        self.rebuild()
 
312
        self.log_message_raw((u"bold",
 
313
                              u"Mandos Monitor version " + version))
 
314
        self.log_message_raw((u"bold",
 
315
                              u"q: Quit  ?: Help"))
262
316
        
263
317
        self.busname = domain + '.Mandos'
264
318
        self.main_loop = gobject.MainLoop()
275
329
            mandos_clients = dbus.Dictionary()
276
330
        
277
331
        (self.mandos_serv
278
 
         .connect_to_signal("ClientRemoved",
 
332
         .connect_to_signal(u"ClientRemoved",
279
333
                            self.find_and_remove_client,
280
334
                            dbus_interface=server_interface,
281
335
                            byte_arrays=True))
282
336
        (self.mandos_serv
283
 
         .connect_to_signal("ClientAdded",
 
337
         .connect_to_signal(u"ClientAdded",
284
338
                            self.add_new_client,
285
339
                            dbus_interface=server_interface,
286
340
                            byte_arrays=True))
287
 
        for path, client in (mandos_clients.iteritems()):
 
341
        (self.mandos_serv
 
342
         .connect_to_signal(u"ClientNotFound",
 
343
                            self.client_not_found,
 
344
                            dbus_interface=server_interface,
 
345
                            byte_arrays=True))
 
346
        for path, client in mandos_clients.iteritems():
288
347
            client_proxy_object = self.bus.get_object(self.busname,
289
348
                                                      path)
290
349
            self.add_client(MandosClientWidget(server_proxy_object
295
354
                                               update_hook
296
355
                                               =self.refresh,
297
356
                                               delete_hook
298
 
                                               =self.remove_client),
 
357
                                               =self.remove_client,
 
358
                                               logger
 
359
                                               =self.log_message),
299
360
                            path=path)
300
361
    
 
362
    def client_not_found(self, fingerprint, address):
 
363
        self.log_message((u"Client with address %s and fingerprint %s"
 
364
                          u" could not be found" % (address,
 
365
                                                    fingerprint)))
 
366
    
 
367
    def rebuild(self):
 
368
        """This rebuilds the User Interface.
 
369
        Call this when the widget layout needs to change"""
 
370
        self.uilist = []
 
371
        #self.uilist.append(urwid.ListBox(self.clients))
 
372
        self.uilist.append(urwid.Frame(ConstrainedListBox(self.clients),
 
373
                                       #header=urwid.Divider(),
 
374
                                       header=None,
 
375
                                       footer=urwid.Divider(div_char=self.divider)))
 
376
        if self.log_visible:
 
377
            self.uilist.append(self.logbox)
 
378
            pass
 
379
        self.topwidget = urwid.Pile(self.uilist)
 
380
    
 
381
    def log_message(self, message):
 
382
        timestamp = datetime.datetime.now().isoformat()
 
383
        self.log_message_raw(timestamp + u": " + message)
 
384
    
 
385
    def log_message_raw(self, markup):
 
386
        """Add a log message to the log buffer."""
 
387
        self.log.append(urwid.Text(markup, wrap=self.log_wrap))
 
388
        if (self.max_log_length
 
389
            and len(self.log) > self.max_log_length):
 
390
            del self.log[0:len(self.log)-self.max_log_length-1]
 
391
        self.logbox.set_focus(len(self.logbox.body.contents),
 
392
                              coming_from=u"above")
 
393
        self.refresh()
 
394
    
 
395
    def toggle_log_display(self):
 
396
        """Toggle visibility of the log buffer."""
 
397
        self.log_visible = not self.log_visible
 
398
        self.rebuild()
 
399
        self.log_message(u"Log visibility changed to: "
 
400
                         + unicode(self.log_visible))
 
401
    
 
402
    def change_log_display(self):
 
403
        """Change type of log display.
 
404
        Currently, this toggles wrapping of text lines."""
 
405
        if self.log_wrap == u"clip":
 
406
            self.log_wrap = u"any"
 
407
        else:
 
408
            self.log_wrap = u"clip"
 
409
        for textwidget in self.log:
 
410
            textwidget.set_wrap_mode(self.log_wrap)
 
411
        self.log_message(u"Wrap mode: " + self.log_wrap)
 
412
    
301
413
    def find_and_remove_client(self, path, name):
302
414
        """Find an client from its object path and remove it.
303
415
        
310
422
            return
311
423
        self.remove_client(client, path)
312
424
    
313
 
    def add_new_client(self, path, properties):
 
425
    def add_new_client(self, path):
314
426
        client_proxy_object = self.bus.get_object(self.busname, path)
315
427
        self.add_client(MandosClientWidget(server_proxy_object
316
428
                                           =self.mandos_serv,
317
429
                                           proxy_object
318
430
                                           =client_proxy_object,
319
 
                                           properties=properties,
320
431
                                           update_hook
321
432
                                           =self.refresh,
322
433
                                           delete_hook
323
 
                                           =self.remove_client),
 
434
                                           =self.remove_client,
 
435
                                           logger
 
436
                                           =self.log_message),
324
437
                        path=path)
325
438
    
326
439
    def add_client(self, client, path=None):
336
449
        if path is None:
337
450
            path = client.proxy.object_path
338
451
        del self.clients_dict[path]
 
452
        if not self.clients_dict:
 
453
            # Work around bug in Urwid 0.9.8.3 - if a SimpleListWalker
 
454
            # is completely emptied, we need to recreate it.
 
455
            self.clients = urwid.SimpleListWalker([])
 
456
            self.rebuild()
339
457
        self.refresh()
340
458
    
341
459
    def refresh(self):
360
478
    
361
479
    def process_input(self, source, condition):
362
480
        keys = self.screen.get_input()
363
 
        translations = { u"j": u"down",
364
 
                         u"k": u"up",
 
481
        translations = { u"ctrl n": u"down",      # Emacs
 
482
                         u"ctrl p": u"up",        # Emacs
 
483
                         u"ctrl v": u"page down", # Emacs
 
484
                         u"meta v": u"page up",   # Emacs
 
485
                         u" ": u"page down",      # less
 
486
                         u"f": u"page down",      # less
 
487
                         u"b": u"page up",        # less
 
488
                         u"j": u"down",           # vi
 
489
                         u"k": u"up",             # vi
365
490
                         }
366
491
        for key in keys:
367
492
            try:
375
500
            elif key == u"window resize":
376
501
                self.size = self.screen.get_cols_rows()
377
502
                self.refresh()
378
 
            elif key == " ":
379
 
                self.refresh()
 
503
            elif key == u"\f":  # Ctrl-L
 
504
                self.refresh()
 
505
            elif key == u"l" or key == u"D":
 
506
                self.toggle_log_display()
 
507
                self.refresh()
 
508
            elif key == u"w" or key == u"i":
 
509
                self.change_log_display()
 
510
                self.refresh()
 
511
            elif key == u"?" or key == u"f1" or key == u"esc":
 
512
                if not self.log_visible:
 
513
                    self.log_visible = True
 
514
                    self.rebuild()
 
515
                self.log_message_raw((u"bold",
 
516
                                      u"  ".
 
517
                                      join((u"q: Quit",
 
518
                                            u"?: Help",
 
519
                                            u"l: Log window toggle",
 
520
                                            u"TAB: Switch window",
 
521
                                            u"w: Wrap (log)"))))
 
522
                self.log_message_raw((u"bold",
 
523
                                      u"  "
 
524
                                      .join((u"Clients:",
 
525
                                             u"e: Enable",
 
526
                                             u"d: Disable",
 
527
                                             u"r: Remove",
 
528
                                             u"s: Start new checker",
 
529
                                             u"S: Stop checker",
 
530
                                             u"C: Checker OK"))))
 
531
                self.refresh()
 
532
            elif key == u"tab":
 
533
                if self.topwidget.get_focus() is self.logbox:
 
534
                    self.topwidget.set_focus(0)
 
535
                else:
 
536
                    self.topwidget.set_focus(self.logbox)
 
537
                self.refresh()
 
538
            #elif (key == u"end" or key == u"meta >" or key == u"G"
 
539
            #      or key == u">"):
 
540
            #    pass            # xxx end-of-buffer
 
541
            #elif (key == u"home" or key == u"meta <" or key == u"g"
 
542
            #      or key == u"<"):
 
543
            #    pass            # xxx beginning-of-buffer
 
544
            #elif key == u"ctrl e" or key == u"$":
 
545
            #    pass            # xxx move-end-of-line
 
546
            #elif key == u"ctrl a" or key == u"^":
 
547
            #    pass            # xxx move-beginning-of-line
 
548
            #elif key == u"ctrl b" or key == u"meta (" or key == u"h":
 
549
            #    pass            # xxx left
 
550
            #elif key == u"ctrl f" or key == u"meta )" or key == u"l":
 
551
            #    pass            # xxx right
 
552
            #elif key == u"a":
 
553
            #    pass            # scroll up log
 
554
            #elif key == u"z":
 
555
            #    pass            # scroll down log
380
556
            elif self.topwidget.selectable():
381
557
                self.topwidget.keypress(self.size, key)
382
558
                self.refresh()
385
561
ui = UserInterface()
386
562
try:
387
563
    ui.run()
388
 
except:
 
564
except Exception, e:
 
565
    ui.log_message(unicode(e))
389
566
    ui.screen.stop()
390
567
    raise