/mandos/release

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

« back to all changes in this revision

Viewing changes to mandos-monitor

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