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@recompile.se>.
4
from __future__ import division, absolute_import, with_statement
25
from __future__ import (division, absolute_import, print_function,
102
130
self.logger = logger
104
132
self._update_timer_callback_tag = None
133
self._update_timer_callback_lock = 0
105
134
self.last_checker_failed = False
107
136
# The widget shown normally
108
self._text_widget = urwid.Text(u"")
137
self._text_widget = urwid.Text("")
109
138
# The widget shown when we have focus
110
self._focus_text_widget = urwid.Text(u"")
139
self._focus_text_widget = urwid.Text("")
111
140
super(MandosClientWidget, self).__init__(
112
141
update_hook=update_hook, delete_hook=delete_hook,
115
144
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",
136
146
last_checked_ok = isoformat_to_datetime(self.properties
138
148
if last_checked_ok is None:
139
149
self.last_checker_failed = True
142
152
- last_checked_ok)
143
153
> datetime.timedelta
145
self.properties["interval"]))
146
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
# Will update the shown timer value every second
147
204
self._update_timer_callback_tag = (gobject.timeout_add
149
206
self.update_timer))
207
elif old and self._update_timer_callback_lock == 0:
208
gobject.source_remove(self._update_timer_callback_tag)
209
self._update_timer_callback_tag = None
151
211
def checker_completed(self, exitstatus, condition, command):
152
212
if exitstatus == 0:
153
213
if self.last_checker_failed:
154
214
self.last_checker_failed = False
155
gobject.source_remove(self._update_timer_callback_tag)
156
self._update_timer_callback_tag = None
157
self.logger(u'Checker for client %s (command "%s")'
159
% (self.properties[u"name"], command))
215
self.using_timer(False)
216
#self.logger('Checker for client %s (command "%s")'
218
# % (self.properties["Name"], command))
163
222
if not self.last_checker_failed:
164
223
self.last_checker_failed = True
165
self._update_timer_callback_tag = (gobject.timeout_add
224
self.using_timer(True)
168
225
if os.WIFEXITED(condition):
169
self.logger(u'Checker for client %s (command "%s")'
170
u' failed with exit code %s'
171
% (self.properties[u"name"], command,
226
self.logger('Checker for client %s (command "%s")'
227
' failed with exit code %s'
228
% (self.properties["Name"], command,
172
229
os.WEXITSTATUS(condition)))
173
230
elif os.WIFSIGNALED(condition):
174
self.logger(u'Checker for client %s (command "%s")'
175
u' was killed by signal %s'
176
% (self.properties[u"name"], command,
231
self.logger('Checker for client %s (command "%s")'
232
' was killed by signal %s'
233
% (self.properties["Name"], command,
177
234
os.WTERMSIG(condition)))
178
235
elif os.WCOREDUMP(condition):
179
self.logger(u'Checker for client %s (command "%s")'
181
% (self.properties[u"name"], command))
236
self.logger('Checker for client %s (command "%s")'
238
% (self.properties["Name"], command))
183
self.logger(u'Checker for client %s completed mysteriously')
240
self.logger('Checker for client %s completed'
186
244
def checker_started(self, command):
187
#self.logger(u'Client %s started checker "%s"'
188
# % (self.properties[u"name"], unicode(command)))
245
#self.logger('Client %s started checker "%s"'
246
# % (self.properties["Name"], unicode(command)))
191
249
def got_secret(self):
192
250
self.last_checker_failed = False
193
self.logger(u'Client %s received its secret'
194
% self.properties[u"name"])
251
self.logger('Client %s received its secret'
252
% self.properties["Name"])
196
254
def need_approval(self, timeout, default):
198
message = u'Client %s needs approval within %s seconds'
256
message = 'Client %s needs approval within %s seconds'
200
message = u'Client %s will get its secret in %s seconds'
258
message = 'Client %s will get its secret in %s seconds'
201
259
self.logger(message
202
% (self.properties[u"name"], timeout/1000))
260
% (self.properties["Name"], timeout/1000))
261
self.using_timer(True)
204
263
def rejected(self, reason):
205
self.logger(u'Client %s was rejected; reason: %s'
206
% (self.properties[u"name"], reason))
264
self.logger('Client %s was rejected; reason: %s'
265
% (self.properties["Name"], reason))
208
267
def selectable(self):
209
268
"""Make this a "selectable" widget.
210
269
This overrides the method from urwid.FlowWidget."""
213
def rows(self, (maxcol,), focus=False):
272
def rows(self, maxcolrow, focus=False):
214
273
"""How many rows this widget will occupy might depend on
215
274
whether we have focus or not.
216
275
This overrides the method from urwid.FlowWidget"""
217
return self.current_widget(focus).rows((maxcol,), focus=focus)
276
return self.current_widget(focus).rows(maxcolrow, focus=focus)
219
278
def current_widget(self, focus=False):
220
279
if focus or self.opened:
224
283
def update(self):
225
284
"Called when what is visible on the screen should be updated."
226
285
# How to add standout mode to a style
227
with_standout = { u"normal": u"standout",
228
u"bold": u"bold-standout",
230
u"underline-blink-standout",
231
u"bold-underline-blink":
232
u"bold-underline-blink-standout",
286
with_standout = { "normal": "standout",
287
"bold": "bold-standout",
289
"underline-blink-standout",
290
"bold-underline-blink":
291
"bold-underline-blink-standout",
235
294
# Rebuild focus and non-focus widgets using current properties
237
296
# Base part of a client. Name!
238
base = (u'%(name)s: '
239
% {u"name": self.properties[u"name"]})
240
if not self.properties[u"enabled"]:
241
message = u"DISABLED"
242
elif self.properties[u"approved_pending"]:
243
if self.properties[u"approved_by_default"]:
244
message = u"Connection established to client. (d)eny?"
246
message = u"Seeks approval to send secret. (a)pprove?"
298
% {"name": self.properties["Name"]})
299
if not self.properties["Enabled"]:
301
elif self.properties["ApprovalPending"]:
302
timeout = datetime.timedelta(milliseconds
305
last_approval_request = isoformat_to_datetime(
306
self.properties["LastApprovalRequest"])
307
if last_approval_request is not None:
308
timer = timeout - (datetime.datetime.utcnow()
309
- last_approval_request)
311
timer = datetime.timedelta()
312
if self.properties["ApprovedByDefault"]:
313
message = "Approval in %s. (d)eny?"
315
message = "Denial in %s. (a)pprove?"
316
message = message % unicode(timer).rsplit(".", 1)[0]
247
317
elif self.last_checker_failed:
248
timeout = datetime.timedelta(milliseconds
249
= self.properties[u"timeout"])
250
last_ok = isoformat_to_datetime(
251
max((self.properties["last_checked_ok"]
252
or self.properties["created"]),
253
self.properties[u"last_enabled"]))
254
timer = timeout - (datetime.datetime.utcnow() - last_ok)
255
message = (u'A checker has failed! Time until client gets diabled: %s'
318
# When checker has failed, print a timer until client expires
319
expires = self.properties["Expires"]
321
timer = datetime.timedelta(0)
323
expires = datetime.datetime.strptime(expires,
324
'%Y-%m-%dT%H:%M:%S.%f')
325
timer = expires - datetime.datetime.utcnow()
326
message = ('A checker has failed! Time until client'
256
328
% unicode(timer).rsplit(".", 1)[0])
259
331
self._text = "%s%s" % (base, message)
261
333
if not urwid.supports_unicode():
262
334
self._text = self._text.encode("ascii", "replace")
263
textlist = [(u"normal", self._text)]
335
textlist = [("normal", self._text)]
264
336
self._text_widget.set_text(textlist)
265
337
self._focus_text_widget.set_text([(with_standout[text[0]],
275
347
self.update_hook()
277
349
def update_timer(self):
350
"""called by gobject. Will indefinitely loop until
351
gobject.source_remove() on tag is called"""
280
353
return True # Keep calling this
355
def delete(self, *args, **kwargs):
283
356
if self._update_timer_callback_tag is not None:
284
357
gobject.source_remove(self._update_timer_callback_tag)
285
358
self._update_timer_callback_tag = None
359
for match in self.match_objects:
361
self.match_objects = ()
286
362
if self.delete_hook is not None:
287
363
self.delete_hook(self)
364
return super(MandosClientWidget, self).delete(*args, **kwargs)
289
def render(self, (maxcol,), focus=False):
366
def render(self, maxcolrow, focus=False):
290
367
"""Render differently if we have focus.
291
368
This overrides the method from urwid.FlowWidget"""
292
return self.current_widget(focus).render((maxcol,),
369
return self.current_widget(focus).render(maxcolrow,
295
def keypress(self, (maxcol,), key):
372
def keypress(self, maxcolrow, key):
297
374
This overrides the method from urwid.FlowWidget"""
299
self.proxy.Enable(dbus_interface = client_interface)
301
self.proxy.Disable(dbus_interface = client_interface)
376
self.proxy.Enable(dbus_interface = client_interface,
379
self.proxy.Disable(dbus_interface = client_interface,
303
382
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
304
dbus_interface = client_interface)
383
dbus_interface = client_interface,
306
386
self.proxy.Approve(dbus.Boolean(False, variant_level=1),
307
dbus_interface = client_interface)
308
elif key == u"r" or key == u"_" or key == u"ctrl k":
387
dbus_interface = client_interface,
389
elif key == "R" or key == "_" or key == "ctrl k":
309
390
self.server_proxy_object.RemoveClient(self.proxy
312
self.proxy.StartChecker(dbus_interface = client_interface)
314
self.proxy.StopChecker(dbus_interface = client_interface)
316
self.proxy.CheckedOK(dbus_interface = client_interface)
394
self.proxy.StartChecker(dbus_interface = client_interface,
397
self.proxy.StopChecker(dbus_interface = client_interface,
400
self.proxy.CheckedOK(dbus_interface = client_interface,
318
# elif key == u"p" or key == "=":
403
# elif key == "p" or key == "=":
319
404
# self.proxy.pause()
320
# elif key == u"u" or key == ":":
405
# elif key == "u" or key == ":":
321
406
# self.proxy.unpause()
322
# elif key == u"RET":
325
# self.proxy.Approve(True)
327
# self.proxy.Approve(False)
362
443
self.screen = urwid.curses_display.Screen()
364
445
self.screen.register_palette((
366
u"default", u"default", None),
368
u"default", u"default", u"bold"),
370
u"default", u"default", u"underline"),
372
u"default", u"default", u"standout"),
373
(u"bold-underline-blink",
374
u"default", u"default", (u"bold", u"underline")),
376
u"default", u"default", (u"bold", u"standout")),
377
(u"underline-blink-standout",
378
u"default", u"default", (u"underline", u"standout")),
379
(u"bold-underline-blink-standout",
380
u"default", u"default", (u"bold", u"underline",
447
"default", "default", None),
449
"default", "default", "bold"),
451
"default", "default", "underline"),
453
"default", "default", "standout"),
454
("bold-underline-blink",
455
"default", "default", ("bold", "underline")),
457
"default", "default", ("bold", "standout")),
458
("underline-blink-standout",
459
"default", "default", ("underline", "standout")),
460
("bold-underline-blink-standout",
461
"default", "default", ("bold", "underline",
384
465
if urwid.supports_unicode():
385
self.divider = u"─" # \u2500
386
#self.divider = u"━" # \u2501
466
self.divider = "─" # \u2500
467
#self.divider = "━" # \u2501
388
#self.divider = u"-" # \u002d
389
self.divider = u"_" # \u005f
469
#self.divider = "-" # \u002d
470
self.divider = "_" # \u005f
391
472
self.screen.start()
406
487
# This keeps track of whether self.uilist currently has
407
488
# self.logbox in it or not
408
489
self.log_visible = True
409
self.log_wrap = u"any"
490
self.log_wrap = "any"
412
self.log_message_raw((u"bold",
413
u"Mandos Monitor version " + version))
414
self.log_message_raw((u"bold",
493
self.log_message_raw(("bold",
494
"Mandos Monitor version " + version))
495
self.log_message_raw(("bold",
417
498
self.busname = domain + '.Mandos'
418
499
self.main_loop = gobject.MainLoop()
419
500
self.bus = dbus.SystemBus()
420
501
mandos_dbus_objc = self.bus.get_object(
421
self.busname, u"/", follow_name_owner_changes=True)
502
self.busname, "/", follow_name_owner_changes=True)
422
503
self.mandos_serv = dbus.Interface(mandos_dbus_objc,
424
505
= server_interface)
489
573
and len(self.log) > self.max_log_length):
490
574
del self.log[0:len(self.log)-self.max_log_length-1]
491
575
self.logbox.set_focus(len(self.logbox.body.contents),
492
coming_from=u"above")
495
579
def toggle_log_display(self):
496
580
"""Toggle visibility of the log buffer."""
497
581
self.log_visible = not self.log_visible
499
self.log_message(u"Log visibility changed to: "
500
+ unicode(self.log_visible))
583
#self.log_message("Log visibility changed to: "
584
# + unicode(self.log_visible))
502
586
def change_log_display(self):
503
587
"""Change type of log display.
504
588
Currently, this toggles wrapping of text lines."""
505
if self.log_wrap == u"clip":
506
self.log_wrap = u"any"
589
if self.log_wrap == "clip":
590
self.log_wrap = "any"
508
self.log_wrap = u"clip"
592
self.log_wrap = "clip"
509
593
for textwidget in self.log:
510
594
textwidget.set_wrap_mode(self.log_wrap)
511
self.log_message(u"Wrap mode: " + self.log_wrap)
595
#self.log_message("Wrap mode: " + self.log_wrap)
513
597
def find_and_remove_client(self, path, name):
514
"""Find an client from its object path and remove it.
598
"""Find a client by its object path and remove it.
516
600
This is connected to the ClientRemoved signal from the
517
601
Mandos server object."""
594
680
except KeyError: # :-)
597
if key == u"q" or key == u"Q":
683
if key == "q" or key == "Q":
600
elif key == u"window resize":
686
elif key == "window resize":
601
687
self.size = self.screen.get_cols_rows()
603
elif key == u"\f": # Ctrl-L
689
elif key == "\f": # Ctrl-L
605
elif key == u"l" or key == u"D":
691
elif key == "l" or key == "D":
606
692
self.toggle_log_display()
608
elif key == u"w" or key == u"i":
694
elif key == "w" or key == "i":
609
695
self.change_log_display()
611
elif key == u"?" or key == u"f1" or key == u"esc":
697
elif key == "?" or key == "f1" or key == "esc":
612
698
if not self.log_visible:
613
699
self.log_visible = True
615
self.log_message_raw((u"bold",
619
u"l: Log window toggle",
620
u"TAB: Switch window",
622
self.log_message_raw((u"bold",
628
u"s: Start new checker",
701
self.log_message_raw(("bold",
705
"l: Log window toggle",
706
"TAB: Switch window",
708
self.log_message_raw(("bold",
714
"s: Start new checker",
635
721
if self.topwidget.get_focus() is self.logbox:
636
722
self.topwidget.set_focus(0)
638
724
self.topwidget.set_focus(self.logbox)
640
#elif (key == u"end" or key == u"meta >" or key == u"G"
726
#elif (key == "end" or key == "meta >" or key == "G"
642
728
# pass # xxx end-of-buffer
643
#elif (key == u"home" or key == u"meta <" or key == u"g"
729
#elif (key == "home" or key == "meta <" or key == "g"
645
731
# pass # xxx beginning-of-buffer
646
#elif key == u"ctrl e" or key == u"$":
732
#elif key == "ctrl e" or key == "$":
647
733
# pass # xxx move-end-of-line
648
#elif key == u"ctrl a" or key == u"^":
734
#elif key == "ctrl a" or key == "^":
649
735
# pass # xxx move-beginning-of-line
650
#elif key == u"ctrl b" or key == u"meta (" or key == u"h":
736
#elif key == "ctrl b" or key == "meta (" or key == "h":
651
737
# pass # xxx left
652
#elif key == u"ctrl f" or key == u"meta )" or key == u"l":
738
#elif key == "ctrl f" or key == "meta )" or key == "l":
653
739
# pass # xxx right
655
741
# pass # scroll up log
657
743
# pass # scroll down log
658
744
elif self.topwidget.selectable():
659
745
self.topwidget.keypress(self.size, key)