/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: 2016-03-19 03:51:23 UTC
  • Revision ID: teddy@recompile.se-20160319035123-53w4dbzdyjef29m1
Server: New tmpfiles.d file for persistent state directory

Provide a tmpfiles.d(5) file for systemd to create persistent state
directory on so-called "volatile" systems.

* Makefile (TMPFILES): New.
  (install-server): Also install "tmpfiles.d-mandos.conf" file as
                    "/usr/lib/tmpfiles.d/mandos.conf".
* tmpfiles.d-mandos.conf: New.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!/usr/bin/python
2
2
# -*- mode: python; coding: utf-8 -*-
3
 
#
 
3
4
4
# Mandos Monitor - Control and monitor the Mandos server
5
 
#
6
 
# Copyright © 2009-2017 Teddy Hogeborn
7
 
# Copyright © 2009-2017 Björn Påhlsson
8
 
#
9
 
# This file is part of Mandos.
10
 
#
11
 
# Mandos is free software: you can redistribute it and/or modify it
12
 
# under the terms of the GNU General Public License as published by
 
5
 
6
# Copyright © 2009-2016 Teddy Hogeborn
 
7
# Copyright © 2009-2016 Björn Påhlsson
 
8
 
9
# This program is free software: you can redistribute it and/or modify
 
10
# it under the terms of the GNU General Public License as published by
13
11
# the Free Software Foundation, either version 3 of the License, or
14
12
# (at your option) any later version.
15
13
#
16
 
#     Mandos is distributed in the hope that it will be useful, but
17
 
#     WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
#     This program is distributed in the hope that it will be useful,
 
15
#     but WITHOUT ANY WARRANTY; without even the implied warranty of
18
16
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
17
#     GNU General Public License for more details.
20
 
#
 
18
21
19
# You should have received a copy of the GNU General Public License
22
 
# along with Mandos.  If not, see <http://www.gnu.org/licenses/>.
23
 
#
 
20
# along with this program.  If not, see
 
21
# <http://www.gnu.org/licenses/>.
 
22
24
23
# Contact the authors at <mandos@recompile.se>.
25
 
#
 
24
26
25
 
27
26
from __future__ import (division, absolute_import, print_function,
28
27
                        unicode_literals)
46
45
 
47
46
import locale
48
47
 
49
 
import logging
50
 
 
51
48
if sys.version_info.major == 2:
52
49
    str = unicode
53
50
 
54
51
locale.setlocale(locale.LC_ALL, '')
55
52
 
 
53
import logging
56
54
logging.getLogger('dbus.proxies').setLevel(logging.CRITICAL)
57
55
 
58
56
# Some useful constants
59
57
domain = 'se.recompile'
60
58
server_interface = domain + '.Mandos'
61
59
client_interface = domain + '.Mandos.Client'
62
 
version = "1.7.15"
 
60
version = "1.7.6"
63
61
 
64
62
try:
65
63
    dbus.OBJECT_MANAGER_IFACE
66
64
except AttributeError:
67
65
    dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
68
66
 
69
 
 
70
67
def isoformat_to_datetime(iso):
71
68
    "Parse an ISO 8601 date string to a datetime.datetime()"
72
69
    if not iso:
80
77
                             int(day),
81
78
                             int(hour),
82
79
                             int(minute),
83
 
                             int(second),            # Whole seconds
84
 
                             int(fraction*1000000))  # Microseconds
85
 
 
 
80
                             int(second),           # Whole seconds
 
81
                             int(fraction*1000000)) # Microseconds
86
82
 
87
83
class MandosClientPropertyCache(object):
88
84
    """This wraps a Mandos Client D-Bus proxy object, caches the
90
86
    changed.
91
87
    """
92
88
    def __init__(self, proxy_object=None, properties=None, **kwargs):
93
 
        self.proxy = proxy_object  # Mandos Client proxy object
 
89
        self.proxy = proxy_object # Mandos Client proxy object
94
90
        self.properties = dict() if properties is None else properties
95
91
        self.property_changed_match = (
96
92
            self.proxy.connect_to_signal("PropertiesChanged",
97
93
                                         self.properties_changed,
98
94
                                         dbus.PROPERTIES_IFACE,
99
95
                                         byte_arrays=True))
100
 
 
 
96
        
101
97
        if properties is None:
