2
2
# -*- mode: python; coding: utf-8 -*-
4
# Mandos Monitor - Control and monitor the Mandos server
6
# Copyright © 2009,2010 Teddy Hogeborn
7
# Copyright © 2009,2010 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
340
self.proxy.Enable(dbus_interface = client_interface)
363
342
self.proxy.Disable(dbus_interface = client_interface)
365
344
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
366
345
dbus_interface = client_interface)
368
347
self.proxy.Approve(dbus.Boolean(False, variant_level=1),
369
348
dbus_interface = client_interface)
370
elif key == "R" or key == "_" or key == "ctrl k":
349
elif key == u"r" or key == u"_" or key == u"ctrl k":
371
350
self.server_proxy_object.RemoveClient(self.proxy
374
353
self.proxy.StartChecker(dbus_interface = client_interface)
376
355
self.proxy.StopChecker(dbus_interface = client_interface)
378
357
self.proxy.CheckedOK(dbus_interface = client_interface)
380
# elif key == "p" or key == "=":
359
# elif key == u"p" or key == "=":
381
360
# self.proxy.pause()
382
# elif key == "u" or key == ":":
361
# elif key == u"u" or key == ":":
383
362
# self.proxy.unpause()
363
# elif key == u"RET":
403
382
"down" key presses, thus not allowing any containing widgets to
404
383
use them as an excuse to shift focus away from this widget.
406
def keypress(self, maxcolrow, key):
407
ret = super(ConstrainedListBox, self).keypress(maxcolrow, key)
408
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"):
420
400
self.screen = urwid.curses_display.Screen()
422
402
self.screen.register_palette((
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",
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",
442
422
if urwid.supports_unicode():
443
self.divider = "─" # \u2500
444
#self.divider = "━" # \u2501
423
self.divider = u"─" # \u2500
424
#self.divider = u"━" # \u2501
446
#self.divider = "-" # \u002d
447
self.divider = "_" # \u005f
426
#self.divider = u"-" # \u002d
427
self.divider = u"_" # \u005f
449
429
self.screen.start()
464
444
# This keeps track of whether self.uilist currently has
465
445
# self.logbox in it or not
466
446
self.log_visible = True
467
self.log_wrap = "any"
447
self.log_wrap = u"any"
470
self.log_message_raw(("bold",
471
"Mandos Monitor version " + version))
472
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",
475
455
self.busname = domain + '.Mandos'
476
456
self.main_loop = gobject.MainLoop()
477
457
self.bus = dbus.SystemBus()
478
458
mandos_dbus_objc = self.bus.get_object(
479
self.busname, "/", follow_name_owner_changes=True)
459
self.busname, u"/", follow_name_owner_changes=True)
480
460
self.mandos_serv = dbus.Interface(mandos_dbus_objc,
482
462
= server_interface)
487
467
mandos_clients = dbus.Dictionary()
489
469
(self.mandos_serv
490
.connect_to_signal("ClientRemoved",
470
.connect_to_signal(u"ClientRemoved",
491
471
self.find_and_remove_client,
492
472
dbus_interface=server_interface,
493
473
byte_arrays=True))
494
474
(self.mandos_serv
495
.connect_to_signal("ClientAdded",
475
.connect_to_signal(u"ClientAdded",
496
476
self.add_new_client,
497
477
dbus_interface=server_interface,
498
478
byte_arrays=True))
499
479
(self.mandos_serv
500
.connect_to_signal("ClientNotFound",
480
.connect_to_signal(u"ClientNotFound",
501
481
self.client_not_found,
502
482
dbus_interface=server_interface,
503
483
byte_arrays=True))
550
530
and len(self.log) > self.max_log_length):
551
531
del self.log[0:len(self.log)-self.max_log_length-1]
552
532
self.logbox.set_focus(len(self.logbox.body.contents),
533
coming_from=u"above")
556
536
def toggle_log_display(self):
557
537
"""Toggle visibility of the log buffer."""
558
538
self.log_visible = not self.log_visible
560
#self.log_message("Log visibility changed to: "
540
#self.log_message(u"Log visibility changed to: "
561
541
# + unicode(self.log_visible))
563
543
def change_log_display(self):
564
544
"""Change type of log display.
565
545
Currently, this toggles wrapping of text lines."""
566
if self.log_wrap == "clip":
567
self.log_wrap = "any"
546
if self.log_wrap == u"clip":
547
self.log_wrap = u"any"
569
self.log_wrap = "clip"
549
self.log_wrap = u"clip"
570
550
for textwidget in self.log:
571
551
textwidget.set_wrap_mode(self.log_wrap)
572
#self.log_message("Wrap mode: " + self.log_wrap)
552
#self.log_message(u"Wrap mode: " + self.log_wrap)
574
554
def find_and_remove_client(self, path, name):
575
555
"""Find an client from its object path and remove it.
640
620
def process_input(self, source, condition):
641
621
keys = self.screen.get_input()
642
translations = { "ctrl n": "down", # Emacs
643
"ctrl p": "up", # Emacs
644
"ctrl v": "page down", # Emacs
645
"meta v": "page up", # Emacs
646
" ": "page down", # less
647
"f": "page down", # less
648
"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
655
635
except KeyError: # :-)
658
if key == "q" or key == "Q":
638
if key == u"q" or key == u"Q":
661
elif key == "window resize":
641
elif key == u"window resize":
662
642
self.size = self.screen.get_cols_rows()
664
elif key == "\f": # Ctrl-L
644
elif key == u"\f": # Ctrl-L
666
elif key == "l" or key == "D":
646
elif key == u"l" or key == u"D":
667
647
self.toggle_log_display()
669
elif key == "w" or key == "i":
649
elif key == u"w" or key == u"i":
670
650
self.change_log_display()
672
elif key == "?" or key == "f1" or key == "esc":
652
elif key == u"?" or key == u"f1" or key == u"esc":
673
653
if not self.log_visible:
674
654
self.log_visible = True
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",
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",
696
676
if self.topwidget.get_focus() is self.logbox:
697
677
self.topwidget.set_focus(0)
699
679
self.topwidget.set_focus(self.logbox)
701
#elif (key == "end" or key == "meta >" or key == "G"
681
#elif (key == u"end" or key == u"meta >" or key == u"G"
703
683
# pass # xxx end-of-buffer
704
#elif (key == "home" or key == "meta <" or key == "g"
684
#elif (key == u"home" or key == u"meta <" or key == u"g"
706
686
# pass # xxx beginning-of-buffer
707
#elif key == "ctrl e" or key == "$":
687
#elif key == u"ctrl e" or key == u"$":
708
688
# pass # xxx move-end-of-line
709
#elif key == "ctrl a" or key == "^":
689
#elif key == u"ctrl a" or key == u"^":
710
690
# pass # xxx move-beginning-of-line
711
#elif key == "ctrl b" or key == "meta (" or key == "h":
691
#elif key == u"ctrl b" or key == u"meta (" or key == u"h":
712
692
# pass # xxx left
713
#elif key == "ctrl f" or key == "meta )" or key == "l":
693
#elif key == u"ctrl f" or key == u"meta )" or key == u"l":
714
694
# pass # xxx right
716
696
# pass # scroll up log
718
698
# pass # scroll down log
719
699
elif self.topwidget.selectable():
720
700
self.topwidget.keypress(self.size, key)