/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: 2013-10-28 10:04:05 UTC
  • Revision ID: teddy@recompile.se-20131028100405-u6miovq7y39m69x7
Doc fix: Refer to architecture libdir.

* debian/mandos-client.README.Debian: Refer to architecture libdir.

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