/mandos/release

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/release
237.2.167 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
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
7
import os
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
8
import signal
9
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
10
import datetime
11
237.2.167 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
237.2.174 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
237.2.167 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"
237.2.167 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
237.2.180 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
237.2.167 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
    """
237.2.174 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
64
    def __init__(self, proxy_object=None, *args, **kwargs):
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
65
        self.proxy = proxy_object # Mandos Client proxy object
66
        
237.2.174 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
67
        self.properties = dict()
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
68
        self.proxy.connect_to_signal(u"PropertyChanged",
237.2.167 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
        
237.2.174 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)
237.2.167 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
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
86
        self.properties[property] = value
237.2.167 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,
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
94
                 delete_hook=None, logger=None, *args, **kwargs):
237.2.167 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
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
101
        # Logger
102
        self.logger = logger
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
103
        
237.2.180 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
        
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
107
        # The widget shown normally
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
108
        self._text_widget = urwid.Text(u"")
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
109
        # The widget shown when we have focus
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
110
        self._focus_text_widget = urwid.Text(u"")
237.2.167 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
237.2.172 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)
237.2.172 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)
237.2.180 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))
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
150
    
151
    def checker_completed(self, exitstatus, condition, command):
152
        if exitstatus == 0:
237.2.180 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
237.2.172 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))
237.2.180 by Teddy Hogeborn
* mandos (AvahiService.entry_group_state_changed): Better debug log
160
            self.update()
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
161
            return
237.2.180 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))
237.2.172 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)))
237.2.180 by Teddy Hogeborn
* mandos (AvahiService.entry_group_state_changed): Better debug log
173
        elif os.WIFSIGNALED(condition):
237.2.172 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)))
237.2.180 by Teddy Hogeborn
* mandos (AvahiService.entry_group_state_changed): Better debug log
178
        elif os.WCOREDUMP(condition):
237.2.172 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))
237.2.180 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()
237.2.172 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
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
190
    
191
    def got_secret(self):
24.1.159 by Björn Påhlsson
added approval to mandos-ctl
192
        self.last_checker_failed = False
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
193
        self.logger(u'Client %s received its secret'
194
                    % self.properties[u"name"])
195
    
24.1.153 by Björn Påhlsson
early commit to ease todays coding
196
    def need_approval(self, timeout, default):
197
        if not default:
198
            message = u'Client %s needs approval within %s seconds'
199
        else:
200
            message = u'Client %s will get its secret in %s seconds'
201
        self.logger(message
202
                    % (self.properties[u"name"], timeout/1000))
203
    
204
    def rejected(self, reason):
205
        self.logger(u'Client %s was rejected; reason: %s'
206
                    % (self.properties[u"name"], reason))
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
207
    
208
    def selectable(self):
209
        """Make this a "selectable" widget.
210
        This overrides the method from urwid.FlowWidget."""
211
        return True
212
    
213
    def rows(self, (maxcol,), focus=False):
214
        """How many rows this widget will occupy might depend on
215
        whether we have focus or not.
