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, unicode_literals
97
123
self.logger = logger
99
125
self._update_timer_callback_tag = None
126
self._update_timer_callback_lock = 0
100
127
self.last_checker_failed = False
102
129
# The widget shown normally
103
self._text_widget = urwid.Text(u"")
130
self._text_widget = urwid.Text("")
104
131
# The widget shown when we have focus
105
self._focus_text_widget = urwid.Text(u"")
132
self._focus_text_widget = urwid.Text("")
106
133
super(MandosClientWidget, self).__init__(
107
134
update_hook=update_hook, delete_hook=delete_hook,
110
137
self.opened = False
111
self.proxy.connect_to_signal(u"CheckerCompleted",
112
self.checker_completed,
115
self.proxy.connect_to_signal(u"CheckerStarted",
116
self.checker_started,
119
self.proxy.connect_to_signal(u"GotSecret",
123
self.proxy.connect_to_signal(u"Rejected",
127
139
last_checked_ok = isoformat_to_datetime(self.properties
129
141
if last_checked_ok is None:
130
142
self.last_checker_failed = True
133
145
- last_checked_ok)
134
146
> datetime.timedelta
136
self.properties["interval"]))
137
151
if self.last_checker_failed:
152
self.using_timer(True)
154
if self.need_approval:
155
self.using_timer(True)
157
self.proxy.connect_to_signal("CheckerCompleted",
158
self.checker_completed,
161
self.proxy.connect_to_signal("CheckerStarted",
162
self.checker_started,
165
self.proxy.connect_to_signal("GotSecret",
169
self.proxy.connect_to_signal("NeedApproval",
173
self.proxy.connect_to_signal("Rejected",
178
def property_changed(self, property=None, value=None):
179
super(self, MandosClientWidget).property_changed(property,
181
if property == "ApprovalPending":
182
using_timer(bool(value))
184
def using_timer(self, flag):
185
"""Call this method with True or False when timer should be
186
activated or deactivated.
188
old = self._update_timer_callback_lock
190
self._update_timer_callback_lock += 1
192
self._update_timer_callback_lock -= 1
193
if old == 0 and self._update_timer_callback_lock:
138
194
self._update_timer_callback_tag = (gobject.timeout_add
140
196
self.update_timer))
197
elif old and self._update_timer_callback_lock == 0:
198
gobject.source_remove(self._update_timer_callback_tag)
199
self._update_timer_callback_tag = None
142
201
def checker_completed(self, exitstatus, condition, command):
143
202
if exitstatus == 0:
144
203
if self.last_checker_failed:
145
204
self.last_checker_failed = False
146
gobject.source_remove(self._update_timer_callback_tag)
147
self._update_timer_callback_tag = None
148
self.logger(u'Checker for client %s (command "%s")'
150
% (self.properties[u"name"], command))
205
self.using_timer(False)
206
#self.logger('Checker for client %s (command "%s")'
208
# % (self.properties["Name"], command))
154
212
if not self.last_checker_failed:
155
213
self.last_checker_failed = True
156
self._update_timer_callback_tag = (gobject.timeout_add
214
self.using_timer(True)
159
215
if os.WIFEXITED(condition):
160
self.logger(u'Checker for client %s (command "%s")'
161
u' failed with exit code %s'
162
% (self.properties[u"name"], command,
216
self.logger('Checker for client %s (command "%s")'
217
' failed with exit code %s'
218
% (self.properties["Name"], command,
163
219
os.WEXITSTATUS(condition)))
164
220
elif os.WIFSIGNALED(condition):
165
self.logger(u'Checker for client %s (command "%s")'
166
u' was killed by signal %s'
167
% (self.properties[u"name"], command,
221
self.logger('Checker for client %s (command "%s")'
222
' was killed by signal %s'
223
% (self.properties["Name"], command,
168
224
os.WTERMSIG(condition)))
169
225
elif os.WCOREDUMP(condition):
170
self.logger(u'Checker for client %s (command "%s")'
172
% (self.properties[u"name"], command))
226
self.logger('Checker for client %s (command "%s")'
228
% (self.properties["Name"], command))
174
self.logger(u'Checker for client %s completed mysteriously')
230
self.logger('Checker for client %s completed'
177
234
def checker_started(self, command):
178
self.logger(u'Client %s started checker "%s"'
179
% (self.properties[u"name"], unicode(command)))
235
#self.logger('Client %s started checker "%s"'
236
# % (self.properties["Name"], unicode(command)))
181
239
def got_secret(self):
182
self.logger(u'Client %s received its secret'
183
% self.properties[u"name"])
186
self.logger(u'Client %s was rejected'
187
% self.properties[u"name"])
240
self.last_checker_failed = False
241
self.logger('Client %s received its secret'
242
% self.properties["Name"])
244
def need_approval(self, timeout, default):
246
message = 'Client %s needs approval within %s seconds'
248
message = 'Client %s will get its secret in %s seconds'
250
% (self.properties["Name"], timeout/1000))
251
self.using_timer(True)
253
def rejected(self, reason):
254
self.logger('Client %s was rejected; reason: %s'
255
% (self.properties["Name"], reason))
189
257
def selectable(self):
190
258
"""Make this a "selectable" widget.
191
259
This overrides the method from urwid.FlowWidget."""
194
def rows(self, (maxcol,), focus=False):
262
def rows(self, maxcolrow, focus=False):
195
263
"""How many rows this widget will occupy might depend on
196
264
whether we have focus or not.
197
265
This overrides the method from urwid.FlowWidget"""
198
return self.current_widget(focus).rows((maxcol,), focus=focus)
266
return self.current_widget(focus).rows(maxcolrow, focus=focus)
200
268
def current_widget(self, focus=False):
201
269
if focus or self.opened:
205
273
def update(self):
206
274
"Called when what is visible on the screen should be updated."
207
275
# How to add standout mode to a style
208
with_standout = { u"normal": u"standout",
209
u"bold": u"bold-standout",
211
u"underline-blink-standout",
212
u"bold-underline-blink":
213
u"bold-underline-blink-standout",
276
with_standout = { "normal": "standout",
277
"bold": "bold-standout",
279
"underline-blink-standout",
280
"bold-underline-blink":
281
"bold-underline-blink-standout",
216
284
# Rebuild focus and non-focus widgets using current properties
217
self._text = (u'%(name)s: %(enabled)s%(timer)s'
218
% { u"name": self.properties[u"name"],
221
if self.properties[u"enabled"]
223
u"timer": (unicode(datetime.timedelta
229
- isoformat_to_datetime
230
(max((self.properties
235
self.properties[u"last_enabled"]))))
236
if (self.last_checker_failed
286
# Base part of a client. Name!
288
% {"name": self.properties["Name"]})
289
if not self.properties["Enabled"]:
291
elif self.properties["ApprovalPending"]:
292
timeout = datetime.timedelta(milliseconds
295
last_approval_request = isoformat_to_datetime(
296
self.properties["LastApprovalRequest"])
297
if last_approval_request is not None:
298
timer = timeout - (datetime.datetime.utcnow()
299
- last_approval_request)
301
timer = datetime.timedelta()
302
if self.properties["ApprovedByDefault"]:
303
message = "Approval in %s. (d)eny?"
305
message = "Denial in %s. (a)pprove?"
306
message = message % unicode(timer).rsplit(".", 1)[0]
307
elif self.last_checker_failed:
308
timeout = datetime.timedelta(milliseconds
311
last_ok = isoformat_to_datetime(
312
max((self.properties["LastCheckedOK"]
313
or self.properties["Created"]),
314
self.properties["LastEnabled"]))
315
timer = timeout - (datetime.datetime.utcnow() - last_ok)
316
message = ('A checker has failed! Time until client'
318
% unicode(timer).rsplit(".", 1)[0])
321
self._text = "%s%s" % (base, message)
240
323
if not urwid.supports_unicode():
241
324
self._text = self._text.encode("ascii", "replace")
242
textlist = [(u"normal", self._text)]
325
textlist = [("normal", self._text)]
243
326
self._text_widget.set_text(textlist)
244
327
self._focus_text_widget.set_text([(with_standout[text[0]],
265
348
if self.delete_hook is not None:
266
349
self.delete_hook(self)
268
def render(self, (maxcol,), focus=False):
351
def render(self, maxcolrow, focus=False):
269
352
"""Render differently if we have focus.
270
353
This overrides the method from urwid.FlowWidget"""
271
return self.current_widget(focus).render((maxcol,),
354
return self.current_widget(focus).render(maxcolrow,
274
def keypress(self, (maxcol,), key):
357
def keypress(self, maxcolrow, key):
276
359
This overrides the method from urwid.FlowWidget"""
277
if key == u"e" or key == u"+":
279
elif key == u"d" or key == u"-":
281
elif key == u"r" or key == u"_" or key == u"ctrl k":
361
self.proxy.Enable(dbus_interface = client_interface)
363
self.proxy.Disable(dbus_interface = client_interface)
365
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
366
dbus_interface = client_interface)
368
self.proxy.Approve(dbus.Boolean(False, variant_level=1),
369
dbus_interface = client_interface)
370
elif key == "R" or key == "_" or key == "ctrl k":
282
371
self.server_proxy_object.RemoveClient(self.proxy
285
self.proxy.StartChecker()
287
self.proxy.StopChecker()
289
self.proxy.CheckedOK()
374
self.proxy.StartChecker(dbus_interface = client_interface)
376
self.proxy.StopChecker(dbus_interface = client_interface)
378
self.proxy.CheckedOK(dbus_interface = client_interface)
291
# elif key == u"p" or key == "=":
380
# elif key == "p" or key == "=":
292
381
# self.proxy.pause()
293
# elif key == u"u" or key == ":":
382
# elif key == "u" or key == ":":
294
383
# self.proxy.unpause()
295
# elif key == u"RET":
331
420
self.screen = urwid.curses_display.Screen()
333
422
self.screen.register_palette((
335
u"default", u"default", None),
337
u"default", u"default", u"bold"),
339
u"default", u"default", u"underline"),
341
u"default", u"default", u"standout"),
342
(u"bold-underline-blink",
343
u"default", u"default", (u"bold", u"underline")),
345
u"default", u"default", (u"bold", u"standout")),
346
(u"underline-blink-standout",
347
u"default", u"default", (u"underline", u"standout")),
348
(u"bold-underline-blink-standout",
349
u"default", u"default", (u"bold", u"underline",
424
"default", "default", None),
426
"default", "default", "bold"),
428
"default", "default", "underline"),
430
"default", "default", "standout"),
431
("bold-underline-blink",
432
"default", "default", ("bold", "underline")),
434
"default", "default", ("bold", "standout")),
435
("underline-blink-standout",
436
"default", "default", ("underline", "standout")),
437
("bold-underline-blink-standout",
438
"default", "default", ("bold", "underline",
353
442
if urwid.supports_unicode():
354
self.divider = u"─" # \u2500
355
#self.divider = u"━" # \u2501
443
self.divider = "─" # \u2500
444
#self.divider = "━" # \u2501
357
#self.divider = u"-" # \u002d
358
self.divider = u"_" # \u005f
446
#self.divider = "-" # \u002d
447
self.divider = "_" # \u005f
360
449
self.screen.start()
375
464
# This keeps track of whether self.uilist currently has
376
465
# self.logbox in it or not
377
466
self.log_visible = True
378
self.log_wrap = u"any"
467
self.log_wrap = "any"
381
self.log_message_raw((u"bold",
382
u"Mandos Monitor version " + version))
383
self.log_message_raw((u"bold",
470
self.log_message_raw(("bold",
471
"Mandos Monitor version " + version))
472
self.log_message_raw(("bold",
386
475
self.busname = domain + '.Mandos'
387
476
self.main_loop = gobject.MainLoop()
388
477
self.bus = dbus.SystemBus()
389
478
mandos_dbus_objc = self.bus.get_object(
390
self.busname, u"/", follow_name_owner_changes=True)
479
self.busname, "/", follow_name_owner_changes=True)
391
480
self.mandos_serv = dbus.Interface(mandos_dbus_objc,
393
482
= server_interface)
458
550
and len(self.log) > self.max_log_length):
459
551
del self.log[0:len(self.log)-self.max_log_length-1]
460
552
self.logbox.set_focus(len(self.logbox.body.contents),
461
coming_from=u"above")
464
556
def toggle_log_display(self):
465
557
"""Toggle visibility of the log buffer."""
466
558
self.log_visible = not self.log_visible
468
self.log_message(u"Log visibility changed to: "
469
+ unicode(self.log_visible))
560
#self.log_message("Log visibility changed to: "
561
# + unicode(self.log_visible))
471
563
def change_log_display(self):
472
564
"""Change type of log display.
473
565
Currently, this toggles wrapping of text lines."""
474
if self.log_wrap == u"clip":
475
self.log_wrap = u"any"
566
if self.log_wrap == "clip":
567
self.log_wrap = "any"
477
self.log_wrap = u"clip"
569
self.log_wrap = "clip"
478
570
for textwidget in self.log:
479
571
textwidget.set_wrap_mode(self.log_wrap)
480
self.log_message(u"Wrap mode: " + self.log_wrap)
572
#self.log_message("Wrap mode: " + self.log_wrap)
482
574
def find_and_remove_client(self, path, name):
483
575
"""Find an client from its object path and remove it.
563
655
except KeyError: # :-)
566
if key == u"q" or key == u"Q":
658
if key == "q" or key == "Q":
569
elif key == u"window resize":
661
elif key == "window resize":
570
662
self.size = self.screen.get_cols_rows()
572
elif key == u"\f": # Ctrl-L
664
elif key == "\f": # Ctrl-L
574
elif key == u"l" or key == u"D":
666
elif key == "l" or key == "D":
575
667
self.toggle_log_display()
577
elif key == u"w" or key == u"i":
669
elif key == "w" or key == "i":
578
670
self.change_log_display()
580
elif key == u"?" or key == u"f1" or key == u"esc":
672
elif key == "?" or key == "f1" or key == "esc":
581
673
if not self.log_visible:
582
674
self.log_visible = True
584
self.log_message_raw((u"bold",
588
u"l: Log window toggle",
589
u"TAB: Switch window",
591
self.log_message_raw((u"bold",
597
u"s: Start new checker",
676
self.log_message_raw(("bold",
680
"l: Log window toggle",
681
"TAB: Switch window",
683
self.log_message_raw(("bold",
689
"s: Start new checker",
602
696
if self.topwidget.get_focus() is self.logbox:
603
697
self.topwidget.set_focus(0)
605
699
self.topwidget.set_focus(self.logbox)
607
#elif (key == u"end" or key == u"meta >" or key == u"G"
701
#elif (key == "end" or key == "meta >" or key == "G"
609
703
# pass # xxx end-of-buffer
610
#elif (key == u"home" or key == u"meta <" or key == u"g"
704
#elif (key == "home" or key == "meta <" or key == "g"
612
706
# pass # xxx beginning-of-buffer
613
#elif key == u"ctrl e" or key == u"$":
707
#elif key == "ctrl e" or key == "$":
614
708
# pass # xxx move-end-of-line
615
#elif key == u"ctrl a" or key == u"^":
709
#elif key == "ctrl a" or key == "^":
616
710
# pass # xxx move-beginning-of-line
617
#elif key == u"ctrl b" or key == u"meta (" or key == u"h":
711
#elif key == "ctrl b" or key == "meta (" or key == "h":
618
712
# pass # xxx left
619
#elif key == u"ctrl f" or key == u"meta )" or key == u"l":
713
#elif key == "ctrl f" or key == "meta )" or key == "l":
620
714
# pass # xxx right
622
716
# pass # scroll up log
624
718
# pass # scroll down log
625
719
elif self.topwidget.selectable():
626
720
self.topwidget.keypress(self.size, key)