/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

  • Committer: Teddy Hogeborn
  • Date: 2014-07-17 02:45:02 UTC
  • mto: (237.7.272 trunk)
  • mto: This revision was merged to the branch mainline in revision 319.
  • Revision ID: teddy@recompile.se-20140717024502-yg5ysk05lx8275k9
Update non-package install instructions.

* INSTALL: Update package names, URLs and versions.  Remove discussion
           of adjusting network device name.  Adjust minimum reboot
           time to reflect new default timeout value.

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
 
 
29
 
from future_builtins import *
 
28
try:
 
29
    from future_builtins import *
 
30
except ImportError:
 
31
    pass
30
32
 
31
33
import sys
32
34
import os
33
 
import signal
34
35
 
35
36
import datetime
36
37
 
38
39
import urwid
39
40
 
40
41
from dbus.mainloop.glib import DBusGMainLoop
41
 
import gobject
 
42
try:
 
43
    import gobject
 
44
except ImportError:
 
45
    from gi.repository import GObject as gobject
42
46
 
43
47
import dbus
44
48
 
45
 
import UserList
46
 
 
47
49
import locale
48
50
 
 
51
if sys.version_info[0] == 2:
 
52
    str = unicode
 
53
 
49
54
locale.setlocale(locale.LC_ALL, '')
50
55
 
51
56
import logging
55
60
domain = 'se.recompile'
56
61
server_interface = domain + '.Mandos'
57
62
client_interface = domain + '.Mandos.Client'
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)
 
63
version = "1.6.6"
67
64
 
68
65
def isoformat_to_datetime(iso):
69
66
    "Parse an ISO 8601 date string to a datetime.datetime()"
134
131
        self.logger = logger
135
132
        
136
133
        self._update_timer_callback_tag = None
137
 
        self._update_timer_callback_lock = 0
138
134
        
139
135
        # The widget shown normally
140
136
        self._text_widget = urwid.Text("")
144
140
        self.update()
145
141
        self.opened = False
146
142
        
147
 
        last_checked_ok = isoformat_to_datetime(self.properties
148
 
                                                ["LastCheckedOK"])
149
 
        
150
 
        if self.properties ["LastCheckerStatus"] != 0:
151
 
            self.using_timer(True)
152
 
        
153
 
        if self.need_approval:
154
 
            self.using_timer(True)
155
 
        
156
143
        self.match_objects = (
157
144
            self.proxy.connect_to_signal("CheckerCompleted",
158
145
                                         self.checker_completed,
174
161
                                         self.rejected,
175
162
                                         client_interface,
176
163
                                         byte_arrays=True))
177
 
        #self.logger('Created client {0}'
178
 
        #            .format(self.properties["Name"]))
179
 
    
180
 
    def property_changed(self, property=None, value=None):
181
 
        super(self, MandosClientWidget).property_changed(property,
182
 
                                                         value)
183
 
        if property == "ApprovalPending":
184
 
            using_timer(bool(value))
185
 
        if property == "LastCheckerStatus":
186
 
            using_timer(value != 0)
187
 
            #self.logger('Checker for client {0} (command "{1}") was '
188
 
            #            ' successful'.format(self.properties["Name"],
189
 
            #                                 command))
 
164
        self.logger('Created client {0}'
 
165
                    .format(self.properties["Name"]), level=0)
190
166
    
191
167
    def using_timer(self, flag):
192
168
        """Call this method with True or False when timer should be
193
169
        activated or deactivated.
194
170
        """
195
 
        old = self._update_timer_callback_lock
196
 
        if flag:
197
 
            self._update_timer_callback_lock += 1
198
 
        else:
199
 
            self._update_timer_callback_lock -= 1
200
 
        if old == 0 and self._update_timer_callback_lock:
 
171
        if flag and self._update_timer_callback_tag is None:
201
172
            # Will update the shown timer value every second
202
173
            self._update_timer_callback_tag = (gobject.timeout_add
203
174
                                               (1000,
204
175
                                                self.update_timer))
205
 
        elif old and self._update_timer_callback_lock == 0:
 
176
        elif not (flag or self._update_timer_callback_tag is None):
206
177
            gobject.source_remove(self._update_timer_callback_tag)
207
178
            self._update_timer_callback_tag = None
208
179
    
209
180
    def checker_completed(self, exitstatus, condition, command):
210
181
        if exitstatus == 0:
 
182
            self.logger('Checker for client {0} (command "{1}")'
 
183
                        ' succeeded'.format(self.properties["Name"],
 
184
                                            command), level=0)
