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,
364
self.proxy.Disable(dbus_interface = client_interface,
367
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
368
dbus_interface = client_interface,
371
self.proxy.Approve(dbus.Boolean(False, variant_level=1),
372
dbus_interface = client_interface,
374
elif key == "R" or key == "_" or key == "ctrl k":
282
375
self.server_proxy_object.RemoveClient(self.proxy
285
self.proxy.StartChecker()
287
self.proxy.StopChecker()
289
self.proxy.CheckedOK()
379
self.proxy.StartChecker(dbus_interface = client_interface,
382
self.proxy.StopChecker(dbus_interface = client_interface,
385
self.proxy.CheckedOK(dbus_interface = client_interface,
291
# elif key == u"p" or key == "=":
388
# elif key == "p" or key == "=":
292
389
# self.proxy.pause()
293
# elif key == u"u" or key == ":":
390
# elif key == "u" or key == ":":
294
391
# self.proxy.unpause()
295
# elif key == u"RET":
331
428
self.screen = urwid.curses_display.Screen()
333
430
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",
432
"default", "default", None),
434
"default", "default", "bold"),
436
"default", "default", "underline"),
438
"default", "default", "standout"),
439
("bold-underline-blink",
440
"default", "default", ("bold", "underline")),
442
"default", "default", ("bold", "standout")),
443
("underline-blink-standout",
444
"default", "default", ("underline", "standout")),
445
("bold-underline-blink-standout",
446
"default", "default", ("bold", "underline",
353
450
if urwid.supports_unicode():
354
self.divider = u"─" # \u2500
355
#self.divider = u"━" # \u2501
451
self.divider = "─" # \u2500
452
#self.divider = "━" # \u2501
357
#self.divider = u"-" # \u002d
358
self.divider = u"_" # \u005f
454
#self.divider = "-" # \u002d
455
self.divider = "_" # \u005f
360
457
self.screen.start()
375
472
# This keeps track of whether self.uilist currently has
376
473
# self.logbox in it or not
377
474
self.log_visible = True
378
self.log_wrap = u"any"
475
self.log_wrap = "any"
381
self.log_message_raw((u"bold",
382
u"Mandos Monitor version " + version))
383
self.log_message_raw((u"bold",
478
self.log_message_raw(("bold",
479
"Mandos Monitor version " + version))
480
self.log_message_raw(("bold",
386
483
self.busname = domain + '.Mandos'
387
484
self.main_loop = gobject.MainLoop()
388
485
self.bus = dbus.SystemBus()
389
486
mandos_dbus_objc = self.bus.get_object(
390
self.busname, u"/", follow_name_owner_changes=True)
487
self.busname, "/", follow_name_owner_changes=True)
391
488
self.mandos_serv = dbus.Interface(mandos_dbus_objc,
393
490
= server_interface)
458
558
and len(self.log) > self.max_log_length):
459
559
del self.log[0:len(self.log)-self.max_log_length-1]
460
560
self.logbox.set_focus(len(self.logbox.body.contents),
461
coming_from=u"above")
464
564
def toggle_log_display(self):
465
565
"""Toggle visibility of the log buffer."""
466
566
self.log_visible = not self.log_visible
468
self.log_message(u"Log visibility changed to: "
469
+ unicode(self.log_visible))
568
#self.log_message("Log visibility changed to: "
569
# + unicode(self.log_visible))
471
571
def change_log_display(self):
472
572
"""Change type of log display.
473
573
Currently, this toggles wrapping of text lines."""
474
if self.log_wrap == u"clip":
475
self.log_wrap = u"any"
574
if self.log_wrap == "clip":
575
self.log_wrap = "any"
477
self.log_wrap = u"clip"
577
self.log_wrap = "clip"
478
578
for textwidget in self.log:
479
579
textwidget.set_wrap_mode(self.log_wrap)
480
self.log_message(u"Wrap mode: " + self.log_wrap)
580
#self.log_message("Wrap mode: " + self.log_wrap)
482
582
def find_and_remove_client(self, path, name):
483
583
"""Find an client from its object path and remove it.
563
663
except KeyError: # :-)
566
if key == u"q" or key == u"Q":
666
if key == "q" or key == "Q":
569
elif key == u"window resize":
669
elif key == "window resize":
570
670
self.size = self.screen.get_cols_rows()
572
elif key == u"\f": # Ctrl-L
672
elif key == "\f": # Ctrl-L
574
elif key == u"l" or key == u"D":
674
elif key == "l" or key == "D":
575
675
self.toggle_log_display()
577
elif key == u"w" or key == u"i":
677
elif key == "w" or key == "i":
578
678
self.change_log_display()
580
elif key == u"?" or key == u"f1" or key == u"esc":
680
elif key == "?" or key == "f1" or key == "esc":
581
681
if not self.log_visible:
582
682
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",
684
self.log_message_raw(("bold",
688
"l: Log window toggle",
689
"TAB: Switch window",
691
self.log_message_raw(("bold",
697
"s: Start new checker",
602
704
if self.topwidget.get_focus() is self.logbox:
603
705
self.topwidget.set_focus(0)
605
707
self.topwidget.set_focus(self.logbox)
607
#elif (key == u"end" or key == u"meta >" or key == u"G"
709
#elif (key == "end" or key == "meta >" or key == "G"
609
711
# pass # xxx end-of-buffer
610
#elif (key == u"home" or key == u"meta <" or key == u"g"
712
#elif (key == "home" or key == "meta <" or key == "g"
612
714
# pass # xxx beginning-of-buffer
613
#elif key == u"ctrl e" or key == u"$":
715
#elif key == "ctrl e" or key == "$":
614
716
# pass # xxx move-end-of-line
615
#elif key == u"ctrl a" or key == u"^":
717
#elif key == "ctrl a" or key == "^":
616
718
# pass # xxx move-beginning-of-line
617
#elif key == u"ctrl b" or key == u"meta (" or key == u"h":
719
#elif key == "ctrl b" or key == "meta (" or key == "h":
618
720
# pass # xxx left
619
#elif key == u"ctrl f" or key == u"meta )" or key == u"l":
721
#elif key == "ctrl f" or key == "meta )" or key == "l":
620
722
# pass # xxx right
622
724
# pass # scroll up log
624
726
# pass # scroll down log
625
727
elif self.topwidget.selectable():
626
728
self.topwidget.keypress(self.size, key)