216
        This overrides the method from urwid.FlowWidget"""
217
        return self.current_widget(focus).rows((maxcol,), focus=focus)
218
    
219
    def current_widget(self, focus=False):
220
        if focus or self.opened:
221
            return self._focus_widget
222
        return self._widget
223
    
224
    def update(self):
225
        "Called when what is visible on the screen should be updated."
226
        # How to add standout mode to a style
227
        with_standout = { u"normal": u"standout",
228
                          u"bold": u"bold-standout",
229
                          u"underline-blink":
230
                              u"underline-blink-standout",
231
                          u"bold-underline-blink":
232
                              u"bold-underline-blink-standout",
233
                          }
24.1.154 by Björn Påhlsson
merge
234
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
235
        # Rebuild focus and non-focus widgets using current properties
24.1.154 by Björn Påhlsson
merge
236
237
        # Base part of a client. Name!
24.1.156 by Björn Påhlsson
merge
238
        base = (u'%(name)s: '
24.1.154 by Björn Påhlsson
merge
239
                      % {u"name": self.properties[u"name"]})
24.1.156 by Björn Påhlsson
merge
240
        if not self.properties[u"enabled"]:
241
            message = u"DISABLED"
24.1.159 by Björn Påhlsson
added approval to mandos-ctl
242
        elif self.properties[u"approved_pending"]:
243
            if self.properties[u"approved_by_default"]:
244
                message = u"Connection established to client. (d)eny?"
245
            else:
246
                message = u"Seeks approval to send secret. (a)pprove?"
24.1.156 by Björn Påhlsson
merge
247
        elif self.last_checker_failed:
248
            timeout = datetime.timedelta(milliseconds
249
                                         = self.properties[u"timeout"])
250
            last_ok = isoformat_to_datetime(
251
                max((self.properties["last_checked_ok"]
252
                     or self.properties["created"]),
253
                    self.properties[u"last_enabled"]))
254
            timer = timeout - (datetime.datetime.utcnow() - last_ok)
255
            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.
256
                           % unicode(timer).rsplit(".", 1)[0])
24.1.154 by Björn Påhlsson
merge
257
        else:
24.1.156 by Björn Påhlsson
merge
258
            message = u"enabled"
259
        self._text = "%s%s" % (base, message)
260
            
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
261
        if not urwid.supports_unicode():
262
            self._text = self._text.encode("ascii", "replace")
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
263
        textlist = [(u"normal", self._text)]
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
264
        self._text_widget.set_text(textlist)
265
        self._focus_text_widget.set_text([(with_standout[text[0]],
266
                                           text[1])
267
                                          if isinstance(text, tuple)
268
                                          else text
269
                                          for text in textlist])
270
        self._widget = self._text_widget
271
        self._focus_widget = urwid.AttrWrap(self._focus_text_widget,
272
                                            "standout")
273
        # Run update hook, if any
274
        if self.update_hook is not None:
275
            self.update_hook()
276
    
237.2.180 by Teddy Hogeborn
* mandos (AvahiService.entry_group_state_changed): Better debug log
277
    def update_timer(self):
278
        "called by gobject"
279
        self.update()
280
        return True             # Keep calling this
281
    
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
282
    def delete(self):
237.2.180 by Teddy Hogeborn
* mandos (AvahiService.entry_group_state_changed): Better debug log
283
        if self._update_timer_callback_tag is not None:
284
            gobject.source_remove(self._update_timer_callback_tag)
285
            self._update_timer_callback_tag = None
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
286
        if self.delete_hook is not None:
287
            self.delete_hook(self)
288
    
289
    def render(self, (maxcol,), focus=False):
290
        """Render differently if we have focus.
291
        This overrides the method from urwid.FlowWidget"""
292
        return self.current_widget(focus).render((maxcol,),
293
                                                 focus=focus)
294
    
295
    def keypress(self, (maxcol,), key):
296
        """Handle keys.
