/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-03 19:06:02 UTC
  • Revision ID: teddy@recompile.se-20120503190602-uqghef5rbpqdvybx
* mandos-monitor: Use new string format method.

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