211
185
            self.update()
212
186
            return
213
187
        # Checker failed
232
206
        self.update()
233
207
    
234
208
    def checker_started(self, command):
235
 
        """Server signals that a checker started. This could be useful
236
 
           to log in the future. """
237
 
        #self.logger('Client {0} started checker "{1}"'
238
 
        #            .format(self.properties["Name"],
239
 
        #                    unicode(command)))
240
 
        pass
 
209
        """Server signals that a checker started."""
 
210
        self.logger('Client {0} started checker "{1}"'
 
211
                    .format(self.properties["Name"],
 
212
                            command), level=0)
241
213
    
242
214
    def got_secret(self):
243
215
        self.logger('Client {0} received its secret'
250
222
            message = 'Client {0} will get its secret in {1} seconds'
251
223
        self.logger(message.format(self.properties["Name"],
252
224
                                   timeout/1000))
253
 
        self.using_timer(True)
254
225
    
255
226
    def rejected(self, reason):
256
227
        self.logger('Client {0} was rejected; reason: {1}'
282
253
                          "bold-underline-blink":
283
254
                              "bold-underline-blink-standout",
284
255
                          }
285
 
 
 
256
        
286
257
        # Rebuild focus and non-focus widgets using current properties
287
 
 
 
258
        
288
259
        # Base part of a client. Name!
289
260
        base = '{name}: '.format(name=self.properties["Name"])
290
261
        if not self.properties["Enabled"]:
291
262
            message = "DISABLED"
 
263
            self.using_timer(False)
292
264
        elif self.properties["ApprovalPending"]:
293
265
            timeout = datetime.timedelta(milliseconds
294
266
                                         = self.properties
296
268
            last_approval_request = isoformat_to_datetime(
297
269
                self.properties["LastApprovalRequest"])
298
270
            if last_approval_request is not None:
299
 
                timer = timeout - (datetime.datetime.utcnow()
300
 
                                   - last_approval_request)
 
271
                timer = max(timeout - (datetime.datetime.utcnow()
 
272
                                       - last_approval_request),
 
273
                            datetime.timedelta())
301
274
            else:
302
275
                timer = datetime.timedelta()
303
276
            if self.properties["ApprovedByDefault"]:
304
277
                message = "Approval in {0}. (d)eny?"
305
278
            else:
306
279
                message = "Denial in {0}. (a)pprove?"
307
 
            message = message.format(unicode(timer).rsplit(".", 1)[0])
 
280
            message = message.format(str(timer).rsplit(".", 1)[0])
 
281
            self.using_timer(True)
308
282
        elif self.properties["LastCheckerStatus"] != 0:
309
283
            # When checker has failed, show timer until client expires
310
284
            expires = self.properties["Expires"]
313
287
            else:
314
288
                expires = (datetime.datetime.strptime
315
289
                           (expires, '%Y-%m-%dT%H:%M:%S.%f'))
316
 
                timer = expires - datetime.datetime.utcnow()
 
290
                timer = max(expires - datetime.datetime.utcnow(),
 
291
                            datetime.timedelta())
317
292
            message = ('A checker has failed! Time until client'
318
293
                       ' gets disabled: {0}'
319
 
                       .format(unicode(timer).rsplit(".", 1)[0]))
 
294
                       .format(str(timer).rsplit(".", 1)[0]))
 
295
            self.using_timer(True)
320
296
        else:
321
297
            message = "enabled"
 
298
            self.using_timer(False)
322
299
        self._text = "{0}{1}".format(base, message)
323
 
            
 
300
        
324
301
        if not urwid.supports_unicode():
325
302
            self._text = self._text.encode("ascii", "replace")
326
303
        textlist = [("normal", self._text)]
403
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
386
            property=property, **kwargs)
427
404
    """This is the entire user interface - the whole screen
428
405
    with boxes, lists of client widgets, etc.
429
406
    """
430
 
    def __init__(self, max_log_length=1000):
 
407
    def __init__(self, max_log_length=1000, log_level=1):
431
408
        DBusGMainLoop(set_as_default=True)
432
409
        
433
410
        self.screen = urwid.curses_display.Screen()
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():
470
448
        self.log = []
471
449
        self.max_log_length = max_log_length
472
450
        
 
451
        self.log_level = log_level
 
452
        
473
453
        # We keep a reference to the log widget so we can remove it
474
454
        # from the ListWalker without it getting destroyed
475
455
        self.logbox = ConstrainedListBox(self.log)
509
489
            self.uilist.append(self.logbox)
