/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: 2012-05-07 19:13:15 UTC
  • Revision ID: teddy@recompile.se-20120507191315-tbe55n4u1uq3l7ft
* mandos: Use all new builtins.
* mandos-ctl: - '' -
* mandos-monitor: - '' -

Show diffs side-by-side

added added

removed removed

Lines of Context:
3
3
4
4
# Mandos Monitor - Control and monitor the Mandos server
5
5
6
 
# Copyright © 2009-2014 Teddy Hogeborn
7
 
# Copyright © 2009-2014 Björn Påhlsson
 
6
# Copyright © 2009-2012 Teddy Hogeborn
 
7
# Copyright © 2009-2012 Björn Påhlsson
8
8
9
9
# This program is free software: you can redistribute it and/or modify
10
10
# it under the terms of the GNU General Public License as published by
25
25
 
26
26
from __future__ import (division, absolute_import, print_function,
27
27
                        unicode_literals)
28
 
try:
29
 
    from future_builtins import *
30
 
except ImportError:
31
 
    pass
 
28
 
 
29
from future_builtins import *
32
30
 
33
31
import sys
34
32
import os
 
33
import signal
35
34
 
36
35
import datetime
37
36
 
39
38
import urwid
40
39
 
41
40
from dbus.mainloop.glib import DBusGMainLoop
42
 
try:
43
 
    import gobject
44
 
except ImportError:
45
 
    from gi.repository import GObject as gobject
 
41
import gobject
46
42
 
47
43
import dbus
48
44
 
 
45
import UserList
 
46
 
49
47
import locale
50
48
 
51
 
if sys.version_info[0] == 2:
52
 
    str = unicode
53
 
 
54
49
locale.setlocale(locale.LC_ALL, '')
55
50
 
56
51
import logging
60
55
domain = 'se.recompile'
61
56
server_interface = domain + '.Mandos'
62
57
client_interface = domain + '.Mandos.Client'
63
 
version = "1.6.4"
 
58
version = "1.5.3"
 
59
 
 
60
# Always run in monochrome mode
 
61
urwid.curses_display.curses.has_colors = lambda : False
 
62
 
 
63
# Urwid doesn't support blinking, but we want it.  Since we have no
 
64
# use for underline on its own, we make underline also always blink.
 
65
urwid.curses_display.curses.A_UNDERLINE |= (
 
66
    urwid.curses_display.curses.A_BLINK)
64
67
 
65
68
def isoformat_to_datetime(iso):
66
69
    "Parse an ISO 8601 date string to a datetime.datetime()"
83
86
    properties and calls a hook function when any of them are
84
87
    changed.
85
88
    """
86
 
    def __init__(self, proxy_object=None, properties=None, **kwargs):
 
89
    def __init__(self, proxy_object=None, *args, **kwargs):
87
90
        self.proxy = proxy_object # Mandos Client proxy object
88
 
        self.properties = dict() if properties is None else properties
 
91
        
 
92
        self.properties = dict()
89
93
        self.property_changed_match = (
90
94
            self.proxy.connect_to_signal("PropertyChanged",
91
 
                                         self._property_changed,
 
95
                                         self.property_changed,
92
96
                                         client_interface,
93
97
                                         byte_arrays=True))
94
98
        
95
 
        if properties is None:
96
 
            self.properties.update(
97
 
                self.proxy.GetAll(client_interface,
98
 
                                  dbus_interface
99
 
                                  = dbus.PROPERTIES_IFACE))
100
 
        
101
 
        super(MandosClientPropertyCache, self).__init__(**kwargs)
102
 
    
103
 
    def _property_changed(self, property, value):
104
 
        """Helper which takes positional arguments"""
105
 
        return self.property_changed(property=property, value=value)
 
99
        self.properties.update(
 
100
            self.proxy.GetAll(client_interface,
 
101
                              dbus_interface = dbus.PROPERTIES_IFACE))
 
102
 
 
103
        #XXX This breaks good super behaviour
 
104
#        super(MandosClientPropertyCache, self).__init__(
 
105
#            *args, **kwargs)
106
106
    
107
107
    def property_changed(self, property=None, value=None):
108
108
        """This is called whenever we get a PropertyChanged signal
111
111
        # Update properties dict with new value
112
112
        self.properties[property] = value
113
113
    
