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
62
33
urwid.curses_display.curses.A_UNDERLINE |= (
63
34
urwid.curses_display.curses.A_BLINK)
65
def isoformat_to_datetime(iso):
66
"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)
72
second, fraction = divmod(float(second), 1)
73
return datetime.datetime(int(year),
78
int(second), # Whole seconds
79
int(fraction*1000000)) # Microseconds
81
36
class MandosClientPropertyCache(object):
82
37
"""This wraps a Mandos Client D-Bus proxy object, caches the
83
38
properties and calls a hook function when any of them are
86
def __init__(self, proxy_object=None, *args, **kwargs):
41
def __init__(self, proxy_object=None, properties=None, *args,
87
43
self.proxy = proxy_object # Mandos Client proxy object
89
self.properties = dict()
90
self.property_changed_match = (
91
self.proxy.connect_to_signal("PropertyChanged",
92
self.property_changed,
45
if properties is None:
46
self.properties = dict()
48
self.properties = properties
49
self.proxy.connect_to_signal(u"PropertyChanged",
50
self.property_changed,
96
self.properties.update(
97
self.proxy.GetAll(client_interface,
98
dbus_interface = dbus.PROPERTIES_IFACE))
100
#XXX This breaks good super behaviour
101
# super(MandosClientPropertyCache, self).__init__(
54
if properties is None:
55
self.properties.update(self.proxy.GetAll(client_interface,
57
dbus.PROPERTIES_IFACE))
58
super(MandosClientPropertyCache, self).__init__(
59
proxy_object=proxy_object,
60
properties=properties, *args, **kwargs)
104
62
def property_changed(self, property=None, value=None):
105
63
"""This is called whenever we get a PropertyChanged signal
130
83
self.logger = logger
132
self._update_timer_callback_tag = None
133
self._update_timer_callback_lock = 0
134
self.last_checker_failed = False
136
85
# The widget shown normally
137
self._text_widget = urwid.Text("")
86
self._text_widget = urwid.Text(u"")
138
87
# The widget shown when we have focus
139
self._focus_text_widget = urwid.Text("")
88
self._focus_text_widget = urwid.Text(u"")
140
89
super(MandosClientWidget, self).__init__(
141
90
update_hook=update_hook, delete_hook=delete_hook,
144
93
self.opened = False
146
last_checked_ok = isoformat_to_datetime(self.properties
148
if last_checked_ok is None:
149
self.last_checker_failed = True
151
self.last_checker_failed = ((datetime.datetime.utcnow()
158
if self.last_checker_failed:
159
self.using_timer(True)
161
if self.need_approval:
162
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"]))
187
def property_changed(self, property=None, value=None):
188
super(self, MandosClientWidget).property_changed(property,
190
if property == "ApprovalPending":
191
using_timer(bool(value))
193
def using_timer(self, flag):
194
"""Call this method with True or False when timer should be
195
activated or deactivated.
197
old = self._update_timer_callback_lock
199
self._update_timer_callback_lock += 1
201
self._update_timer_callback_lock -= 1
202
if old == 0 and self._update_timer_callback_lock:
203
self._update_timer_callback_tag = (gobject.timeout_add
206
elif old and self._update_timer_callback_lock == 0:
207
gobject.source_remove(self._update_timer_callback_tag)
208
self._update_timer_callback_tag = None
94
self.proxy.connect_to_signal(u"CheckerCompleted",
95
self.checker_completed,
98
self.proxy.connect_to_signal(u"CheckerStarted",
102
self.proxy.connect_to_signal(u"GotSecret",
106
self.proxy.connect_to_signal(u"Rejected",
210
111
def checker_completed(self, exitstatus, condition, command):
211
112
if exitstatus == 0:
212
if self.last_checker_failed:
213
self.last_checker_failed = False
214
self.using_timer(False)
215
#self.logger('Checker for client %s (command "%s")'
217
# % (self.properties["Name"], command))
113
self.logger(u'Checker for client %s (command "%s")'
115
% (self.properties[u"name"], command))
221
if not self.last_checker_failed:
222
self.last_checker_failed = True
223
self.using_timer(True)
224
117
if os.WIFEXITED(condition):
225
self.logger('Checker for client %s (command "%s")'
226
' failed with exit code %s'
227
% (self.properties["Name"], command,
118
self.logger(u'Checker for client %s (command "%s")'
119
u' failed with exit code %s'
120
% (self.properties[u"name"], command,
228
121
os.WEXITSTATUS(condition)))
229
elif os.WIFSIGNALED(condition):
230
self.logger('Checker for client %s (command "%s")'
231
' was killed by signal %s'
232
% (self.properties["Name"], command,
123
if os.WIFSIGNALED(condition):
124
self.logger(u'Checker for client %s (command "%s")'
125
u' was killed by signal %s'
126
% (self.properties[u"name"], command,
233
127
os.WTERMSIG(condition)))
234
elif os.WCOREDUMP(condition):
235
self.logger('Checker for client %s (command "%s")'
237
% (self.properties["Name"], command))
239
self.logger('Checker for client %s completed'
129
if os.WCOREDUMP(condition):
130
self.logger(u'Checker for client %s (command "%s")'
132
% (self.properties[u"name"], command))
133
self.logger(u'Checker for client %s completed mysteriously')
243
135
def checker_started(self, command):
244
#self.logger('Client %s started checker "%s"'
245
# % (self.properties["Name"], unicode(command)))
136
self.logger(u'Client %s started checker "%s"'
137
% (self.properties[u"name"], unicode(command)))
248
139
def got_secret(self):
249
self.last_checker_failed = False
250
self.logger('Client %s received its secret'
251
% self.properties["Name"])
253
def need_approval(self, timeout, default):
255
message = 'Client %s needs approval within %s seconds'
257
message = 'Client %s will get its secret in %s seconds'
259
% (self.properties["Name"], timeout/1000))
260
self.using_timer(True)
262
def rejected(self, reason):
263
self.logger('Client %s was rejected; reason: %s'
264
% (self.properties["Name"], reason))
140
self.logger(u'Client %s received its secret'
141
% self.properties[u"name"])
144
self.logger(u'Client %s was rejected'
145
% self.properties[u"name"])
266
147
def selectable(self):
267
148
"""Make this a "selectable" widget.
268
149
This overrides the method from urwid.FlowWidget."""
271
def rows(self, maxcolrow, focus=False):
152
def rows(self, (maxcol,), focus=False):
272
153
"""How many rows this widget will occupy might depend on
273
154
whether we have focus or not.
274
155
This overrides the method from urwid.FlowWidget"""
275
return self.current_widget(focus).rows(maxcolrow, focus=focus)
156
return self.current_widget(focus).rows((maxcol,), focus=focus)
277
158
def current_widget(self, focus=False):
278
159
if focus or self.opened:
282
163
def update(self):
283
164
"Called when what is visible on the screen should be updated."
284
165
# 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",
166
with_standout = { u"normal": u"standout",
167
u"bold": u"bold-standout",
169
u"underline-blink-standout",
170
u"bold-underline-blink":
171
u"bold-underline-blink-standout",
293
174
# Rebuild focus and non-focus widgets using current properties
295
# Base part of a client. Name!
297
% {"name": self.properties["Name"]})
298
if not self.properties["Enabled"]:
300
elif self.properties["ApprovalPending"]:
301
timeout = datetime.timedelta(milliseconds
304
last_approval_request = isoformat_to_datetime(
305
self.properties["LastApprovalRequest"])
306
if last_approval_request is not None:
307
timer = timeout - (datetime.datetime.utcnow()
308
- last_approval_request)
310
timer = datetime.timedelta()
311
if self.properties["ApprovedByDefault"]:
312
message = "Approval in %s. (d)eny?"
314
message = "Denial in %s. (a)pprove?"
315
message = message % unicode(timer).rsplit(".", 1)[0]
316
elif self.last_checker_failed:
317
timeout = datetime.timedelta(milliseconds
320
last_ok = isoformat_to_datetime(
321
max((self.properties["LastCheckedOK"]
322
or self.properties["Created"]),
323
self.properties["LastEnabled"]))
324
timer = timeout - (datetime.datetime.utcnow() - last_ok)
325
message = ('A checker has failed! Time until client'
327
% unicode(timer).rsplit(".", 1)[0])
330
self._text = "%s%s" % (base, message)
175
self._text = (u'%(name)s: %(enabled)s'
176
% { u"name": self.properties[u"name"],
179
if self.properties[u"enabled"]
332
181
if not urwid.supports_unicode():
333
182
self._text = self._text.encode("ascii", "replace")
334
textlist = [("normal", self._text)]
183
textlist = [(u"normal", self._text)]
335
184
self._text_widget.set_text(textlist)
336
185
self._focus_text_widget.set_text([(with_standout[text[0]],
345
194
if self.update_hook is not None:
346
195
self.update_hook()
348
def update_timer(self):
351
return True # Keep calling this
353
def delete(self, *args, **kwargs):
354
if self._update_timer_callback_tag is not None:
355
gobject.source_remove(self._update_timer_callback_tag)
356
self._update_timer_callback_tag = None
357
for match in self.match_objects:
359
self.match_objects = ()
360
198
if self.delete_hook is not None:
361
199
self.delete_hook(self)
362
return super(MandosClientWidget, self).delete(*args, **kwargs)
364
def render(self, maxcolrow, focus=False):
201
def render(self, (maxcol,), focus=False):
365
202
"""Render differently if we have focus.
366
203
This overrides the method from urwid.FlowWidget"""
367
return self.current_widget(focus).render(maxcolrow,
204
return self.current_widget(focus).render((maxcol,),
370
def keypress(self, maxcolrow, key):
207
def keypress(self, (maxcol,), key):
372
209
This overrides the method from urwid.FlowWidget"""
374
self.proxy.Enable(dbus_interface = client_interface,
377
self.proxy.Disable(dbus_interface = client_interface,
380
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
381
dbus_interface = client_interface,
384
self.proxy.Approve(dbus.Boolean(False, variant_level=1),
385
dbus_interface = client_interface,
387
elif key == "R" or key == "_" or key == "ctrl k":
210
if key == u"e" or key == u"+":
212
elif key == u"d" or key == u"-":
214
elif key == u"r" or key == u"_" or key == u"ctrl k":
388
215
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,
218
self.proxy.StartChecker()
220
self.proxy.StopChecker()
222
self.proxy.CheckedOK()
401
# elif key == "p" or key == "=":
224
# elif key == u"p" or key == "=":
402
225
# self.proxy.pause()
403
# elif key == "u" or key == ":":
226
# elif key == u"u" or key == ":":
404
227
# self.proxy.unpause()
228
# elif key == u"RET":
441
264
self.screen = urwid.curses_display.Screen()
443
266
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",
268
u"default", u"default", None),
270
u"default", u"default", u"bold"),
272
u"default", u"default", u"underline"),
274
u"default", u"default", u"standout"),
275
(u"bold-underline-blink",
276
u"default", u"default", (u"bold", u"underline")),
278
u"default", u"default", (u"bold", u"standout")),
279
(u"underline-blink-standout",
280
u"default", u"default", (u"underline", u"standout")),
281
(u"bold-underline-blink-standout",
282
u"default", u"default", (u"bold", u"underline",
463
286
if urwid.supports_unicode():
464
self.divider = "─" # \u2500
465
#self.divider = "━" # \u2501
287
self.divider = u"─" # \u2500
288
#self.divider = u"━" # \u2501
467
#self.divider = "-" # \u002d
468
self.divider = "_" # \u005f
290
#self.divider = u"-" # \u002d
291
self.divider = u"_" # \u005f
470
293
self.screen.start()
571
391
and len(self.log) > self.max_log_length):
572
392
del self.log[0:len(self.log)-self.max_log_length-1]
573
393
self.logbox.set_focus(len(self.logbox.body.contents),
394
coming_from=u"above")
577
397
def toggle_log_display(self):
578
398
"""Toggle visibility of the log buffer."""
579
399
self.log_visible = not self.log_visible
581
#self.log_message("Log visibility changed to: "
582
# + unicode(self.log_visible))
401
self.log_message(u"Log visibility changed to: "
402
+ unicode(self.log_visible))
584
404
def change_log_display(self):
585
405
"""Change type of log display.
586
406
Currently, this toggles wrapping of text lines."""
587
if self.log_wrap == "clip":
588
self.log_wrap = "any"
407
if self.log_wrap == u"clip":
408
self.log_wrap = u"any"
590
self.log_wrap = "clip"
410
self.log_wrap = u"clip"
591
411
for textwidget in self.log:
592
412
textwidget.set_wrap_mode(self.log_wrap)
593
#self.log_message("Wrap mode: " + self.log_wrap)
413
self.log_message(u"Wrap mode: " + self.log_wrap)
595
415
def find_and_remove_client(self, path, name):
596
"""Find a client by its object path and remove it.
416
"""Find an client from its object path and remove it.
598
418
This is connected to the ClientRemoved signal from the
599
419
Mandos server object."""
678
495
except KeyError: # :-)
681
if key == "q" or key == "Q":
498
if key == u"q" or key == u"Q":
684
elif key == "window resize":
501
elif key == u"window resize":
685
502
self.size = self.screen.get_cols_rows()
687
elif key == "\f": # Ctrl-L
504
elif key == u"\f": # Ctrl-L
689
elif key == "l" or key == "D":
506
elif key == u"l" or key == u"D":
690
507
self.toggle_log_display()
692
elif key == "w" or key == "i":
509
elif key == u"w" or key == u"i":
693
510
self.change_log_display()
695
elif key == "?" or key == "f1" or key == "esc":
512
elif key == u"?" or key == u"f1" or key == u"esc":
696
513
if not self.log_visible:
697
514
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",
516
self.log_message_raw((u"bold",
520
u"l: Log window toggle",
521
u"TAB: Switch window",
523
self.log_message_raw((u"bold",
529
u"s: Start new checker",
719
534
if self.topwidget.get_focus() is self.logbox:
720
535
self.topwidget.set_focus(0)
722
537
self.topwidget.set_focus(self.logbox)
724
#elif (key == "end" or key == "meta >" or key == "G"
539
#elif (key == u"end" or key == u"meta >" or key == u"G"
726
541
# pass # xxx end-of-buffer
727
#elif (key == "home" or key == "meta <" or key == "g"
542
#elif (key == u"home" or key == u"meta <" or key == u"g"
729
544
# pass # xxx beginning-of-buffer
730
#elif key == "ctrl e" or key == "$":
545
#elif key == u"ctrl e" or key == u"$":
731
546
# pass # xxx move-end-of-line
732
#elif key == "ctrl a" or key == "^":
547
#elif key == u"ctrl a" or key == u"^":
733
548
# pass # xxx move-beginning-of-line
734
#elif key == "ctrl b" or key == "meta (" or key == "h":
549
#elif key == u"ctrl b" or key == u"meta (" or key == u"h":
735
550
# pass # xxx left
736
#elif key == "ctrl f" or key == "meta )" or key == "l":
551
#elif key == u"ctrl f" or key == u"meta )" or key == u"l":
737
552
# pass # xxx right
739
554
# pass # scroll up log
741
556
# pass # scroll down log
742
557
elif self.topwidget.selectable():
743
558
self.topwidget.keypress(self.size, key)