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
130
102
self.logger = logger
132
104
self._update_timer_callback_tag = None
133
self._update_timer_callback_lock = 0
134
105
self.last_checker_failed = False
136
107
# The widget shown normally
137
self._text_widget = urwid.Text("")
108
self._text_widget = urwid.Text(u"")
138
109
# The widget shown when we have focus
139
self._focus_text_widget = urwid.Text("")
110
self._focus_text_widget = urwid.Text(u"")
140
111
super(MandosClientWidget, self).__init__(
141
112
update_hook=update_hook, delete_hook=delete_hook,
144
115
self.opened = False
116
self.proxy.connect_to_signal(u"CheckerCompleted",
117
self.checker_completed,
120
self.proxy.connect_to_signal(u"CheckerStarted",
121
self.checker_started,
124
self.proxy.connect_to_signal(u"GotSecret",
128
self.proxy.connect_to_signal(u"NeedApproval",
132
self.proxy.connect_to_signal(u"Rejected",
146
136
last_checked_ok = isoformat_to_datetime(self.properties
148
138
if last_checked_ok is None:
149
139
self.last_checker_failed = True
153
143
> datetime.timedelta
158
147
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
148
self._update_timer_callback_tag = (gobject.timeout_add
205
150
self.update_timer))
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
210
152
def checker_completed(self, exitstatus, condition, command):
211
153
if exitstatus == 0:
212
154
if self.last_checker_failed:
213
155
self.last_checker_failed = False
214
self.using_timer(False)
215
#self.logger('Checker for client %s (command "%s")'
217
# % (self.properties["Name"], command))
156
gobject.source_remove(self._update_timer_callback_tag)
157
self._update_timer_callback_tag = None
158
self.logger(u'Checker for client %s (command "%s")'
160
% (self.properties[u"Name"], command))
221
164
if not self.last_checker_failed:
222
165
self.last_checker_failed = True
223
self.using_timer(True)
166
self._update_timer_callback_tag = (gobject.timeout_add
224
169
if os.WIFEXITED(condition):
225
self.logger('Checker for client %s (command "%s")'
226
' failed with exit code %s'
227
% (self.properties["Name"], command,
170
self.logger(u'Checker for client %s (command "%s")'
171
u' failed with exit code %s'
172
% (self.properties[u"Name"], command,
228
173
os.WEXITSTATUS(condition)))
229
174
elif os.WIFSIGNALED(condition):
230
self.logger('Checker for client %s (command "%s")'
231
' was killed by signal %s'
232
% (self.properties["Name"], command,
175
self.logger(u'Checker for client %s (command "%s")'
176
u' was killed by signal %s'
177
% (self.properties[u"Name"], command,
233
178
os.WTERMSIG(condition)))
234
179
elif os.WCOREDUMP(condition):
235
self.logger('Checker for client %s (command "%s")'
237
% (self.properties["Name"], command))
180
self.logger(u'Checker for client %s (command "%s")'
182
% (self.properties[u"Name"], command))
239
self.logger('Checker for client %s completed'
184
self.logger(u'Checker for client %s completed'
243
188
def checker_started(self, command):
244
#self.logger('Client %s started checker "%s"'
245
# % (self.properties["Name"], unicode(command)))
189
#self.logger(u'Client %s started checker "%s"'
190
# % (self.properties[u"Name"], unicode(command)))
248
193
def got_secret(self):
249
194
self.last_checker_failed = False
250
self.logger('Client %s received its secret'
251
% self.properties["Name"])
195
self.logger(u'Client %s received its secret'
196
% self.properties[u"Name"])
253
198
def need_approval(self, timeout, default):
255
message = 'Client %s needs approval within %s seconds'
200
message = u'Client %s needs approval within %s seconds'
257
message = 'Client %s will get its secret in %s seconds'
202
message = u'Client %s will get its secret in %s seconds'
258
203
self.logger(message
259
% (self.properties["Name"], timeout/1000))
260
self.using_timer(True)
204
% (self.properties[u"Name"], timeout/1000))
262
206
def rejected(self, reason):
263
self.logger('Client %s was rejected; reason: %s'
264
% (self.properties["Name"], reason))
207
self.logger(u'Client %s was rejected; reason: %s'
208
% (self.properties[u"Name"], reason))
266
210
def selectable(self):
267
211
"""Make this a "selectable" widget.
268
212
This overrides the method from urwid.FlowWidget."""
271
def rows(self, maxcolrow, focus=False):
215
def rows(self, (maxcol,), focus=False):
272
216
"""How many rows this widget will occupy might depend on
273
217
whether we have focus or not.
274
218
This overrides the method from urwid.FlowWidget"""
275
return self.current_widget(focus).rows(maxcolrow, focus=focus)
219
return self.current_widget(focus).rows((maxcol,), focus=focus)
277
221
def current_widget(self, focus=False):
278
222
if focus or self.opened:
282
226
def update(self):
283
227
"Called when what is visible on the screen should be updated."
284
228
# 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",
229
with_standout = { u"normal": u"standout",
230
u"bold": u"bold-standout",
232
u"underline-blink-standout",
233
u"bold-underline-blink":
234
u"bold-underline-blink-standout",
293
237
# Rebuild focus and non-focus widgets using current properties
295
239
# 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]
240
base = (u'%(name)s: '
241
% {u"name": self.properties[u"Name"]})
242
if not self.properties[u"Enabled"]:
243
message = u"DISABLED"
244
elif self.properties[u"ApprovalPending"]:
245
if self.properties[u"ApprovedByDefault"]:
246
message = u"Connection established to client. (d)eny?"
248
message = u"Seeks approval to send secret. (a)pprove?"
316
249
elif self.last_checker_failed:
317
250
timeout = datetime.timedelta(milliseconds
318
251
= self.properties
320
253
last_ok = isoformat_to_datetime(
321
max((self.properties["LastCheckedOK"]
322
or self.properties["Created"]),
323
self.properties["LastEnabled"]))
254
max((self.properties[u"LastCheckedOK"]
255
or self.properties[u"Created"]),
256
self.properties[u"LastEnabled"]))
324
257
timer = timeout - (datetime.datetime.utcnow() - last_ok)
325
message = ('A checker has failed! Time until client'
258
message = (u'A checker has failed! Time until client'
327
260
% unicode(timer).rsplit(".", 1)[0])
330
263
self._text = "%s%s" % (base, message)
332
265
if not urwid.supports_unicode():
333
266
self._text = self._text.encode("ascii", "replace")
334
textlist = [("normal", self._text)]
267
textlist = [(u"normal", self._text)]
335
268
self._text_widget.set_text(textlist)
336
269
self._focus_text_widget.set_text([(with_standout[text[0]],
351
284
return True # Keep calling this
353
def delete(self, *args, **kwargs):
354
287
if self._update_timer_callback_tag is not None:
355
288
gobject.source_remove(self._update_timer_callback_tag)
356
289
self._update_timer_callback_tag = None
357
for match in self.match_objects:
359
self.match_objects = ()
360
290
if self.delete_hook is not None:
361
291
self.delete_hook(self)
362
return super(MandosClientWidget, self).delete(*args, **kwargs)
364
def render(self, maxcolrow, focus=False):
293
def render(self, (maxcol,), focus=False):
365
294
"""Render differently if we have focus.
366
295
This overrides the method from urwid.FlowWidget"""
367
return self.current_widget(focus).render(maxcolrow,
296
return self.current_widget(focus).render((maxcol,),
370
def keypress(self, maxcolrow, key):
299
def keypress(self, (maxcol,), key):
372
301
This overrides the method from urwid.FlowWidget"""
374
self.proxy.Enable(dbus_interface = client_interface,
377
self.proxy.Disable(dbus_interface = client_interface,
303
self.proxy.Enable(dbus_interface = client_interface)
305
self.proxy.Disable(dbus_interface = client_interface)
380
307
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
381
dbus_interface = client_interface,
308
dbus_interface = client_interface)
384
310
self.proxy.Approve(dbus.Boolean(False, variant_level=1),
385
dbus_interface = client_interface,
387
elif key == "R" or key == "_" or key == "ctrl k":
311
dbus_interface = client_interface)
312
elif key == u"r" or key == u"_" or key == u"ctrl k":
388
313
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,
316
self.proxy.StartChecker(dbus_interface = client_interface)
318
self.proxy.StopChecker(dbus_interface = client_interface)
320
self.proxy.CheckedOK(dbus_interface = client_interface)
401
# elif key == "p" or key == "=":
322
# elif key == u"p" or key == "=":
402
323
# self.proxy.pause()
403
# elif key == "u" or key == ":":
324
# elif key == u"u" or key == ":":
404
325
# self.proxy.unpause()
326
# elif key == u"RET":
329
# self.proxy.Approve(True)
331
# self.proxy.Approve(False)
441
367
self.screen = urwid.curses_display.Screen()
443
369
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",
371
u"default", u"default", None),
373
u"default", u"default", u"bold"),
375
u"default", u"default", u"underline"),
377
u"default", u"default", u"standout"),
378
(u"bold-underline-blink",
379
u"default", u"default", (u"bold", u"underline")),
381
u"default", u"default", (u"bold", u"standout")),
382
(u"underline-blink-standout",
383
u"default", u"default", (u"underline", u"standout")),
384
(u"bold-underline-blink-standout",
385
u"default", u"default", (u"bold", u"underline",
463
389
if urwid.supports_unicode():
464
self.divider = "─" # \u2500
465
#self.divider = "━" # \u2501
390
self.divider = u"─" # \u2500
391
#self.divider = u"━" # \u2501
467
#self.divider = "-" # \u002d
468
self.divider = "_" # \u005f
393
#self.divider = u"-" # \u002d
394
self.divider = u"_" # \u005f
470
396
self.screen.start()
485
411
# This keeps track of whether self.uilist currently has
486
412
# self.logbox in it or not
487
413
self.log_visible = True
488
self.log_wrap = "any"
414
self.log_wrap = u"any"
491
self.log_message_raw(("bold",
492
"Mandos Monitor version " + version))
493
self.log_message_raw(("bold",
417
self.log_message_raw((u"bold",
418
u"Mandos Monitor version " + version))
419
self.log_message_raw((u"bold",
496
422
self.busname = domain + '.Mandos'
497
423
self.main_loop = gobject.MainLoop()
498
424
self.bus = dbus.SystemBus()
499
425
mandos_dbus_objc = self.bus.get_object(
500
self.busname, "/", follow_name_owner_changes=True)
426
self.busname, u"/", follow_name_owner_changes=True)
501
427
self.mandos_serv = dbus.Interface(mandos_dbus_objc,
503
429
= server_interface)
571
497
and len(self.log) > self.max_log_length):
572
498
del self.log[0:len(self.log)-self.max_log_length-1]
573
499
self.logbox.set_focus(len(self.logbox.body.contents),
500
coming_from=u"above")
577
503
def toggle_log_display(self):
578
504
"""Toggle visibility of the log buffer."""
579
505
self.log_visible = not self.log_visible
581
#self.log_message("Log visibility changed to: "
582
# + unicode(self.log_visible))
507
self.log_message(u"Log visibility changed to: "
508
+ unicode(self.log_visible))
584
510
def change_log_display(self):
585
511
"""Change type of log display.
586
512
Currently, this toggles wrapping of text lines."""
587
if self.log_wrap == "clip":
588
self.log_wrap = "any"
513
if self.log_wrap == u"clip":
514
self.log_wrap = u"any"
590
self.log_wrap = "clip"
516
self.log_wrap = u"clip"
591
517
for textwidget in self.log:
592
518
textwidget.set_wrap_mode(self.log_wrap)
593
#self.log_message("Wrap mode: " + self.log_wrap)
519
self.log_message(u"Wrap mode: " + self.log_wrap)
595
521
def find_and_remove_client(self, path, name):
596
"""Find a client by its object path and remove it.
522
"""Find an client from its object path and remove it.
598
524
This is connected to the ClientRemoved signal from the
599
525
Mandos server object."""
678
602
except KeyError: # :-)
681
if key == "q" or key == "Q":
605
if key == u"q" or key == u"Q":
684
elif key == "window resize":
608
elif key == u"window resize":
685
609
self.size = self.screen.get_cols_rows()
687
elif key == "\f": # Ctrl-L
611
elif key == u"\f": # Ctrl-L
689
elif key == "l" or key == "D":
613
elif key == u"l" or key == u"D":
690
614
self.toggle_log_display()
692
elif key == "w" or key == "i":
616
elif key == u"w" or key == u"i":
693
617
self.change_log_display()
695
elif key == "?" or key == "f1" or key == "esc":
619
elif key == u"?" or key == u"f1" or key == u"esc":
696
620
if not self.log_visible:
697
621
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",
623
self.log_message_raw((u"bold",
627
u"l: Log window toggle",
628
u"TAB: Switch window",
630
self.log_message_raw((u"bold",
636
u"s: Start new checker",
719
643
if self.topwidget.get_focus() is self.logbox:
720
644
self.topwidget.set_focus(0)
722
646
self.topwidget.set_focus(self.logbox)
724
#elif (key == "end" or key == "meta >" or key == "G"
648
#elif (key == u"end" or key == u"meta >" or key == u"G"
726
650
# pass # xxx end-of-buffer
727
#elif (key == "home" or key == "meta <" or key == "g"
651
#elif (key == u"home" or key == u"meta <" or key == u"g"
729
653
# pass # xxx beginning-of-buffer
730
#elif key == "ctrl e" or key == "$":
654
#elif key == u"ctrl e" or key == u"$":
731
655
# pass # xxx move-end-of-line
732
#elif key == "ctrl a" or key == "^":
656
#elif key == u"ctrl a" or key == u"^":
733
657
# pass # xxx move-beginning-of-line
734
#elif key == "ctrl b" or key == "meta (" or key == "h":
658
#elif key == u"ctrl b" or key == u"meta (" or key == u"h":
735
659
# pass # xxx left
736
#elif key == "ctrl f" or key == "meta )" or key == "l":
660
#elif key == u"ctrl f" or key == u"meta )" or key == u"l":
737
661
# pass # xxx right
739
663
# pass # scroll up log
741
665
# pass # scroll down log
742
666
elif self.topwidget.selectable():
743
667
self.topwidget.keypress(self.size, key)