114
 
    def delete(self):
 
114
    def delete(self, *args, **kwargs):
115
115
        self.property_changed_match.remove()
 
116
        super(MandosClientPropertyCache, self).__init__(
 
117
            *args, **kwargs)
116
118
 
117
119
 
118
120
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
120
122
    """
121
123
    
122
124
    def __init__(self, server_proxy_object=None, update_hook=None,
123
 
                 delete_hook=None, logger=None, **kwargs):
 
125
                 delete_hook=None, logger=None, *args, **kwargs):
124
126
        # Called on update
125
127
        self.update_hook = update_hook
126
128
        # Called on delete
131
133
        self.logger = logger
132
134
        
133
135
        self._update_timer_callback_tag = None
 
136
        self._update_timer_callback_lock = 0
134
137
        
135
138
        # The widget shown normally
136
139
        self._text_widget = urwid.Text("")
137
140
        # The widget shown when we have focus
138
141
        self._focus_text_widget = urwid.Text("")
139
 
        super(MandosClientWidget, self).__init__(**kwargs)
 
142
        super(MandosClientWidget, self).__init__(
 
143
            update_hook=update_hook, delete_hook=delete_hook,
 
144
            *args, **kwargs)
140
145
        self.update()
141
146
        self.opened = False
142
147
        
 
148
        last_checked_ok = isoformat_to_datetime(self.properties
 
149
                                                ["LastCheckedOK"])
 
150
        
 
151
        if self.properties ["LastCheckerStatus"] != 0:
 
152
            self.using_timer(True)
 
153
        
 
154
        if self.need_approval:
 
155
            self.using_timer(True)
 
156
        
143
157
        self.match_objects = (
144
158
            self.proxy.connect_to_signal("CheckerCompleted",
145
159
                                         self.checker_completed,
164
178
        #self.logger('Created client {0}'
165
179
        #            .format(self.properties["Name"]))
166
180
    
 
181
    def property_changed(self, property=None, value=None):
 
182
        super(self, MandosClientWidget).property_changed(property,
 
183
                                                         value)
 
184
        if property == "ApprovalPending":
 
185
            using_timer(bool(value))
 
186
        if property == "LastCheckerStatus":
 
187
            using_timer(value != 0)
 
188
            #self.logger('Checker for client {0} (command "{1}") was '
 
189
            #            ' successful'.format(self.properties["Name"],
 
190
            #                                 command))
 
191
    
167
192
    def using_timer(self, flag):
168
193
        """Call this method with True or False when timer should be
169
194
        activated or deactivated.
170
195
        """
171
 
        if flag and self._update_timer_callback_tag is None:
 
196
        old = self._update_timer_callback_lock
 
197
        if flag:
 
198
            self._update_timer_callback_lock += 1
 
199
        else:
 
200
            self._update_timer_callback_lock -= 1
 
201
        if old == 0 and self._update_timer_callback_lock:
172
202
            # Will update the shown timer value every second
173
203
            self._update_timer_callback_tag = (gobject.timeout_add
174
204
                                               (1000,
175
205
                                                self.update_timer))
176
 
        elif not (flag or self._update_timer_callback_tag is None):
 
206
        elif old and self._update_timer_callback_lock == 0:
177
207
            gobject.source_remove(self._update_timer_callback_tag)
178
208
            self._update_timer_callback_tag = None
179
209
    
207
237
           to log in the future. """
208
238
        #self.logger('Client {0} started checker "{1}"'
209
239
        #            .format(self.properties["Name"],
210
 
        #                    str(command)))
 
240
        #                    unicode(command)))
211
241
        pass
212
242
    
213
243
    def got_secret(self):
221
251
            message = 'Client {0} will get its secret in {1} seconds'
222
252
        self.logger(message.format(self.properties["Name"],
223
253
                                   timeout/1000))
 
254
        self.using_timer(True)
224
255
    
225
256
    def rejected(self, reason):
226
257
        self.logger('Client {0} was rejected; reason: {1}'
252
283
                          "bold-underline-blink":
253
284
                              "bold-underline-blink-standout",
254
285
                          }
255
 
        
 
286
 
256
287
        # Rebuild focus and non-focus widgets using current properties
257
 
        
 
288
 
258
289
        # Base part of a client. Name!
259
290
        base = '{name}: '.format(name=self.properties["Name"])