102
 
            self.properties.update(self.proxy.GetAll(
103
 
                client_interface,
104
 
                dbus_interface=dbus.PROPERTIES_IFACE))
105
 
 
 
98
            self.properties.update(
 
99
                self.proxy.GetAll(client_interface,
 
100
                                  dbus_interface
 
101
                                  = dbus.PROPERTIES_IFACE))
 
102
        
106
103
        super(MandosClientPropertyCache, self).__init__(**kwargs)
107
 
 
 
104
    
108
105
    def properties_changed(self, interface, properties, invalidated):
109
106
        """This is called whenever we get a PropertiesChanged signal
110
107
        It updates the changed properties in the "properties" dict.
112
109
        # Update properties dict with new value
113
110
        if interface == client_interface:
114
111
            self.properties.update(properties)
115
 
 
 
112
    
116
113
    def delete(self):
117
114
        self.property_changed_match.remove()
118
115
 
120
117
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
121
118
    """A Mandos Client which is visible on the screen.
122
119
    """
123
 
 
 
120
    
124
121
    def __init__(self, server_proxy_object=None, update_hook=None,
125
122
                 delete_hook=None, logger=None, **kwargs):
126
123
        # Called on update
131
128
        self.server_proxy_object = server_proxy_object
132
129
        # Logger
133
130
        self.logger = logger
134
 
 
 
131
        
135
132
        self._update_timer_callback_tag = None
136
 
 
 
133
        
137
134
        # The widget shown normally
138
135
        self._text_widget = urwid.Text("")
139
136
        # The widget shown when we have focus
141
138
        super(MandosClientWidget, self).__init__(**kwargs)
142
139
        self.update()
143
140
        self.opened = False
144
 
 
 
141
        
145
142
        self.match_objects = (
146
143
            self.proxy.connect_to_signal("CheckerCompleted",
147
144
                                         self.checker_completed,
165
162
                                         byte_arrays=True))
166
163
        self.logger('Created client {}'
167
164
                    .format(self.properties["Name"]), level=0)
168
 
 
 
165
    
169
166
    def using_timer(self, flag):
170
167
        """Call this method with True or False when timer should be
171
168
        activated or deactivated.
178
175
        elif not (flag or self._update_timer_callback_tag is None):
179
176
            GLib.source_remove(self._update_timer_callback_tag)
180
177
            self._update_timer_callback_tag = None
181
 
 
 
178
    
182
179
    def checker_completed(self, exitstatus, condition, command):
183
180
        if exitstatus == 0:
184
181
            self.logger('Checker for client {} (command "{}")'
198
195
                        .format(self.properties["Name"], command,
199
196
                                os.WTERMSIG(condition)))
200
197
        self.update()
201
 
 
 
198
    
202
199
    def checker_started(self, command):
203
200
        """Server signals that a checker started."""
204
201
        self.logger('Client {} started checker "{}"'
205
202
                    .format(self.properties["Name"],
206
203
                            command), level=0)
207
 
 
 
204
    
208
205
    def got_secret(self):
209
206
        self.logger('Client {} received its secret'
210
207
                    .format(self.properties["Name"]))
211
 
 
 
208
    
212
209
    def need_approval(self, timeout, default):
213
210
        if not default:
214
211
            message = 'Client {} needs approval within {} seconds'
216
213
            message = 'Client {} will get its secret in {} seconds'
217
214
        self.logger(message.format(self.properties["Name"],
218
215
                                   timeout/1000))
219
 
 
 
216
    
220
217
    def rejected(self, reason):
221
218
        self.logger('Client {} was rejected; reason: {}'
222
219
                    .format(self.properties["Name"], reason))
223
 
 
 
220
    
224
221
    def selectable(self):
225
222
        """Make this a "selectable" widget.
226
223
        This overrides the method from urwid.FlowWidget."""
227
224
        return True
228
 
 
 
225
    
229
226
    def rows(self, maxcolrow, focus=False):
230
227
        """How many rows this widget will occupy might depend on
231
228
        whether we have focus or not.
232
229
        This overrides the method from urwid.FlowWidget"""
233
230
        return self.current_widget(focus).rows(maxcolrow, focus=focus)
234
 
 
 
231
    
235
232
    def current_widget(self, focus=False):
236
233
        if focus or self.opened:
237
234
            return self._focus_widget
238
235
        return self._widget
239
 
 
 
236
    
240
237
    def update(self):