297
        This overrides the method from urwid.FlowWidget"""
24.1.154 by Björn Påhlsson
merge
298
        if key == u"+":
299
            self.proxy.Enable(dbus_interface = client_interface)
300
        elif key == u"-":
301
            self.proxy.Disable(dbus_interface = client_interface)
302
        elif key == u"a":
303
            self.proxy.Approve(dbus.Boolean(True, variant_level=1),
304
                               dbus_interface = client_interface)
305
        elif key == u"d":
306
            self.proxy.Approve(dbus.Boolean(False, variant_level=1),
307
                                  dbus_interface = client_interface)
237.2.171 by Teddy Hogeborn
* debian/rules: Only set BROKEN_PIE if binutils is a specific range of
308
        elif key == u"r" or key == u"_" or key == u"ctrl k":
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
309
            self.server_proxy_object.RemoveClient(self.proxy
310
                                                  .object_path)
311
        elif key == u"s":
24.1.154 by Björn Påhlsson
merge
312
            self.proxy.StartChecker(dbus_interface = client_interface)
237.2.170 by Teddy Hogeborn
* mandos-monitor (MandosClientWidget): Change "StopChecker" key to "S"
313
        elif key == u"S":
24.1.154 by Björn Påhlsson
merge
314
            self.proxy.StopChecker(dbus_interface = client_interface)
237.2.170 by Teddy Hogeborn
* mandos-monitor (MandosClientWidget): Change "StopChecker" key to "S"
315
        elif key == u"C":
24.1.154 by Björn Påhlsson
merge
316
            self.proxy.CheckedOK(dbus_interface = client_interface)
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
317
        # xxx
318
#         elif key == u"p" or key == "=":
319
#             self.proxy.pause()
320
#         elif key == u"u" or key == ":":
321
#             self.proxy.unpause()
322
#         elif key == u"RET":
323
#             self.open()
24.1.154 by Björn Påhlsson
merge
324
#        elif key == u"+":
325
#            self.proxy.Approve(True)
326
#        elif key == u"-":
327
#            self.proxy.Approve(False)
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
328
        else:
329
            return key
330
    
331
    def property_changed(self, property=None, value=None,
332
                         *args, **kwargs):
333
        """Call self.update() if old value is not new value.
334
        This overrides the method from MandosClientPropertyCache"""
335
        property_name = unicode(property)
336
        old_value = self.properties.get(property_name)
337
        super(MandosClientWidget, self).property_changed(
338
            property=property, value=value, *args, **kwargs)
339
        if self.properties.get(property_name) != old_value:
340
            self.update()
341
342
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
343
class ConstrainedListBox(urwid.ListBox):
344
    """Like a normal urwid.ListBox, but will consume all "up" or
345
    "down" key presses, thus not allowing any containing widgets to
346
    use them as an excuse to shift focus away from this widget.
347
    """
348
    def keypress(self, (maxcol, maxrow), key):
349
        ret = super(ConstrainedListBox, self).keypress((maxcol, maxrow), key)
350
        if ret in (u"up", u"down"):
351
            return
352
        return ret
353
354
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
355
class UserInterface(object):
356
    """This is the entire user interface - the whole screen
357
    with boxes, lists of client widgets, etc.