260
291
        if not self.properties["Enabled"]:
261
292
            message = "DISABLED"
262
 
            self.using_timer(False)
263
293
        elif self.properties["ApprovalPending"]:
264
294
            timeout = datetime.timedelta(milliseconds
265
295
                                         = self.properties
267
297
            last_approval_request = isoformat_to_datetime(
268
298
                self.properties["LastApprovalRequest"])
269
299
            if last_approval_request is not None:
270
 
                timer = max(timeout - (datetime.datetime.utcnow()
271
 
                                       - last_approval_request),
272
 
                            datetime.timedelta())
 
300
                timer = timeout - (datetime.datetime.utcnow()
 
301
                                   - last_approval_request)
273
302
            else:
274
303
                timer = datetime.timedelta()
275
304
            if self.properties["ApprovedByDefault"]:
276
305
                message = "Approval in {0}. (d)eny?"
277
306
            else:
278
307
                message = "Denial in {0}. (a)pprove?"
279
 
            message = message.format(str(timer).rsplit(".", 1)[0])
280
 
            self.using_timer(True)
 
308
            message = message.format(unicode(timer).rsplit(".", 1)[0])
281
309
        elif self.properties["LastCheckerStatus"] != 0:
282
310
            # When checker has failed, show timer until client expires
283
311
            expires = self.properties["Expires"]
286
314
            else:
287
315
                expires = (datetime.datetime.strptime
288
316
                           (expires, '%Y-%m-%dT%H:%M:%S.%f'))
289
 
                timer = max(expires - datetime.datetime.utcnow(),
290
 
                            datetime.timedelta())
 
317
                timer = expires - datetime.datetime.utcnow()
291
318
            message = ('A checker has failed! Time until client'
292
319
                       ' gets disabled: {0}'
293
 
                       .format(str(timer).rsplit(".", 1)[0]))
294
 
            self.using_timer(True)
 
320
                       .format(unicode(timer).rsplit(".", 1)[0]))
295
321
        else:
296
322
            message = "enabled"
297
 
            self.using_timer(False)
298
323
        self._text = "{0}{1}".format(base, message)
299
 
        
 
324
            
300
325
        if not urwid.supports_unicode():
301
326
            self._text = self._text.encode("ascii", "replace")
302
327
        textlist = [("normal", self._text)]
319
344
        self.update()
320
345
        return True             # Keep calling this
321
346
    
322
 
    def delete(self, **kwargs):
 
347
    def delete(self, *args, **kwargs):
323
348
        if self._update_timer_callback_tag is not None:
324
349
            gobject.source_remove(self._update_timer_callback_tag)
325
350
            self._update_timer_callback_tag = None
328
353
        self.match_objects = ()
329
354
        if self.delete_hook is not None:
330
355
            self.delete_hook(self)
331
 
        return super(MandosClientWidget, self).delete(**kwargs)
 
356
        return super(MandosClientWidget, self).delete(*args, **kwargs)
332
357
    
333
358
    def render(self, maxcolrow, focus=False):
334
359
        """Render differently if we have focus.
376
401
        else:
377
402
            return key
378
403
    
379
 
    def property_changed(self, property=None, **kwargs):
 
404
    def property_changed(self, property=None, value=None,
 
405
                         *args, **kwargs):
380
406
        """Call self.update() if old value is not new value.
381
407
        This overrides the method from MandosClientPropertyCache"""
382
 
        property_name = str(property)
 
408
        property_name = unicode(property)
383
409
        old_value = self.properties.get(property_name)
384
410
        super(MandosClientWidget, self).property_changed(
385
 
            property=property, **kwargs)
 
411
            property=property, value=value, *args, **kwargs)
386
412
        if self.properties.get(property_name) != old_value:
387
413
            self.update()
388
414
 
392
418
    "down" key presses, thus not allowing any containing widgets to
393
419
    use them as an excuse to shift focus away from this widget.
394
420
    """
395
 
    def keypress(self, *args, **kwargs):
396
 
        ret = super(ConstrainedListBox, self).keypress(*args, **kwargs)
 
421
    def keypress(self, maxcolrow, key):
 
422
        ret = super(ConstrainedListBox, self).keypress(maxcolrow, key)
397
423
        if ret in ("up", "down"):
398
424
            return