241
238
        "Called when what is visible on the screen should be updated."
242
239
        # How to add standout mode to a style
243
 
        with_standout = {"normal": "standout",
244
 
                         "bold": "bold-standout",
245
 
                         "underline-blink":
246
 
                         "underline-blink-standout",
247
 
                         "bold-underline-blink":
248
 
                         "bold-underline-blink-standout",
249
 
                         }
250
 
 
 
240
        with_standout = { "normal": "standout",
 
241
                          "bold": "bold-standout",
 
242
                          "underline-blink":
 
243
                              "underline-blink-standout",
 
244
                          "bold-underline-blink":
 
245
                              "bold-underline-blink-standout",
 
246
                          }
 
247
        
251
248
        # Rebuild focus and non-focus widgets using current properties
252
 
 
 
249
        
253
250
        # Base part of a client. Name!
254
251
        base = '{name}: '.format(name=self.properties["Name"])
255
252
        if not self.properties["Enabled"]:
256
253
            message = "DISABLED"
257
254
            self.using_timer(False)
258
255
        elif self.properties["ApprovalPending"]:
259
 
            timeout = datetime.timedelta(
260
 
                milliseconds=self.properties["ApprovalDelay"])
 
256
            timeout = datetime.timedelta(milliseconds
 
257
                                         = self.properties
 
258
                                         ["ApprovalDelay"])
261
259
            last_approval_request = isoformat_to_datetime(
262
260
                self.properties["LastApprovalRequest"])
263
261
            if last_approval_request is not None:
290
288
            message = "enabled"
291
289
            self.using_timer(False)
292
290
        self._text = "{}{}".format(base, message)
293
 
 
 
291
        
294
292
        if not urwid.supports_unicode():
295
293
            self._text = self._text.encode("ascii", "replace")
296
294
        textlist = [("normal", self._text)]
306
304
        # Run update hook, if any
307
305
        if self.update_hook is not None:
308
306
            self.update_hook()
309
 
 
 
307
    
310
308
    def update_timer(self):
311
309
        """called by GLib. Will indefinitely loop until
312
310
        GLib.source_remove() on tag is called
313
311
        """
314
312
        self.update()
315
313
        return True             # Keep calling this
316
 
 
 
314
    
317
315
    def delete(self, **kwargs):
318
316
        if self._update_timer_callback_tag is not None:
319
317
            GLib.source_remove(self._update_timer_callback_tag)
324
322
        if self.delete_hook is not None:
325
323
            self.delete_hook(self)
326
324
        return super(MandosClientWidget, self).delete(**kwargs)
327
 
 
 
325
    
328
326
    def render(self, maxcolrow, focus=False):
329
327
        """Render differently if we have focus.
330
328
        This overrides the method from urwid.FlowWidget"""
331
329
        return self.current_widget(focus).render(maxcolrow,
332
330
                                                 focus=focus)
333
 
 
 
331
    
334
332
    def keypress(self, maxcolrow, key):
335
333
        """Handle keys.
336
334
        This overrides the method from urwid.FlowWidget"""
337
335
        if key == "+":
338
336
            self.proxy.Set(client_interface, "Enabled",
339
 
                           dbus.Boolean(True), ignore_reply=True,
340
 
                           dbus_interface=dbus.PROPERTIES_IFACE)
 
337
                           dbus.Boolean(True), ignore_reply = True,
 
338
                           dbus_interface = dbus.PROPERTIES_IFACE)
341
339
        elif key == "-":
342
340
            self.proxy.Set(client_interface, "Enabled", False,
343
 
                           ignore_reply=True,
344
 
                           dbus_interface=dbus.PROPERTIES_IFACE)
 
341
                           ignore_reply = True,
 
342
                           dbus_interface = dbus.PROPERTIES_IFACE)
345
343
        elif key == "a":
346
344
            self.proxy.Approve(dbus.Boolean(True, variant_level=1),
347
 
                               dbus_interface=client_interface,
 
345
                               dbus_interface = client_interface,
348
346
                               ignore_reply=True)
349
347
        elif key == "d":
350
348
            self.proxy.Approve(dbus.Boolean(False, variant_level=1),
351
 
                               dbus_interface=client_interface,
 
349
                                  dbus_interface = client_interface,
352
350
                               ignore_reply=True)
353
351
        elif key == "R" or key == "_" or key == "ctrl k":