358
    """
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
359
    def __init__(self, max_log_length=1000):
360
        DBusGMainLoop(set_as_default=True)
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
361
        
362
        self.screen = urwid.curses_display.Screen()
363
        
364
        self.screen.register_palette((
365
                (u"normal",
366
                 u"default", u"default", None),
367
                (u"bold",
368
                 u"default", u"default", u"bold"),
369
                (u"underline-blink",
370
                 u"default", u"default", u"underline"),
371
                (u"standout",
372
                 u"default", u"default", u"standout"),
373
                (u"bold-underline-blink",
374
                 u"default", u"default", (u"bold", u"underline")),
375
                (u"bold-standout",
376
                 u"default", u"default", (u"bold", u"standout")),
377
                (u"underline-blink-standout",
378
                 u"default", u"default", (u"underline", u"standout")),
379
                (u"bold-underline-blink-standout",
380
                 u"default", u"default", (u"bold", u"underline",
381
                                          u"standout")),
382
                ))
383
        
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
384
        if urwid.supports_unicode():
237.2.171 by Teddy Hogeborn
* debian/rules: Only set BROKEN_PIE if binutils is a specific range of
385
            self.divider = u"─" # \u2500
386
            #self.divider = u"━" # \u2501
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
387
        else:
388
            #self.divider = u"-" # \u002d
389
            self.divider = u"_" # \u005f
390
        
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
391
        self.screen.start()
392
        
393
        self.size = self.screen.get_cols_rows()
394
        
395
        self.clients = urwid.SimpleListWalker([])
396
        self.clients_dict = {}
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
397
        
398
        # We will add Text widgets to this list
399
        self.log = []
400
        self.max_log_length = max_log_length
401
        
402
        # We keep a reference to the log widget so we can remove it
403
        # from the ListWalker without it getting destroyed
404
        self.logbox = ConstrainedListBox(self.log)
405
        
406
        # This keeps track of whether self.uilist currently has
407
        # self.logbox in it or not
408
        self.log_visible = True
409
        self.log_wrap = u"any"
410
        
411
        self.rebuild()
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
412
        self.log_message_raw((u"bold",
413
                              u"Mandos Monitor version " + version))
414
        self.log_message_raw((u"bold",
415
                              u"q: Quit  ?: Help"))
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
416
        
417
        self.busname = domain + '.Mandos'
418
        self.main_loop = gobject.MainLoop()
419
        self.bus = dbus.SystemBus()
420
        mandos_dbus_objc = self.bus.get_object(
421
            self.busname, u"/", follow_name_owner_changes=True)
422
        self.mandos_serv = dbus.Interface(mandos_dbus_objc,
423
                                          dbus_interface
424
                                          = server_interface)
425
        try:
426
            mandos_clients = (self.mandos_serv
427
                              .GetAllClientsWithProperties())
428
        except dbus.exceptions.DBusException:
429
            mandos_clients = dbus.Dictionary()
430
        
431
        (self.mandos_serv
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
432
         .connect_to_signal(u"ClientRemoved",
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
433
                            self.find_and_remove_client,
434
                            dbus_interface=server_interface,
435
                            byte_arrays=True))
436
        (self.mandos_serv
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
437
         .connect_to_signal(u"ClientAdded",
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
438
                            self.add_new_client,
439
                            dbus_interface=server_interface,
440
                            byte_arrays=True))
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
441
        (self.mandos_serv
442
         .connect_to_signal(u"ClientNotFound",
443
                            self.client_not_found,
444
                            dbus_interface=server_interface,
445
                            byte_arrays=True))
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
446
        for path, client in mandos_clients.iteritems():
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
447
            client_proxy_object = self.bus.get_object(self.busname,
448
                                                      path)
449
            self.add_client(MandosClientWidget(server_proxy_object
450
                                               =self.mandos_serv,
451
                                               proxy_object
452
                                               =client_proxy_object,
453
                                               properties=client,
454
                                               update_hook
455
                                               =self.refresh,
456
                                               delete_hook
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
457
                                               =self.remove_client,
458
                                               logger
459
                                               =self.log_message),
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
460
                            path=path)
461
    
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
462
    def client_not_found(self, fingerprint, address):
463
        self.log_message((u"Client with address %s and fingerprint %s"
464
                          u" could not be found" % (address,
465
                                                    fingerprint)))
466
    
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
467
    def rebuild(self):
468
        """This rebuilds the User Interface.
469
        Call this when the widget layout needs to change"""
470
        self.uilist = []
471
        #self.uilist.append(urwid.ListBox(self.clients))
472
        self.uilist.append(urwid.Frame(ConstrainedListBox(self.clients),
473
                                       #header=urwid.Divider(),
474
                                       header=None,
475
                                       footer=urwid.Divider(div_char=self.divider)))
476
        if self.log_visible:
477
            self.uilist.append(self.logbox)
478
            pass
479
        self.topwidget = urwid.Pile(self.uilist)
480
    
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
481
    def log_message(self, message):
482
        timestamp = datetime.datetime.now().isoformat()
483
        self.log_message_raw(timestamp + u": " + message)
484
    
485
    def log_message_raw(self, markup):
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
486
        """Add a log message to the log buffer."""
487
        self.log.append(urwid.Text(markup, wrap=self.log_wrap))
488
        if (self.max_log_length
489
            and len(self.log) > self.max_log_length):
490
            del self.log[0:len(self.log)-self.max_log_length-1]
237.2.171 by Teddy Hogeborn
* debian/rules: Only set BROKEN_PIE if binutils is a specific range of
491
        self.logbox.set_focus(len(self.logbox.body.contents),
492
                              coming_from=u"above")
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
493
        self.refresh()
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
494
    
495
    def toggle_log_display(self):
496
        """Toggle visibility of the log buffer."""
497
        self.log_visible = not self.log_visible
498
        self.rebuild()
499
        self.log_message(u"Log visibility changed to: "
500
                         + unicode(self.log_visible))
501
    
502
    def change_log_display(self):
503
        """Change type of log display.
