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>.
4
from __future__ import division, absolute_import, with_statement
25
from __future__ import (division, absolute_import, print_function,
81
130
self.logger = logger
132
self._update_timer_callback_tag = None
133
self._update_timer_callback_lock = 0
134
self.last_checker_failed = False
83
136
# The widget shown normally
84
self._text_widget = urwid.Text(u"")
137
self._text_widget = urwid.Text("")
85
138
# The widget shown when we have focus
86
self._focus_text_widget = urwid.Text(u"")
139
self._focus_text_widget = urwid.Text("")
87
140
super(MandosClientWidget, self).__init__(
88
141
update_hook=update_hook, delete_hook=delete_hook,
91
144
self.opened = False
92
self.proxy.connect_to_signal(u"CheckerCompleted",
93
self.checker_completed,
96
self.proxy.connect_to_signal(u"CheckerStarted",
100
self.proxy.connect_to_signal(u"GotSecret",
104
self.proxy.connect_to_signal(u"Rejected",
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
109
210
def checker_completed(self, exitstatus, condition, command):
110
211
if exitstatus == 0:
111
self.logger(u'Checker for client %s (command "%s")'
113
% (self.properties[u"name"], command))
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))
221
if not self.last_checker_failed:
222
self.last_checker_failed = True
223
self.using_timer(True)
115
224
if os.WIFEXITED(condition):
116
self.logger(u'Checker for client %s (command "%s")'
117
u' failed with exit code %s'
118
% (self.properties[u"name"], command,
225
self.logger('Checker for client %s (command "%s")'
226
' failed with exit code %s'
227
% (self.properties["Name"], command,
119
228
os.WEXITSTATUS(condition)))
121
if os.WIFSIGNALED(condition):
122
self.logger(u'Checker for client %s (command "%s")'
123
u' was killed by signal %s'
124
% (self.properties[u"name"], command,
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,
125
233
os.WTERMSIG(condition)))
127
if os.WCOREDUMP(condition):
128
self.logger(u'Checker for client %s (command "%s")'
130
% (self.properties[u"name"], command))
131
self.logger(u'Checker for client %s completed mysteriously')
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'
133
243
def checker_started(self, command):
134
self.logger(u'Client %s started checker "%s"'
135
% (self.properties[u"name"], unicode(command)))
244
#self.logger('Client %s started checker "%s"'
245
# % (self.properties["Name"], unicode(command)))
137
248
def got_secret(self):
138
self.logger(u'Client %s received its secret'
139
% self.properties[u"name"])
142
self.logger(u'Client %s was rejected'
143
% self.properties[u"name"])
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))
145
266
def selectable(self):
146
267
"""Make this a "selectable" widget.
147
268
This overrides the method from urwid.FlowWidget."""
150
def rows(self, (maxcol,), focus=False):
271
def rows(self, maxcolrow, focus=False):
151
272
"""How many rows this widget will occupy might depend on
152
273
whether we have focus or not.
153
274
This overrides the method from urwid.FlowWidget"""
154
return self.current_widget(focus).rows((maxcol,), focus=focus)
275
return self.current_widget(focus).rows(maxcolrow, focus=focus)
156
277
def current_widget(self, focus=False):
157
278
if focus or self.opened:
161
282
def update(self):
162
283
"Called when what is visible on the screen should be updated."
163
284
# How to add standout mode to a style
164
with_standout = { u"normal": u"standout",
165
u"bold": u"bold-standout",
167
u"underline-blink-standout",
168
u"bold-underline-blink":
169
u"bold-underline-blink-standout",
285
with_standout = { "normal": "standout",
286
"bold": "bold-standout",
288
"underline-blink-standout",
289
"bold-underline-blink":
290
"bold-underline-blink-standout",
172
293
# Rebuild focus and non-focus widgets using current properties
173
self._text = (u'%(name)s: %(enabled)s'
174
% { u"name": self.properties[u"name"],
177
if self.properties[u"enabled"]
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)
179
332
if not urwid.supports_unicode():
180
333
self._text = self._text.encode("ascii", "replace")
181
textlist = [(u"normal", self._text)]
334
textlist = [("normal", self._text)]
182
335
self._text_widget.set_text(textlist)
183
336
self._focus_text_widget.set_text([(with_standout[text[0]],
192
345
if self.update_hook is not None:
193
346
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 = ()
196
360
if self.delete_hook is not None:
197
361
self.delete_hook(self)
362
return super(MandosClientWidget, self).delete(*args, **kwargs)
199
def render(self, (maxcol,), focus=False):
364
def render(self, maxcolrow, focus=False):
200
365
"""Render differently if we have focus.
201
366
This overrides the method from urwid.FlowWidget"""
202
return self.current_widget(focus).render((maxcol,),
367
return self.current_widget(focus).render(maxcolrow,
205
def keypress(self, (maxcol,), key):
370
def keypress(self, maxcolrow, key):
207
372
This overrides the method from urwid.FlowWidget"""
208
if key == u"e" or key == u"+":
210
elif key == u"d" or key == u"-":
212
elif key == u"r" or key == u"_" or key == u"ctrl k":
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":
213
388
self.server_proxy_object.RemoveClient(self.proxy
216
self.proxy.StartChecker()
218
self.proxy.StopChecker()
220
self.proxy.CheckedOK()
392
self.proxy.StartChecker(dbus_interface = client_interface,
395
self.proxy.StopChecker(dbus_interface = client_interface,
398
self.proxy.CheckedOK(dbus_interface = client_interface,
222
# elif key == u"p" or key == "=":
401
# elif key == "p" or key == "=":
223
402
# self.proxy.pause()
224
# elif key == u"u" or key == ":":
403
# elif key == "u" or key == ":":
225
404
# self.proxy.unpause()
226
# elif key == u"RET":
262
441
self.screen = urwid.curses_display.Screen()
264
443
self.screen.register_palette((
266
u"default", u"default", None),
268
u"default", u"default", u"bold"),
270
u"default", u"default", u"underline"),
272
u"default", u"default", u"standout"),
273
(u"bold-underline-blink",
274
u"default", u"default", (u"bold", u"underline")),
276
u"default", u"default", (u"bold", u"standout")),
277
(u"underline-blink-standout",
278
u"default", u"default", (u"underline", u"standout")),
279
(u"bold-underline-blink-standout",
280
u"default", u"default", (u"bold", u"underline",
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",
284
463
if urwid.supports_unicode():
285
self.divider = u"─" # \u2500
286
#self.divider = u"━" # \u2501
464
self.divider = "─" # \u2500
465
#self.divider = "━" # \u2501
288
#self.divider = u"-" # \u002d
289
self.divider = u"_" # \u005f
467
#self.divider = "-" # \u002d
468
self.divider = "_" # \u005f
291
470
self.screen.start()
389
571
and len(self.log) > self.max_log_length):
390
572
del self.log[0:len(self.log)-self.max_log_length-1]
391
573
self.logbox.set_focus(len(self.logbox.body.contents),
392
coming_from=u"above")
395
577
def toggle_log_display(self):
396
578
"""Toggle visibility of the log buffer."""
397
579
self.log_visible = not self.log_visible
399
self.log_message(u"Log visibility changed to: "
400
+ unicode(self.log_visible))
581
#self.log_message("Log visibility changed to: "
582
# + unicode(self.log_visible))
402
584
def change_log_display(self):
403
585
"""Change type of log display.
404
586
Currently, this toggles wrapping of text lines."""
405
if self.log_wrap == u"clip":
406
self.log_wrap = u"any"
587
if self.log_wrap == "clip":
588
self.log_wrap = "any"
408
self.log_wrap = u"clip"
590
self.log_wrap = "clip"
409
591
for textwidget in self.log:
410
592
textwidget.set_wrap_mode(self.log_wrap)
411
self.log_message(u"Wrap mode: " + self.log_wrap)
593
#self.log_message("Wrap mode: " + self.log_wrap)
413
595
def find_and_remove_client(self, path, name):
414
"""Find an client from its object path and remove it.
596
"""Find a client by its object path and remove it.
416
598
This is connected to the ClientRemoved signal from the
417
599
Mandos server object."""
494
678
except KeyError: # :-)
497
if key == u"q" or key == u"Q":
681
if key == "q" or key == "Q":
500
elif key == u"window resize":
684
elif key == "window resize":
501
685
self.size = self.screen.get_cols_rows()
503
elif key == u"\f": # Ctrl-L
687
elif key == "\f": # Ctrl-L
505
elif key == u"l" or key == u"D":
689
elif key == "l" or key == "D":
506
690
self.toggle_log_display()
508
elif key == u"w" or key == u"i":
692
elif key == "w" or key == "i":
509
693
self.change_log_display()
511
elif key == u"?" or key == u"f1" or key == u"esc":
695
elif key == "?" or key == "f1" or key == "esc":
512
696
if not self.log_visible:
513
697
self.log_visible = True
515
self.log_message_raw((u"bold",
519
u"l: Log window toggle",
520
u"TAB: Switch window",
522
self.log_message_raw((u"bold",
528
u"s: Start new checker",
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",
533
719
if self.topwidget.get_focus() is self.logbox:
534
720
self.topwidget.set_focus(0)
536
722
self.topwidget.set_focus(self.logbox)
538
#elif (key == u"end" or key == u"meta >" or key == u"G"
724
#elif (key == "end" or key == "meta >" or key == "G"
540
726
# pass # xxx end-of-buffer
541
#elif (key == u"home" or key == u"meta <" or key == u"g"
727
#elif (key == "home" or key == "meta <" or key == "g"
543
729
# pass # xxx beginning-of-buffer
544
#elif key == u"ctrl e" or key == u"$":
730
#elif key == "ctrl e" or key == "$":
545
731
# pass # xxx move-end-of-line
546
#elif key == u"ctrl a" or key == u"^":
732
#elif key == "ctrl a" or key == "^":
547
733
# pass # xxx move-beginning-of-line
548
#elif key == u"ctrl b" or key == u"meta (" or key == u"h":
734
#elif key == "ctrl b" or key == "meta (" or key == "h":
549
735
# pass # xxx left
550
#elif key == u"ctrl f" or key == u"meta )" or key == u"l":
736
#elif key == "ctrl f" or key == "meta )" or key == "l":
551
737
# pass # xxx right
553
739
# pass # scroll up log
555
741
# pass # scroll down log
556
742
elif self.topwidget.selectable():
557
743
self.topwidget.keypress(self.size, key)