354
352
            self.server_proxy_object.RemoveClient(self.proxy
356
354
                                                  ignore_reply=True)
357
355
        elif key == "s":
358
356
            self.proxy.Set(client_interface, "CheckerRunning",
359
 
                           dbus.Boolean(True), ignore_reply=True,
360
 
                           dbus_interface=dbus.PROPERTIES_IFACE)
 
357
                           dbus.Boolean(True), ignore_reply = True,
 
358
                           dbus_interface = dbus.PROPERTIES_IFACE)
361
359
        elif key == "S":
362
360
            self.proxy.Set(client_interface, "CheckerRunning",
363
 
                           dbus.Boolean(False), ignore_reply=True,
364
 
                           dbus_interface=dbus.PROPERTIES_IFACE)
 
361
                           dbus.Boolean(False), ignore_reply = True,
 
362
                           dbus_interface = dbus.PROPERTIES_IFACE)
365
363
        elif key == "C":
366
 
            self.proxy.CheckedOK(dbus_interface=client_interface,
 
364
            self.proxy.CheckedOK(dbus_interface = client_interface,
367
365
                                 ignore_reply=True)
368
366
        # xxx
369
367
#         elif key == "p" or key == "=":
374
372
#             self.open()
375
373
        else:
376
374
            return key
377
 
 
 
375
    
378
376
    def properties_changed(self, interface, properties, invalidated):
379
377
        """Call self.update() if any properties changed.
380
378
        This overrides the method from MandosClientPropertyCache"""
381
 
        old_values = {key: self.properties.get(key)
382
 
                      for key in properties.keys()}
 
379
        old_values = { key: self.properties.get(key)
 
380
                       for key in properties.keys() }
383
381
        super(MandosClientWidget, self).properties_changed(
384
382
            interface, properties, invalidated)
385
383
        if any(old_values[key] != self.properties.get(key)
393
391
    use them as an excuse to shift focus away from this widget.
394
392
    """
395
393
    def keypress(self, *args, **kwargs):
396
 
        ret = (super(ConstrainedListBox, self)
397
 
               .keypress(*args, **kwargs))
 
394
        ret = super(ConstrainedListBox, self).keypress(*args, **kwargs)
398
395
        if ret in ("up", "down"):
399
396
            return
400
397
        return ret
406
403
    """
407
404
    def __init__(self, max_log_length=1000, log_level=1):
408
405
        DBusGMainLoop(set_as_default=True)
409
 
 
 
406
        
410
407
        self.screen = urwid.curses_display.Screen()
411
 
 
 
408
        
412
409
        self.screen.register_palette((
413
410
                ("normal",
414
411
                 "default", "default", None),
419
416
                ("standout",
420
417
                 "standout", "default", "standout"),
421
418
                ("bold-underline-blink",
422
 
                 "bold,underline,blink", "default",
423
 
                 "bold,underline,blink"),
 
419
                 "bold,underline,blink", "default", "bold,underline,blink"),
424
420
                ("bold-standout",
425
421
                 "bold,standout", "default", "bold,standout"),
426
422
                ("underline-blink-standout",
430
426
                 "bold,underline,blink,standout", "default",
431
427
                 "bold,underline,blink,standout"),
432
428
                ))
433
 
 
 
429
        
434
430
        if urwid.supports_unicode():
435
 
            self.divider = "─"  # \u2500
 
431
            self.divider = "─" # \u2500
 
432
            #self.divider = "━" # \u2501
436
433
        else:
437
 
            self.divider = "_"  # \u005f
438
 
 
 
434
            #self.divider = "-" # \u002d
 
435
            self.divider = "_" # \u005f
 
436
        
439
437
        self.screen.start()
440
 
 
 
438
        
441
439
        self.size = self.screen.get_cols_rows()
442
 
 
 
440
        
443
441
        self.clients = urwid.SimpleListWalker([])
444
442
        self.clients_dict = {}
445
 
 
 
443
        
446
444
        # We will add Text widgets to this list
447
445
        self.log = []
448
446
        self.max_log_length = max_log_length
449
 
 
 
447
        
450
448
        self.log_level = log_level
451
 
 
 
449
        
452
450
        # We keep a reference to the log widget so we can remove it
453
451
        # from the ListWalker without it getting destroyed
454
452
        self.logbox = ConstrainedListBox(self.log)
455
 
 
 
453
        
456
454
        # This keeps track of whether self.uilist currently has
457
455
        # self.logbox in it or not
458
456
        self.log_visible = True
459
457
        self.log_wrap = "any"
460
 
 
 
458
        
461
459
        self.rebuild()
462
460
        self.log_message_raw(("bold",
463
461
                              "Mandos Monitor version " + version))
464
462
        self.log_message_raw(("bold",
465
463
                              "q: Quit  ?: Help"))
466
 
 
 
464
        
467
465
        self.busname = domain + '.Mandos'
468
466
        self.main_loop = GLib.MainLoop()
469
 
 
 
467
    
470
468
    def client_not_found(self, fingerprint, address):
471
469
        self.log_message("Client with address {} and fingerprint {}"
472
470
                         " could not be found"
473
471
                         .format(address, fingerprint))
474
 
 
 
472
    
475
473
    def rebuild(self):
476
474
        """This rebuilds the User Interface.
477
475
        Call this when the widget layout needs to change"""
478
476
        self.uilist = []
479
 
        # self.uilist.append(urwid.ListBox(self.clients))
 
477
        #self.uilist.append(urwid.ListBox(self.clients))
480
478
        self.uilist.append(urwid.Frame(ConstrainedListBox(self.
481
479
                                                          clients),
482
 
                                       # header=urwid.Divider(),
 
480
                                       #header=urwid.Divider(),
483
481
                                       header=None,
484
 
                                       footer=urwid.Divider(
485
 
                                           div_char=self.divider)))
 
482
                                       footer=
 
483
                                       urwid.Divider(div_char=
 
484
                                                     self.divider)))