504
        Currently, this toggles wrapping of text lines."""
505
        if self.log_wrap == u"clip":
506
            self.log_wrap = u"any"
507
        else:
508
            self.log_wrap = u"clip"
509
        for textwidget in self.log:
510
            textwidget.set_wrap_mode(self.log_wrap)
511
        self.log_message(u"Wrap mode: " + self.log_wrap)
512
    
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
513
    def find_and_remove_client(self, path, name):
514
        """Find an client from its object path and remove it.
515
        
516
        This is connected to the ClientRemoved signal from the
517
        Mandos server object."""
518
        try:
519
            client = self.clients_dict[path]
520
        except KeyError:
521
            # not found?
522
            return
523
        self.remove_client(client, path)
524
    
237.2.174 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
525
    def add_new_client(self, path):
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
526
        client_proxy_object = self.bus.get_object(self.busname, path)
527
        self.add_client(MandosClientWidget(server_proxy_object
528
                                           =self.mandos_serv,
529
                                           proxy_object
530
                                           =client_proxy_object,
531
                                           update_hook
532
                                           =self.refresh,
533
                                           delete_hook
237.2.174 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
534
                                           =self.remove_client,
535
                                           logger
536
                                           =self.log_message),
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
537
                        path=path)
538
    
539
    def add_client(self, client, path=None):
540
        self.clients.append(client)
541
        if path is None:
542
            path = client.proxy.object_path
543
        self.clients_dict[path] = client
544
        self.clients.sort(None, lambda c: c.properties[u"name"])
545
        self.refresh()
546
    
547
    def remove_client(self, client, path=None):
548
        self.clients.remove(client)
549
        if path is None:
550
            path = client.proxy.object_path
551
        del self.clients_dict[path]
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
552
        if not self.clients_dict:
553
            # Work around bug in Urwid 0.9.8.3 - if a SimpleListWalker
554
            # is completely emptied, we need to recreate it.
555
            self.clients = urwid.SimpleListWalker([])
556
            self.rebuild()
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
557
        self.refresh()
558
    
559
    def refresh(self):
560
        """Redraw the screen"""
561
        canvas = self.topwidget.render(self.size, focus=True)
562
        self.screen.draw_screen(self.size, canvas)
563
    
564
    def run(self):
565
        """Start the main loop and exit when it's done."""
566
        self.refresh()
567
        self._input_callback_tag = (gobject.io_add_watch
568
                                    (sys.stdin.fileno(),
569
                                     gobject.IO_IN,
570
                                     self.process_input))
571
        self.main_loop.run()
572
        # Main loop has finished, we should close everything now
573
        gobject.source_remove(self._input_callback_tag)
574
        self.screen.stop()
575
    
576
    def stop(self):
577
        self.main_loop.quit()
578
    
579
    def process_input(self, source, condition):
580
        keys = self.screen.get_input()
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
581
        translations = { u"ctrl n": u"down",      # Emacs
582
                         u"ctrl p": u"up",        # Emacs
583
                         u"ctrl v": u"page down", # Emacs
584
                         u"meta v": u"page up",   # Emacs
585
                         u" ": u"page down",      # less
586
                         u"f": u"page down",      # less
587
                         u"b": u"page up",        # less
588
                         u"j": u"down",           # vi
589
                         u"k": u"up",             # vi
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
590
                         }
591
        for key in keys:
592
            try:
593
                key = translations[key]
594
            except KeyError:    # :-)