510
490
        self.topwidget = urwid.Pile(self.uilist)
511
491
    
512
 
    def log_message(self, message):
 
492
    def log_message(self, message, level=1):
 
493
        """Log message formatted with timestamp"""
 
494
        if level < self.log_level:
 
495
            return
513
496
        timestamp = datetime.datetime.now().isoformat()
514
 
        self.log_message_raw(timestamp + ": " + message)
 
497
        self.log_message_raw("{0}: {1}".format(timestamp, message),
 
498
                             level=level)
515
499
    
516
 
    def log_message_raw(self, markup):
 
500
    def log_message_raw(self, markup, level=1):
517
501
        """Add a log message to the log buffer."""
 
502
        if level < self.log_level:
 
503
            return
518
504
        self.log.append(urwid.Text(markup, wrap=self.log_wrap))
519
505
        if (self.max_log_length
520
506
            and len(self.log) > self.max_log_length):
527
513
        """Toggle visibility of the log buffer."""
528
514
        self.log_visible = not self.log_visible
529
515
        self.rebuild()
530
 
        #self.log_message("Log visibility changed to: "
531
 
        #                 + unicode(self.log_visible))
 
516
        self.log_message("Log visibility changed to: {0}"
 
517
                         .format(self.log_visible), level=0)
532
518
    
533
519
    def change_log_display(self):
534
520
        """Change type of log display.
539
525
            self.log_wrap = "clip"
540
526
        for textwidget in self.log:
541
527
            textwidget.set_wrap_mode(self.log_wrap)
542
 
        #self.log_message("Wrap mode: " + self.log_wrap)
 
528
        self.log_message("Wrap mode: {0}".format(self.log_wrap),
 
529
                         level=0)
543
530
    
544
531
    def find_and_remove_client(self, path, name):
545
532
        """Find a client by its object path and remove it.
574
561
        if path is None:
575
562
            path = client.proxy.object_path
576
563
        self.clients_dict[path] = client
577
 
        self.clients.sort(None, lambda c: c.properties["Name"])
 
564
        self.clients.sort(key=lambda c: c.properties["Name"])
578
565
        self.refresh()
579
566
    
580
567
    def remove_client(self, client, path=None):
582
569
        if path is None:
583
570
            path = client.proxy.object_path
584
571
        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
572
        self.refresh()
591
573
    
592
574
    def refresh(self):
605
587
        try:
606
588
            mandos_clients = (self.mandos_serv
607
589
                              .GetAllClientsWithProperties())
 
590
            if not mandos_clients:
 
591
                self.log_message_raw(("bold", "Note: Server has no clients."))
608
592
        except dbus.exceptions.DBusException:
 
593
            self.log_message_raw(("bold", "Note: No Mandos server running."))
609
594
            mandos_clients = dbus.Dictionary()
610
595
        
611
596
        (self.mandos_serv
623
608
                            self.client_not_found,
624
609
                            dbus_interface=server_interface,
625
610
                            byte_arrays=True))
626
 
        for path, client in mandos_clients.iteritems():
 
611
        for path, client in mandos_clients.items():
627
612
            client_proxy_object = self.bus.get_object(self.busname,
628
613
                                                      path)
629
614
            self.add_client(MandosClientWidget(server_proxy_object
694
679
                                            "?: Help",
695
680
                                            "l: Log window toggle",
696
681
                                            "TAB: Switch window",
697
 
                                            "w: Wrap (log)"))))
 
682
                                            "w: Wrap (log lines)",
 
683
                                            "v: Toggle verbose log",
 
684
                                            ))))
698
685
                self.log_message_raw(("bold",
699
686
                                      "  "
700
687
                                      .join(("Clients:",
713
700
                else:
714
701
                    self.topwidget.set_focus(self.logbox)
715
702
                self.refresh()
 
703
            elif key == "v":
 
704
                if self.log_level == 0:
 
705
                    self.log_level = 1
 
706
                    self.log_message("Verbose mode: Off")
 
707
                else:
 
708
                    self.log_level = 0
 
709
                    self.log_message("Verbose mode: On")
716
710
            #elif (key == "end" or key == "meta >" or key == "G"
717
711
            #      or key == ">"):
718
712
            #    pass            # xxx end-of-buffer
741
735
    ui.run()
742
736
except KeyboardInterrupt:
743
737
    ui.screen.stop()
744
 
except Exception, e:
745
 
    ui.log_message(unicode(e))
 
738
except Exception as e:
 
739
    ui.log_message(str(e))
746
740
    ui.screen.stop()
747
741
    raise