486
485
        if self.log_visible:
487
486
            self.uilist.append(self.logbox)
488
487
        self.topwidget = urwid.Pile(self.uilist)
489
 
 
 
488
    
490
489
    def log_message(self, message, level=1):
491
490
        """Log message formatted with timestamp"""
492
491
        if level < self.log_level:
494
493
        timestamp = datetime.datetime.now().isoformat()
495
494
        self.log_message_raw("{}: {}".format(timestamp, message),
496
495
                             level=level)
497
 
 
 
496
    
498
497
    def log_message_raw(self, markup, level=1):
499
498
        """Add a log message to the log buffer."""
500
499
        if level < self.log_level:
501
500
            return
502
501
        self.log.append(urwid.Text(markup, wrap=self.log_wrap))
503
 
        if self.max_log_length:
504
 
            if len(self.log) > self.max_log_length:
505
 
                del self.log[0:len(self.log)-self.max_log_length-1]
 
502
        if (self.max_log_length
 
503
            and len(self.log) > self.max_log_length):
 
504
            del self.log[0:len(self.log)-self.max_log_length-1]
506
505
        self.logbox.set_focus(len(self.logbox.body.contents),
507
506
                              coming_from="above")
508
507
        self.refresh()
509
 
 
 
508
    
510
509
    def toggle_log_display(self):
511
510
        """Toggle visibility of the log buffer."""
512
511
        self.log_visible = not self.log_visible
513
512
        self.rebuild()
514
513
        self.log_message("Log visibility changed to: {}"
515
514
                         .format(self.log_visible), level=0)
516
 
 
 
515
    
517
516
    def change_log_display(self):
518
517
        """Change type of log display.
519
518
        Currently, this toggles wrapping of text lines."""
525
524
            textwidget.set_wrap_mode(self.log_wrap)
526
525
        self.log_message("Wrap mode: {}".format(self.log_wrap),
527
526
                         level=0)
528
 
 
 
527
    
529
528
    def find_and_remove_client(self, path, interfaces):
530
529
        """Find a client by its object path and remove it.
531
 
 
 
530
        
532
531
        This is connected to the InterfacesRemoved signal from the
533
532
        Mandos server object."""
534
533
        if client_interface not in interfaces:
542
541
                             .format(path))
543
542
            return
544
543
        client.delete()
545
 
 
 
544
    
546
545
    def add_new_client(self, path, ifs_and_props):
547
546
        """Find a client by its object path and remove it.
548
 
 
 
547
        
549
548
        This is connected to the InterfacesAdded signal from the
550
549
        Mandos server object.
551
550
        """
553
552
            # Not a Mandos client object; ignore
554
553
            return
555
554
        client_proxy_object = self.bus.get_object(self.busname, path)