595
                pass
596
            
597
            if key == u"q" or key == u"Q":
598
                self.stop()
599
                break
600
            elif key == u"window resize":
601
                self.size = self.screen.get_cols_rows()
602
                self.refresh()
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
603
            elif key == u"\f":  # Ctrl-L
604
                self.refresh()
605
            elif key == u"l" or key == u"D":
606
                self.toggle_log_display()
607
                self.refresh()
608
            elif key == u"w" or key == u"i":
609
                self.change_log_display()
610
                self.refresh()
237.2.171 by Teddy Hogeborn
* debian/rules: Only set BROKEN_PIE if binutils is a specific range of
611
            elif key == u"?" or key == u"f1" or key == u"esc":
237.2.170 by Teddy Hogeborn
* mandos-monitor (MandosClientWidget): Change "StopChecker" key to "S"
612
                if not self.log_visible:
613
                    self.log_visible = True
614
                    self.rebuild()
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
615
                self.log_message_raw((u"bold",
616
                                      u"  ".
617
                                      join((u"q: Quit",
618
                                            u"?: Help",
619
                                            u"l: Log window toggle",
620
                                            u"TAB: Switch window",
621
                                            u"w: Wrap (log)"))))
622
                self.log_message_raw((u"bold",
623
                                      u"  "
624
                                      .join((u"Clients:",
24.1.155 by Björn Påhlsson
mandos server: Added debuglevel that adjust at what level information
625
                                             u"+: Enable",
626
                                             u"-: Disable",
237.2.172 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
627
                                             u"r: Remove",
628
                                             u"s: Start new checker",
629
                                             u"S: Stop checker",
24.1.154 by Björn Påhlsson
merge
630
                                             u"C: Checker OK",
24.1.155 by Björn Påhlsson
mandos server: Added debuglevel that adjust at what level information
631
                                             u"a: Approve",
632
                                             u"d: Deny"))))
237.2.168 by Teddy Hogeborn
* mandos-monitor (MandosClientPropertyCache): Remove conversion of
633
                self.refresh()
634
            elif key == u"tab":
635
                if self.topwidget.get_focus() is self.logbox:
636
                    self.topwidget.set_focus(0)
637
                else:
638
                    self.topwidget.set_focus(self.logbox)
639
                self.refresh()
237.2.170 by Teddy Hogeborn
* mandos-monitor (MandosClientWidget): Change "StopChecker" key to "S"
640
            #elif (key == u"end" or key == u"meta >" or key == u"G"
641
            #      or key == u">"):
642
            #    pass            # xxx end-of-buffer
643
            #elif (key == u"home" or key == u"meta <" or key == u"g"
644
            #      or key == u"<"):
645
            #    pass            # xxx beginning-of-buffer
646
            #elif key == u"ctrl e" or key == u"$":
647
            #    pass            # xxx move-end-of-line
648
            #elif key == u"ctrl a" or key == u"^":
649
            #    pass            # xxx move-beginning-of-line
650
            #elif key == u"ctrl b" or key == u"meta (" or key == u"h":
651
            #    pass            # xxx left
652
            #elif key == u"ctrl f" or key == u"meta )" or key == u"l":
653
            #    pass            # xxx right
654
            #elif key == u"a":
655
            #    pass            # scroll up log
656
            #elif key == u"z":
657
            #    pass            # scroll down log
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
658
            elif self.topwidget.selectable():
659
                self.topwidget.keypress(self.size, key)
660
                self.refresh()
661
        return True
662
663
ui = UserInterface()
664
try:
665
    ui.run()
24.1.159 by Björn Påhlsson
added approval to mandos-ctl
666
except KeyboardInterrupt:
667
    ui.screen.stop()
237.2.174 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
668
except Exception, e:
669
    ui.log_message(unicode(e))
237.2.167 by Teddy Hogeborn
* mandos-monitor: New prototype version of interactive server
670
    ui.screen.stop()
671
    raise