/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: 2014-07-13 20:37:44 UTC
  • mfrom: (710 trunk)
  • mto: This revision was merged to the branch mainline in revision 711.
  • Revision ID: teddy@recompile.se-20140713203744-538hqhk81vw9dixv
Merge from trunk.

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