556
 
        self.add_client(MandosClientWidget(
557
 
            server_proxy_object=self.mandos_serv,
558
 
            proxy_object=client_proxy_object,
559
 
            update_hook=self.refresh,
560
 
            delete_hook=self.remove_client,
561
 
            logger=self.log_message,
562
 
            properties=dict(ifs_and_props[client_interface])),
 
555
        self.add_client(MandosClientWidget(server_proxy_object
 
556
                                           =self.mandos_serv,
 
557
                                           proxy_object
 
558
                                           =client_proxy_object,
 
559
                                           update_hook
 
560
                                           =self.refresh,
 
561
                                           delete_hook
 
562
                                           =self.remove_client,
 
563
                                           logger
 
564
                                           =self.log_message,
 
565
                                           properties
 
566
                                           = dict(ifs_and_props[
 
567
                                               client_interface])),
563
568
                        path=path)
564
 
 
 
569
    
565
570
    def add_client(self, client, path=None):
566
571
        self.clients.append(client)
567
572
        if path is None:
569
574
        self.clients_dict[path] = client
570
575
        self.clients.sort(key=lambda c: c.properties["Name"])
571
576
        self.refresh()
572
 
 
 
577
    
573
578
    def remove_client(self, client, path=None):
574
579
        self.clients.remove(client)
575
580
        if path is None:
576
581
            path = client.proxy.object_path
577
582
        del self.clients_dict[path]
578
583
        self.refresh()
579
 
 
 
584
    
580
585
    def refresh(self):
581
586
        """Redraw the screen"""
582
587
        canvas = self.topwidget.render(self.size, focus=True)
583
588
        self.screen.draw_screen(self.size, canvas)
584
 
 
 
589
    
585
590
    def run(self):
586
591
        """Start the main loop and exit when it's done."""
587
592
        self.bus = dbus.SystemBus()
588
593
        mandos_dbus_objc = self.bus.get_object(
589
594
            self.busname, "/", follow_name_owner_changes=True)
590
 
        self.mandos_serv = dbus.Interface(
591
 
            mandos_dbus_objc, dbus_interface=server_interface)
 
595
        self.mandos_serv = dbus.Interface(mandos_dbus_objc,
 
596
                                          dbus_interface
 
597
                                          = server_interface)
592
598
        try:
593
599
            mandos_clients = (self.mandos_serv
594
600
                              .GetAllClientsWithProperties())
595
601
            if not mandos_clients:
596
 
                self.log_message_raw(("bold",
597
 
                                      "Note: Server has no clients."))
 
602
                self.log_message_raw(("bold", "Note: Server has no clients."))
598
603
        except dbus.exceptions.DBusException:
599
 
            self.log_message_raw(("bold",
600
 
                                  "Note: No Mandos server running."))
 
604
            self.log_message_raw(("bold", "Note: No Mandos server running."))
601
605
            mandos_clients = dbus.Dictionary()
602
 
 
 
606
        
603
607
        (self.mandos_serv
604
608
         .connect_to_signal("InterfacesRemoved",
605
609
                            self.find_and_remove_client,
606
 
                            dbus_interface=dbus.OBJECT_MANAGER_IFACE,
 
610
                            dbus_interface
 
611
                            = dbus.OBJECT_MANAGER_IFACE,
607
612
                            byte_arrays=True))
608
613
        (self.mandos_serv
609
614
         .connect_to_signal("InterfacesAdded",
610
615
                            self.add_new_client,
611
 
                            dbus_interface=dbus.OBJECT_MANAGER_IFACE,
 
616
                            dbus_interface
 
617
                            = dbus.OBJECT_MANAGER_IFACE,
612
618
                            byte_arrays=True))
613
619
        (self.mandos_serv
614
620
         .connect_to_signal("ClientNotFound",
618
624
        for path, client in mandos_clients.items():
619
625
            client_proxy_object = self.bus.get_object(self.busname,
620
626
                                                      path)
621
 
            self.add_client(MandosClientWidget(
622
 
                server_proxy_object=self.mandos_serv,
623
 
                proxy_object=client_proxy_object,
624
 
                properties=client,
625
 
                update_hook=self.refresh,
626
 
                delete_hook=self.remove_client,
627
 
                logger=self.log_message),
 
627
            self.add_client(MandosClientWidget(server_proxy_object
 
628
                                               =self.mandos_serv,
 
629
                                               proxy_object
 
630
                                               =client_proxy_object,
 
631
                                               properties=client,
 
632
                                               update_hook
 
633
                                               =self.refresh,
 
634
                                               delete_hook
 
635
                                               =self.remove_client,
 
636
                                               logger
 
637
                                               =self.log_message),
628
638
                            path=path)
629
 
 
 
639
        
630
640
        self.refresh()
631
641
        self._input_callback_tag = (GLib.io_add_watch
632
642
                                    (sys.stdin.fileno(),
636
646
        # Main loop has finished, we should close everything now
637
647
        GLib.source_remove(self._input_callback_tag)
638
648
        self.screen.stop()
639
 
 
 
649
    
640
650
    def stop(self):
641
651
        self.main_loop.quit()
642
 
 
 
652
    
643
653
    def process_input(self, source, condition):
644
654
        keys = self.screen.get_input()
645
 
        translations = {"ctrl n": "down",       # Emacs
646
 
                        "ctrl p": "up",         # Emacs
647
 
                        "ctrl v": "page down",  # Emacs
648
 
                        "meta v": "page up",    # Emacs
649
 
                        " ": "page down",       # less
650
 
                        "f": "page down",       # less
651
 
                        "b": "page up",         # less
652
 
                        "j": "down",            # vi
653
 
                        "k": "up",              # vi
654
 
                        }
 
655
        translations = { "ctrl n": "down",      # Emacs
 
656
                         "ctrl p": "up",        # Emacs
 
657
                         "ctrl v": "page down", # Emacs
 
658
                         "meta v": "page up",   # Emacs
 
659
                         " ": "page down",      # less
 
660
                         "f": "page down",      # less
 
661
                         "b": "page up",        # less
 
662
                         "j": "down",           # vi
 
663
                         "k": "up",             # vi
 
664
                         }
655
665
        for key in keys:
656
666
            try:
657
667
                key = translations[key]
658
668
            except KeyError:    # :-)
659
669
                pass
660
 
 
 
670
            
661
671
            if key == "q" or key == "Q":
662
672
                self.stop()
663
673
                break
711
721
                else:
712
722
                    self.log_level = 0
713
723
                    self.log_message("Verbose mode: On")
714
 
            # elif (key == "end" or key == "meta >" or key == "G"
715
 
            #       or key == ">"):
716
 
            #     pass            # xxx end-of-buffer
717
 
            # elif (key == "home" or key == "meta <" or key == "g"
718
 
            #       or key == "<"):
719
 
            #     pass            # xxx beginning-of-buffer
720
 
            # elif key == "ctrl e" or key == "$":
721
 
            #     pass            # xxx move-end-of-line
722
 
            # elif key == "ctrl a" or key == "^":
723
 
            #     pass            # xxx move-beginning-of-line
724
 
            # elif key == "ctrl b" or key == "meta (" or key == "h":
725
 
            #     pass            # xxx left
726
 
            # elif key == "ctrl f" or key == "meta )" or key == "l":
727
 
            #     pass            # xxx right
728
 
            # elif key == "a":
729
 
            #     pass            # scroll up log
730
 
            # elif key == "z":
731
 
            #     pass            # scroll down log
 
724
            #elif (key == "end" or key == "meta >" or key == "G"
 
725
            #      or key == ">"):
 
726
            #    pass            # xxx end-of-buffer
 
727
            #elif (key == "home" or key == "meta <" or key == "g"
 
728
            #      or key == "<"):
 
729
            #    pass            # xxx beginning-of-buffer
 
730
            #elif key == "ctrl e" or key == "$":
 
731
            #    pass            # xxx move-end-of-line
 
732
            #elif key == "ctrl a" or key == "^":
 
733
            #    pass            # xxx move-beginning-of-line
 
734
            #elif key == "ctrl b" or key == "meta (" or key == "h":
 
735
            #    pass            # xxx left
 
736
            #elif key == "ctrl f" or key == "meta )" or key == "l":
 
737
            #    pass            # xxx right
 
738
            #elif key == "a":
 
739
            #    pass            # scroll up log
 
740
            #elif key == "z":
 
741
            #    pass            # scroll down log
732
742
            elif self.topwidget.selectable():
733
743
                self.topwidget.keypress(self.size, key)
734
744
                self.refresh()
735
745
        return True
736
746
 
737
 
 
738
747
ui = UserInterface()
739
748
try:
740
749
    ui.run()