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, unicode_literals
4
from __future__ import division, absolute_import, with_statement
65
44
"Parse an ISO 8601 date string to a datetime.datetime()"
68
d, t = iso.split("T", 1)
69
year, month, day = d.split("-", 2)
70
hour, minute, second = t.split(":", 2)
47
d, t = iso.split(u"T", 1)
48
year, month, day = d.split(u"-", 2)
49
hour, minute, second = t.split(u":", 2)
71
50
second, fraction = divmod(float(second), 1)
72
51
return datetime.datetime(int(year),
127
106
self.last_checker_failed = False
129
108
# The widget shown normally
130
self._text_widget = urwid.Text("")
109
self._text_widget = urwid.Text(u"")
131
110
# The widget shown when we have focus
132
self._focus_text_widget = urwid.Text("")
111
self._focus_text_widget = urwid.Text(u"")
133
112
super(MandosClientWidget, self).__init__(
134
113
update_hook=update_hook, delete_hook=delete_hook,
154
133
if self.need_approval:
155
134
self.using_timer(True)
157
self.proxy.connect_to_signal("CheckerCompleted",
136
self.proxy.connect_to_signal(u"CheckerCompleted",
158
137
self.checker_completed,
159
138
client_interface,
160
139
byte_arrays=True)
161
self.proxy.connect_to_signal("CheckerStarted",
140
self.proxy.connect_to_signal(u"CheckerStarted",
162
141
self.checker_started,
163
142
client_interface,
164
143
byte_arrays=True)
165
self.proxy.connect_to_signal("GotSecret",
144
self.proxy.connect_to_signal(u"GotSecret",
167
146
client_interface,
168
147
byte_arrays=True)
169
self.proxy.connect_to_signal("NeedApproval",
148
self.proxy.connect_to_signal(u"NeedApproval",
170
149
self.need_approval,
171
150
client_interface,
172
151
byte_arrays=True)
173
self.proxy.connect_to_signal("Rejected",
152
self.proxy.connect_to_signal(u"Rejected",
175
154
client_interface,
176
155
byte_arrays=True)
213
192
self.last_checker_failed = True
214
193
self.using_timer(True)
215
194
if os.WIFEXITED(condition):
216
self.logger('Checker for client %s (command "%s")'
217
' failed with exit code %s'
218
% (self.properties["Name"], command,
195
self.logger(u'Checker for client %s (command "%s")'
196
u' failed with exit code %s'
197
% (self.properties[u"Name"], command,
219
198
os.WEXITSTATUS(condition)))
220
199
elif os.WIFSIGNALED(condition):
221
self.logger('Checker for client %s (command "%s")'
222
' was killed by signal %s'
223
% (self.properties["Name"], command,
200
self.logger(u'Checker for client %s (command "%s")'
201
u' was killed by signal %s'
202
% (self.properties[u"Name"], command,
224
203
os.WTERMSIG(condition)))
225
204
elif os.WCOREDUMP(condition):
226
self.logger('Checker for client %s (command "%s")'
228
% (self.properties["Name"], command))
205
self.logger(u'Checker for client %s (command "%s")'
207
% (self.properties[u"Name"], command))
230
self.logger('Checker for client %s completed'
209
self.logger(u'Checker for client %s completed'
234
213
def checker_started(self, command):
235
#self.logger('Client %s started checker "%s"'
236
# % (self.properties["Name"], unicode(command)))
214
#self.logger(u'Client %s started checker "%s"'
215
# % (self.properties[u"Name"], unicode(command)))
239
218
def got_secret(self):
240
219
self.last_checker_failed = False
241
self.logger('Client %s received its secret'
242
% self.properties["Name"])
220
self.logger(u'Client %s received its secret'
221
% self.properties[u"Name"])
244
223
def need_approval(self, timeout, default):
246
message = 'Client %s needs approval within %s seconds'
225
message = u'Client %s needs approval within %s seconds'
248
message = 'Client %s will get its secret in %s seconds'
227
message = u'Client %s will get its secret in %s seconds'
249
228
self.logger(message
250
% (self.properties["Name"], timeout/1000))
229
% (self.properties[u"Name"], timeout/1000))
251
230
self.using_timer(True)
253
232
def rejected(self, reason):
254
self.logger('Client %s was rejected; reason: %s'
255
% (self.properties["Name"], reason))
233
self.logger(u'Client %s was rejected; reason: %s'
234
% (self.properties[u"Name"], reason))
257
236
def selectable(self):
258
237
"""Make this a "selectable" widget.
259
238
This overrides the method from urwid.FlowWidget."""
262
def rows(self, maxcolrow, focus=False):
241
def rows(self, (maxcol,), focus=False):
263
242
"""How many rows this widget will occupy might depend on
264
243
whether we have focus or not.
265
244
This overrides the method from urwid.FlowWidget"""
266
return self.current_widget(focus).rows(maxcolrow, focus=focus)
245
return self.current_widget(focus).rows((maxcol,), focus=focus)
268
247
def current_widget(self, focus=False):
269
248
if focus or self.opened:
273
252
def update(self):
274
253
"Called when what is visible on the screen should be updated."
275
254
# How to add standout mode to a style
276
with_standout = { "normal": "standout",
277
"bold": "bold-standout",
279
"underline-blink-standout",
280
"bold-underline-blink":
281
"bold-underline-blink-standout",
255
with_standout = { u"normal": u"standout",
256
u"bold": u"bold-standout",
258
u"underline-blink-standout",
259
u"bold-underline-blink":
260
u"bold-underline-blink-standout",
284
263
# Rebuild focus and non-focus widgets using current properties
286
265
# Base part of a client. Name!
288
% {"name": self.properties["Name"]})
289
if not self.properties["Enabled"]:
291
elif self.properties["ApprovalPending"]:
266
base = (u'%(name)s: '
267
% {u"name": self.properties[u"Name"]})
268
if not self.properties[u"Enabled"]:
269
message = u"DISABLED"
270
elif self.properties[u"ApprovalPending"]:
292
271
timeout = datetime.timedelta(milliseconds
293
272
= self.properties
295
274
last_approval_request = isoformat_to_datetime(
296
self.properties["LastApprovalRequest"])
275
self.properties[u"LastApprovalRequest"])
297
276
if last_approval_request is not None:
298
277
timer = timeout - (datetime.datetime.utcnow()
299
278
- last_approval_request)
301
280
timer = datetime.timedelta()
302
if self.properties["ApprovedByDefault"]:
303
message = "Approval in %s. (d)eny?"
281
if self.properties[u"ApprovedByDefault"]:
282
message = u"Approval in %s. (d)eny?"
305
message = "Denial in %s. (a)pprove?"
284
message = u"Denial in %s. (a)pprove?"
306
285
message = message % unicode(timer).rsplit(".", 1)[0]
307
286
elif self.last_checker_failed:
308
287
timeout = datetime.timedelta(milliseconds
309
288
= self.properties
311
290
last_ok = isoformat_to_datetime(
312
max((self.properties["LastCheckedOK"]
313
or self.properties["Created"]),
314
self.properties["LastEnabled"]))
291
max((self.properties[u"LastCheckedOK"]
292
or self.properties[u"Created"]),
293
self.properties[u"LastEnabled"]))
315
294
timer = timeout - (datetime.datetime.utcnow() - last_ok)
316
message = ('A checker has failed! Time until client'
295
message = (u'A checker has failed! Time until client'
296
u' gets disabled: %s'
318
297
% unicode(timer).rsplit(".", 1)[0])
321
300
self._text = "%s%s" % (base, message)
323
302
if not urwid.supports_unicode():
324
303
self._text = self._text.encode("ascii", "replace")
325
textlist = [("normal", self._text)]
304
textlist = [(u"normal", self._text)]
326
305
self._text_widget.set_text(textlist)
327
306
self._focus_text_widget.set_text([(with_standout[text[0]],
348
327
if self.delete_hook is not None:
349
328
self.delete_hook(self)
351
def render(self, maxcolrow, focus=False):
330
def render(self, (maxcol,), focus=False):
352
331
"""Render differently if we have focus.
353
332
This overrides the method from urwid.FlowWidget"""
354
return self.current_widget(focus).render(maxcolrow,
333
return self.current_widget(focus).render((maxcol,),
357
def keypress(self, maxcolrow, key):
336
def keypress(self, (maxcol,), key):
359
338
This overrides the method from urwid.FlowWidget"""
361
self.proxy.Enable(dbus_interface = client_interface,
364
self.proxy.Disable(dbus_interface = client_interface,
340
self.proxy.Enable(dbus_interface = client_interface)
342
self.proxy.Disable(dbus_interface = client_interface)
367
344
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
368
dbus_interface = client_interface,
345
dbus_interface = client_interface)
371
347
self.proxy.Approve(dbus.Boolean(False, variant_level=1),
372
dbus_interface = client_interface,
374
elif key == "R" or key == "_" or key == "ctrl k":
348
dbus_interface = client_interface)
349
elif key == u"r" or key == u"_" or key == u"ctrl k":
375
350
self.server_proxy_object.RemoveClient(self.proxy
379
self.proxy.StartChecker(dbus_interface = client_interface,
382
self.proxy.StopChecker(dbus_interface = client_interface,
385
self.proxy.CheckedOK(dbus_interface = client_interface,
353
self.proxy.StartChecker(dbus_interface = client_interface)
355
self.proxy.StopChecker(dbus_interface = client_interface)
357
self.proxy.CheckedOK(dbus_interface = client_interface)
388
# elif key == "p" or key == "=":
359
# elif key == u"p" or key == "=":
389
360
# self.proxy.pause()
390
# elif key == "u" or key == ":":
361
# elif key == u"u" or key == ":":
391
362
# self.proxy.unpause()
363
# elif key == u"RET":
411
382
"down" key presses, thus not allowing any containing widgets to
412
383
use them as an excuse to shift focus away from this widget.
414
def keypress(self, maxcolrow, key):
415
ret = super(ConstrainedListBox, self).keypress(maxcolrow, key)
416
if ret in ("up", "down"):
385
def keypress(self, (maxcol, maxrow), key):
386
ret = super(ConstrainedListBox, self).keypress((maxcol,
388
if ret in (u"up", u"down"):
428
400
self.screen = urwid.curses_display.Screen()
430
402
self.screen.register_palette((
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",
404
u"default", u"default", None),
406
u"default", u"default", u"bold"),
408
u"default", u"default", u"underline"),
410
u"default", u"default", u"standout"),
411
(u"bold-underline-blink",
412
u"default", u"default", (u"bold", u"underline")),
414
u"default", u"default", (u"bold", u"standout")),
415
(u"underline-blink-standout",
416
u"default", u"default", (u"underline", u"standout")),
417
(u"bold-underline-blink-standout",
418
u"default", u"default", (u"bold", u"underline",
450
422
if urwid.supports_unicode():
451
self.divider = "─" # \u2500
452
#self.divider = "━" # \u2501
423
self.divider = u"─" # \u2500
424
#self.divider = u"━" # \u2501
454
#self.divider = "-" # \u002d
455
self.divider = "_" # \u005f
426
#self.divider = u"-" # \u002d
427
self.divider = u"_" # \u005f
457
429
self.screen.start()
472
444
# This keeps track of whether self.uilist currently has
473
445
# self.logbox in it or not
474
446
self.log_visible = True
475
self.log_wrap = "any"
447
self.log_wrap = u"any"
478
self.log_message_raw(("bold",
479
"Mandos Monitor version " + version))
480
self.log_message_raw(("bold",
450
self.log_message_raw((u"bold",
451
u"Mandos Monitor version " + version))
452
self.log_message_raw((u"bold",
483
455
self.busname = domain + '.Mandos'
484
456
self.main_loop = gobject.MainLoop()
485
457
self.bus = dbus.SystemBus()
486
458
mandos_dbus_objc = self.bus.get_object(
487
self.busname, "/", follow_name_owner_changes=True)
459
self.busname, u"/", follow_name_owner_changes=True)
488
460
self.mandos_serv = dbus.Interface(mandos_dbus_objc,
490
462
= server_interface)
495
467
mandos_clients = dbus.Dictionary()
497
469
(self.mandos_serv
498
.connect_to_signal("ClientRemoved",
470
.connect_to_signal(u"ClientRemoved",
499
471
self.find_and_remove_client,
500
472
dbus_interface=server_interface,
501
473
byte_arrays=True))
502
474
(self.mandos_serv
503
.connect_to_signal("ClientAdded",
475
.connect_to_signal(u"ClientAdded",
504
476
self.add_new_client,
505
477
dbus_interface=server_interface,
506
478
byte_arrays=True))
507
479
(self.mandos_serv
508
.connect_to_signal("ClientNotFound",
480
.connect_to_signal(u"ClientNotFound",
509
481
self.client_not_found,
510
482
dbus_interface=server_interface,
511
483
byte_arrays=True))
558
530
and len(self.log) > self.max_log_length):
559
531
del self.log[0:len(self.log)-self.max_log_length-1]
560
532
self.logbox.set_focus(len(self.logbox.body.contents),
533
coming_from=u"above")
564
536
def toggle_log_display(self):
565
537
"""Toggle visibility of the log buffer."""
566
538
self.log_visible = not self.log_visible
568
#self.log_message("Log visibility changed to: "
540
#self.log_message(u"Log visibility changed to: "
569
541
# + unicode(self.log_visible))
571
543
def change_log_display(self):
572
544
"""Change type of log display.
573
545
Currently, this toggles wrapping of text lines."""
574
if self.log_wrap == "clip":
575
self.log_wrap = "any"
546
if self.log_wrap == u"clip":
547
self.log_wrap = u"any"
577
self.log_wrap = "clip"
549
self.log_wrap = u"clip"
578
550
for textwidget in self.log:
579
551
textwidget.set_wrap_mode(self.log_wrap)
580
#self.log_message("Wrap mode: " + self.log_wrap)
552
#self.log_message(u"Wrap mode: " + self.log_wrap)
582
554
def find_and_remove_client(self, path, name):
583
555
"""Find an client from its object path and remove it.
648
620
def process_input(self, source, condition):
649
621
keys = self.screen.get_input()
650
translations = { "ctrl n": "down", # Emacs
651
"ctrl p": "up", # Emacs
652
"ctrl v": "page down", # Emacs
653
"meta v": "page up", # Emacs
654
" ": "page down", # less
655
"f": "page down", # less
656
"b": "page up", # less
622
translations = { u"ctrl n": u"down", # Emacs
623
u"ctrl p": u"up", # Emacs
624
u"ctrl v": u"page down", # Emacs
625
u"meta v": u"page up", # Emacs
626
u" ": u"page down", # less
627
u"f": u"page down", # less
628
u"b": u"page up", # less
663
635
except KeyError: # :-)
666
if key == "q" or key == "Q":
638
if key == u"q" or key == u"Q":
669
elif key == "window resize":
641
elif key == u"window resize":
670
642
self.size = self.screen.get_cols_rows()
672
elif key == "\f": # Ctrl-L
644
elif key == u"\f": # Ctrl-L
674
elif key == "l" or key == "D":
646
elif key == u"l" or key == u"D":
675
647
self.toggle_log_display()
677
elif key == "w" or key == "i":
649
elif key == u"w" or key == u"i":
678
650
self.change_log_display()
680
elif key == "?" or key == "f1" or key == "esc":
652
elif key == u"?" or key == u"f1" or key == u"esc":
681
653
if not self.log_visible:
682
654
self.log_visible = True
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",
656
self.log_message_raw((u"bold",
660
u"l: Log window toggle",
661
u"TAB: Switch window",
663
self.log_message_raw((u"bold",
669
u"s: Start new checker",
704
676
if self.topwidget.get_focus() is self.logbox:
705
677
self.topwidget.set_focus(0)
707
679
self.topwidget.set_focus(self.logbox)
709
#elif (key == "end" or key == "meta >" or key == "G"
681
#elif (key == u"end" or key == u"meta >" or key == u"G"
711
683
# pass # xxx end-of-buffer
712
#elif (key == "home" or key == "meta <" or key == "g"
684
#elif (key == u"home" or key == u"meta <" or key == u"g"
714
686
# pass # xxx beginning-of-buffer
715
#elif key == "ctrl e" or key == "$":
687
#elif key == u"ctrl e" or key == u"$":
716
688
# pass # xxx move-end-of-line
717
#elif key == "ctrl a" or key == "^":
689
#elif key == u"ctrl a" or key == u"^":
718
690
# pass # xxx move-beginning-of-line
719
#elif key == "ctrl b" or key == "meta (" or key == "h":
691
#elif key == u"ctrl b" or key == u"meta (" or key == u"h":
720
692
# pass # xxx left
721
#elif key == "ctrl f" or key == "meta )" or key == "l":
693
#elif key == u"ctrl f" or key == u"meta )" or key == u"l":
722
694
# pass # xxx right
724
696
# pass # scroll up log
726
698
# pass # scroll down log
727
699
elif self.topwidget.selectable():
728
700
self.topwidget.keypress(self.size, key)