2
2
# -*- mode: python; coding: utf-8 -*-
4
# Mandos Monitor - Control and monitor the Mandos server
6
# Copyright © 2009-2011 Teddy Hogeborn
7
# Copyright © 2009-2011 Björn Påhlsson
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
11
# the Free Software Foundation, either version 3 of the License, or
12
# (at your option) any later version.
14
# This program is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
# GNU General Public License for more details.
19
# You should have received a copy of the GNU General Public License
20
# along with this program. If not, see <http://www.gnu.org/licenses/>.
22
# Contact the authors at <mandos@fukt.bsnet.se>.
25
from __future__ import (division, absolute_import, print_function,
4
from __future__ import division, absolute_import, with_statement
66
44
"Parse an ISO 8601 date string to a datetime.datetime()"
69
d, t = iso.split("T", 1)
70
year, month, day = d.split("-", 2)
71
hour, minute, second = t.split(":", 2)
47
d, t = iso.split(u"T", 1)
48
year, month, day = d.split(u"-", 2)
49
hour, minute, second = t.split(u":", 2)
72
50
second, fraction = divmod(float(second), 1)
73
51
return datetime.datetime(int(year),
87
65
self.proxy = proxy_object # Mandos Client proxy object
89
67
self.properties = dict()
90
self.property_changed_match = (
91
self.proxy.connect_to_signal("PropertyChanged",
92
self.property_changed,
68
self.proxy.connect_to_signal(u"PropertyChanged",
69
self.property_changed,
96
73
self.properties.update(
97
74
self.proxy.GetAll(client_interface,
98
75
dbus_interface = dbus.PROPERTIES_IFACE))
100
#XXX This breaks good super behaviour
77
#XXX This break good super behaviour!
101
78
# super(MandosClientPropertyCache, self).__init__(
102
79
# *args, **kwargs)
134
106
self.last_checker_failed = False
136
108
# The widget shown normally
137
self._text_widget = urwid.Text("")
109
self._text_widget = urwid.Text(u"")
138
110
# The widget shown when we have focus
139
self._focus_text_widget = urwid.Text("")
111
self._focus_text_widget = urwid.Text(u"")
140
112
super(MandosClientWidget, self).__init__(
141
113
update_hook=update_hook, delete_hook=delete_hook,
161
133
if self.need_approval:
162
134
self.using_timer(True)
164
self.match_objects = (
165
self.proxy.connect_to_signal("CheckerCompleted",
166
self.checker_completed,
169
self.proxy.connect_to_signal("CheckerStarted",
170
self.checker_started,
173
self.proxy.connect_to_signal("GotSecret",
177
self.proxy.connect_to_signal("NeedApproval",
181
self.proxy.connect_to_signal("Rejected",
185
#self.logger('Created client %s' % (self.properties["Name"]))
136
self.proxy.connect_to_signal(u"CheckerCompleted",
137
self.checker_completed,
140
self.proxy.connect_to_signal(u"CheckerStarted",
141
self.checker_started,
144
self.proxy.connect_to_signal(u"GotSecret",
148
self.proxy.connect_to_signal(u"NeedApproval",
152
self.proxy.connect_to_signal(u"Rejected",
187
157
def property_changed(self, property=None, value=None):
188
158
super(self, MandosClientWidget).property_changed(property,
190
if property == "ApprovalPending":
160
if property == u"ApprovalPending":
191
161
using_timer(bool(value))
193
163
def using_timer(self, flag):
222
192
self.last_checker_failed = True
223
193
self.using_timer(True)
224
194
if os.WIFEXITED(condition):
225
self.logger('Checker for client %s (command "%s")'
226
' failed with exit code %s'
227
% (self.properties["Name"], command,
195
self.logger(u'Checker for client %s (command "%s")'
196
u' failed with exit code %s'
197
% (self.properties[u"Name"], command,
228
198
os.WEXITSTATUS(condition)))
229
199
elif os.WIFSIGNALED(condition):
230
self.logger('Checker for client %s (command "%s")'
231
' was killed by signal %s'
232
% (self.properties["Name"], command,
200
self.logger(u'Checker for client %s (command "%s")'
201
u' was killed by signal %s'
202
% (self.properties[u"Name"], command,
233
203
os.WTERMSIG(condition)))
234
204
elif os.WCOREDUMP(condition):
235
self.logger('Checker for client %s (command "%s")'
237
% (self.properties["Name"], command))
205
self.logger(u'Checker for client %s (command "%s")'
207
% (self.properties[u"Name"], command))
239
self.logger('Checker for client %s completed'
209
self.logger(u'Checker for client %s completed'
243
213
def checker_started(self, command):
244
#self.logger('Client %s started checker "%s"'
245
# % (self.properties["Name"], unicode(command)))
214
#self.logger(u'Client %s started checker "%s"'
215
# % (self.properties[u"Name"], unicode(command)))
248
218
def got_secret(self):
249
219
self.last_checker_failed = False
250
self.logger('Client %s received its secret'
251
% self.properties["Name"])
220
self.logger(u'Client %s received its secret'
221
% self.properties[u"Name"])
253
223
def need_approval(self, timeout, default):
255
message = 'Client %s needs approval within %s seconds'
225
message = u'Client %s needs approval within %s seconds'
257
message = 'Client %s will get its secret in %s seconds'
227
message = u'Client %s will get its secret in %s seconds'
258
228
self.logger(message
259
% (self.properties["Name"], timeout/1000))
229
% (self.properties[u"Name"], timeout/1000))
260
230
self.using_timer(True)
262
232
def rejected(self, reason):
263
self.logger('Client %s was rejected; reason: %s'
264
% (self.properties["Name"], reason))
233
self.logger(u'Client %s was rejected; reason: %s'
234
% (self.properties[u"Name"], reason))
266
236
def selectable(self):
267
237
"""Make this a "selectable" widget.
268
238
This overrides the method from urwid.FlowWidget."""
271
def rows(self, maxcolrow, focus=False):
241
def rows(self, (maxcol,), focus=False):
272
242
"""How many rows this widget will occupy might depend on
273
243
whether we have focus or not.
274
244
This overrides the method from urwid.FlowWidget"""
275
return self.current_widget(focus).rows(maxcolrow, focus=focus)
245
return self.current_widget(focus).rows((maxcol,), focus=focus)
277
247
def current_widget(self, focus=False):
278
248
if focus or self.opened:
282
252
def update(self):
283
253
"Called when what is visible on the screen should be updated."
284
254
# How to add standout mode to a style
285
with_standout = { "normal": "standout",
286
"bold": "bold-standout",
288
"underline-blink-standout",
289
"bold-underline-blink":
290
"bold-underline-blink-standout",
255
with_standout = { u"normal": u"standout",
256
u"bold": u"bold-standout",
258
u"underline-blink-standout",
259
u"bold-underline-blink":
260
u"bold-underline-blink-standout",
293
263
# Rebuild focus and non-focus widgets using current properties
295
265
# Base part of a client. Name!
297
% {"name": self.properties["Name"]})
298
if not self.properties["Enabled"]:
300
elif self.properties["ApprovalPending"]:
266
base = (u'%(name)s: '
267
% {u"name": self.properties[u"Name"]})
268
if not self.properties[u"Enabled"]:
269
message = u"DISABLED"
270
elif self.properties[u"ApprovalPending"]:
301
271
timeout = datetime.timedelta(milliseconds
302
272
= self.properties
304
274
last_approval_request = isoformat_to_datetime(
305
self.properties["LastApprovalRequest"])
275
self.properties[u"LastApprovalRequest"])
306
276
if last_approval_request is not None:
307
277
timer = timeout - (datetime.datetime.utcnow()
308
278
- last_approval_request)
310
280
timer = datetime.timedelta()
311
if self.properties["ApprovedByDefault"]:
312
message = "Approval in %s. (d)eny?"
281
if self.properties[u"ApprovedByDefault"]:
282
message = u"Approval in %s. (d)eny?"
314
message = "Denial in %s. (a)pprove?"
284
message = u"Denial in %s. (a)pprove?"
315
285
message = message % unicode(timer).rsplit(".", 1)[0]
316
286
elif self.last_checker_failed:
317
287
timeout = datetime.timedelta(milliseconds
318
288
= self.properties
320
290
last_ok = isoformat_to_datetime(
321
max((self.properties["LastCheckedOK"]
322
or self.properties["Created"]),
323
self.properties["LastEnabled"]))
291
max((self.properties[u"LastCheckedOK"]
292
or self.properties[u"Created"]),
293
self.properties[u"LastEnabled"]))
324
294
timer = timeout - (datetime.datetime.utcnow() - last_ok)
325
message = ('A checker has failed! Time until client'
295
message = (u'A checker has failed! Time until client'
296
u' gets disabled: %s'
327
297
% unicode(timer).rsplit(".", 1)[0])
330
300
self._text = "%s%s" % (base, message)
332
302
if not urwid.supports_unicode():
333
303
self._text = self._text.encode("ascii", "replace")
334
textlist = [("normal", self._text)]
304
textlist = [(u"normal", self._text)]
335
305
self._text_widget.set_text(textlist)
336
306
self._focus_text_widget.set_text([(with_standout[text[0]],
351
321
return True # Keep calling this
353
def delete(self, *args, **kwargs):
354
324
if self._update_timer_callback_tag is not None:
355
325
gobject.source_remove(self._update_timer_callback_tag)
356
326
self._update_timer_callback_tag = None
357
for match in self.match_objects:
359
self.match_objects = ()
360
327
if self.delete_hook is not None:
361
328
self.delete_hook(self)
362
return super(MandosClientWidget, self).delete(*args, **kwargs)
364
def render(self, maxcolrow, focus=False):
330
def render(self, (maxcol,), focus=False):
365
331
"""Render differently if we have focus.
366
332
This overrides the method from urwid.FlowWidget"""
367
return self.current_widget(focus).render(maxcolrow,
333
return self.current_widget(focus).render((maxcol,),
370
def keypress(self, maxcolrow, key):
336
def keypress(self, (maxcol,), key):
372
338
This overrides the method from urwid.FlowWidget"""
374
self.proxy.Enable(dbus_interface = client_interface,
377
self.proxy.Disable(dbus_interface = client_interface,
340
self.proxy.Enable(dbus_interface = client_interface)
342
self.proxy.Disable(dbus_interface = client_interface)
380
344
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
381
dbus_interface = client_interface,
345
dbus_interface = client_interface)
384
347
self.proxy.Approve(dbus.Boolean(False, variant_level=1),
385
dbus_interface = client_interface,
387
elif key == "R" or key == "_" or key == "ctrl k":
348
dbus_interface = client_interface)
349
elif key == u"r" or key == u"_" or key == u"ctrl k":
388
350
self.server_proxy_object.RemoveClient(self.proxy
392
self.proxy.StartChecker(dbus_interface = client_interface,
395
self.proxy.StopChecker(dbus_interface = client_interface,
398
self.proxy.CheckedOK(dbus_interface = client_interface,
353
self.proxy.StartChecker(dbus_interface = client_interface)
355
self.proxy.StopChecker(dbus_interface = client_interface)
357
self.proxy.CheckedOK(dbus_interface = client_interface)
401
# elif key == "p" or key == "=":
359
# elif key == u"p" or key == "=":
402
360
# self.proxy.pause()
403
# elif key == "u" or key == ":":
361
# elif key == u"u" or key == ":":
404
362
# self.proxy.unpause()
363
# elif key == u"RET":
424
382
"down" key presses, thus not allowing any containing widgets to
425
383
use them as an excuse to shift focus away from this widget.
427
def keypress(self, maxcolrow, key):
428
ret = super(ConstrainedListBox, self).keypress(maxcolrow, key)
429
if ret in ("up", "down"):
385
def keypress(self, (maxcol, maxrow), key):
386
ret = super(ConstrainedListBox, self).keypress((maxcol,
388
if ret in (u"up", u"down"):
441
400
self.screen = urwid.curses_display.Screen()
443
402
self.screen.register_palette((
445
"default", "default", None),
447
"default", "default", "bold"),
449
"default", "default", "underline"),
451
"default", "default", "standout"),
452
("bold-underline-blink",
453
"default", "default", ("bold", "underline")),
455
"default", "default", ("bold", "standout")),
456
("underline-blink-standout",
457
"default", "default", ("underline", "standout")),
458
("bold-underline-blink-standout",
459
"default", "default", ("bold", "underline",
404
u"default", u"default", None),
406
u"default", u"default", u"bold"),
408
u"default", u"default", u"underline"),
410
u"default", u"default", u"standout"),
411
(u"bold-underline-blink",
412
u"default", u"default", (u"bold", u"underline")),
414
u"default", u"default", (u"bold", u"standout")),
415
(u"underline-blink-standout",
416
u"default", u"default", (u"underline", u"standout")),
417
(u"bold-underline-blink-standout",
418
u"default", u"default", (u"bold", u"underline",
463
422
if urwid.supports_unicode():
464
self.divider = "─" # \u2500
465
#self.divider = "━" # \u2501
423
self.divider = u"─" # \u2500
424
#self.divider = u"━" # \u2501
467
#self.divider = "-" # \u002d
468
self.divider = "_" # \u005f
426
#self.divider = u"-" # \u002d
427
self.divider = u"_" # \u005f
470
429
self.screen.start()
485
444
# This keeps track of whether self.uilist currently has
486
445
# self.logbox in it or not
487
446
self.log_visible = True
488
self.log_wrap = "any"
447
self.log_wrap = u"any"
491
self.log_message_raw(("bold",
492
"Mandos Monitor version " + version))
493
self.log_message_raw(("bold",
450
self.log_message_raw((u"bold",
451
u"Mandos Monitor version " + version))
452
self.log_message_raw((u"bold",
496
455
self.busname = domain + '.Mandos'
497
456
self.main_loop = gobject.MainLoop()
498
457
self.bus = dbus.SystemBus()
499
458
mandos_dbus_objc = self.bus.get_object(
500
self.busname, "/", follow_name_owner_changes=True)
459
self.busname, u"/", follow_name_owner_changes=True)
501
460
self.mandos_serv = dbus.Interface(mandos_dbus_objc,
503
462
= server_interface)
508
467
mandos_clients = dbus.Dictionary()
510
469
(self.mandos_serv
511
.connect_to_signal("ClientRemoved",
470
.connect_to_signal(u"ClientRemoved",
512
471
self.find_and_remove_client,
513
472
dbus_interface=server_interface,
514
473
byte_arrays=True))
515
474
(self.mandos_serv
516
.connect_to_signal("ClientAdded",
475
.connect_to_signal(u"ClientAdded",
517
476
self.add_new_client,
518
477
dbus_interface=server_interface,
519
478
byte_arrays=True))
520
479
(self.mandos_serv
521
.connect_to_signal("ClientNotFound",
480
.connect_to_signal(u"ClientNotFound",
522
481
self.client_not_found,
523
482
dbus_interface=server_interface,
524
483
byte_arrays=True))
571
530
and len(self.log) > self.max_log_length):
572
531
del self.log[0:len(self.log)-self.max_log_length-1]
573
532
self.logbox.set_focus(len(self.logbox.body.contents),
533
coming_from=u"above")
577
536
def toggle_log_display(self):
578
537
"""Toggle visibility of the log buffer."""
579
538
self.log_visible = not self.log_visible
581
#self.log_message("Log visibility changed to: "
540
#self.log_message(u"Log visibility changed to: "
582
541
# + unicode(self.log_visible))
584
543
def change_log_display(self):
585
544
"""Change type of log display.
586
545
Currently, this toggles wrapping of text lines."""
587
if self.log_wrap == "clip":
588
self.log_wrap = "any"
546
if self.log_wrap == u"clip":
547
self.log_wrap = u"any"
590
self.log_wrap = "clip"
549
self.log_wrap = u"clip"
591
550
for textwidget in self.log:
592
551
textwidget.set_wrap_mode(self.log_wrap)
593
#self.log_message("Wrap mode: " + self.log_wrap)
552
#self.log_message(u"Wrap mode: " + self.log_wrap)
595
554
def find_and_remove_client(self, path, name):
596
"""Find a client by its object path and remove it.
555
"""Find an client from its object path and remove it.
598
557
This is connected to the ClientRemoved signal from the
599
558
Mandos server object."""
663
620
def process_input(self, source, condition):
664
621
keys = self.screen.get_input()
665
translations = { "ctrl n": "down", # Emacs
666
"ctrl p": "up", # Emacs
667
"ctrl v": "page down", # Emacs
668
"meta v": "page up", # Emacs
669
" ": "page down", # less
670
"f": "page down", # less
671
"b": "page up", # less
622
translations = { u"ctrl n": u"down", # Emacs
623
u"ctrl p": u"up", # Emacs
624
u"ctrl v": u"page down", # Emacs
625
u"meta v": u"page up", # Emacs
626
u" ": u"page down", # less
627
u"f": u"page down", # less
628
u"b": u"page up", # less
678
635
except KeyError: # :-)
681
if key == "q" or key == "Q":
638
if key == u"q" or key == u"Q":
684
elif key == "window resize":
641
elif key == u"window resize":
685
642
self.size = self.screen.get_cols_rows()
687
elif key == "\f": # Ctrl-L
644
elif key == u"\f": # Ctrl-L
689
elif key == "l" or key == "D":
646
elif key == u"l" or key == u"D":
690
647
self.toggle_log_display()
692
elif key == "w" or key == "i":
649
elif key == u"w" or key == u"i":
693
650
self.change_log_display()
695
elif key == "?" or key == "f1" or key == "esc":
652
elif key == u"?" or key == u"f1" or key == u"esc":
696
653
if not self.log_visible:
697
654
self.log_visible = True
699
self.log_message_raw(("bold",
703
"l: Log window toggle",
704
"TAB: Switch window",
706
self.log_message_raw(("bold",
712
"s: Start new checker",
656
self.log_message_raw((u"bold",
660
u"l: Log window toggle",
661
u"TAB: Switch window",
663
self.log_message_raw((u"bold",
669
u"s: Start new checker",
719
676
if self.topwidget.get_focus() is self.logbox:
720
677
self.topwidget.set_focus(0)
722
679
self.topwidget.set_focus(self.logbox)
724
#elif (key == "end" or key == "meta >" or key == "G"
681
#elif (key == u"end" or key == u"meta >" or key == u"G"
726
683
# pass # xxx end-of-buffer
727
#elif (key == "home" or key == "meta <" or key == "g"
684
#elif (key == u"home" or key == u"meta <" or key == u"g"
729
686
# pass # xxx beginning-of-buffer
730
#elif key == "ctrl e" or key == "$":
687
#elif key == u"ctrl e" or key == u"$":
731
688
# pass # xxx move-end-of-line
732
#elif key == "ctrl a" or key == "^":
689
#elif key == u"ctrl a" or key == u"^":
733
690
# pass # xxx move-beginning-of-line
734
#elif key == "ctrl b" or key == "meta (" or key == "h":
691
#elif key == u"ctrl b" or key == u"meta (" or key == u"h":
735
692
# pass # xxx left
736
#elif key == "ctrl f" or key == "meta )" or key == "l":
693
#elif key == u"ctrl f" or key == u"meta )" or key == u"l":
737
694
# pass # xxx right
739
696
# pass # scroll up log
741
698
# pass # scroll down log
742
699
elif self.topwidget.selectable():
743
700
self.topwidget.keypress(self.size, key)