399
425
        return ret
412
438
                ("normal",
413
439
                 "default", "default", None),
414
440
                ("bold",
415
 
                 "bold", "default", "bold"),
 
441
                 "default", "default", "bold"),
416
442
                ("underline-blink",
417
 
                 "underline,blink", "default", "underline,blink"),
 
443
                 "default", "default", "underline"),
418
444
                ("standout",
419
 
                 "standout", "default", "standout"),
 
445
                 "default", "default", "standout"),
420
446
                ("bold-underline-blink",
421
 
                 "bold,underline,blink", "default", "bold,underline,blink"),
 
447
                 "default", "default", ("bold", "underline")),
422
448
                ("bold-standout",
423
 
                 "bold,standout", "default", "bold,standout"),
 
449
                 "default", "default", ("bold", "standout")),
424
450
                ("underline-blink-standout",
425
 
                 "underline,blink,standout", "default",
426
 
                 "underline,blink,standout"),
 
451
                 "default", "default", ("underline", "standout")),
427
452
                ("bold-underline-blink-standout",
428
 
                 "bold,underline,blink,standout", "default",
429
 
                 "bold,underline,blink,standout"),
 
453
                 "default", "default", ("bold", "underline",
 
454
                                          "standout")),
430
455
                ))
431
456
        
432
457
        if urwid.supports_unicode():
487
512
        self.topwidget = urwid.Pile(self.uilist)
488
513
    
489
514
    def log_message(self, message):
490
 
        """Log message formatted with timestamp"""
491
515
        timestamp = datetime.datetime.now().isoformat()
492
516
        self.log_message_raw(timestamp + ": " + message)
493
517
    
506
530
        self.log_visible = not self.log_visible
507
531
        self.rebuild()
508
532
        #self.log_message("Log visibility changed to: "
509
 
        #                 + str(self.log_visible))
 
533
        #                 + unicode(self.log_visible))
510
534
    
511
535
    def change_log_display(self):
512
536
        """Change type of log display.
552
576
        if path is None:
553
577
            path = client.proxy.object_path
554
578
        self.clients_dict[path] = client
555
 
        self.clients.sort(key=lambda c: c.properties["Name"])
 
579
        self.clients.sort(None, lambda c: c.properties["Name"])
556
580
        self.refresh()
557
581
    
558
582
    def remove_client(self, client, path=None):
560
584
        if path is None:
561
585
            path = client.proxy.object_path
562
586
        del self.clients_dict[path]
 
587
        if not self.clients_dict:
 
588
            # Work around bug in Urwid 0.9.8.3 - if a SimpleListWalker
 
589
            # is completely emptied, we need to recreate it.
 
590
            self.clients = urwid.SimpleListWalker([])
 
591
            self.rebuild()
563
592
        self.refresh()
564
593
    
565
594
    def refresh(self):
578
607
        try:
579
608
            mandos_clients = (self.mandos_serv
580
609
                              .GetAllClientsWithProperties())
581
 
            if not mandos_clients:
582
 
                self.log_message_raw(("bold", "Note: Server has no clients."))
583
610
        except dbus.exceptions.DBusException:
584
 
            self.log_message_raw(("bold", "Note: No Mandos server running."))
585
611
            mandos_clients = dbus.Dictionary()
586
612
        
587
613
        (self.mandos_serv
599
625
                            self.client_not_found,
600
626
                            dbus_interface=server_interface,
601
627
                            byte_arrays=True))
602
 
        for path, client in mandos_clients.items():
 
628
        for path, client in mandos_clients.iteritems():
603
629
            client_proxy_object = self.bus.get_object(self.busname,
604
630
                                                      path)
605
631
            self.add_client(MandosClientWidget(server_proxy_object
614
640
                                               logger
615
641
                                               =self.log_message),
616
642
                            path=path)
617
 
        
 
643
 
618
644
        self.refresh()
619
645
        self._input_callback_tag = (gobject.io_add_watch
620
646
                                    (sys.stdin.fileno(),
717
743
    ui.run()
718
744
except KeyboardInterrupt:
719
745
    ui.screen.stop()
720
 
except Exception as e:
721
 
    ui.log_message(str(e))
 
746
except Exception, e:
 
747
    ui.log_message(unicode(e))
722
748
    ui.screen.stop()
723
749
    raise