130
102
self.logger = logger
132
104
self._update_timer_callback_tag = None
133
self._update_timer_callback_lock = 0
105
self.last_checker_failed = False
135
107
# The widget shown normally
136
self._text_widget = urwid.Text("")
108
self._text_widget = urwid.Text(u"")
137
109
# The widget shown when we have focus
138
self._focus_text_widget = urwid.Text("")
110
self._focus_text_widget = urwid.Text(u"")
139
111
super(MandosClientWidget, self).__init__(
140
112
update_hook=update_hook, delete_hook=delete_hook,
143
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",
145
136
last_checked_ok = isoformat_to_datetime(self.properties
148
if self.properties ["LastCheckerStatus"] != 0:
149
self.using_timer(True)
151
if self.need_approval:
152
self.using_timer(True)
154
self.match_objects = (
155
self.proxy.connect_to_signal("CheckerCompleted",
156
self.checker_completed,
159
self.proxy.connect_to_signal("CheckerStarted",
160
self.checker_started,
163
self.proxy.connect_to_signal("GotSecret",
167
self.proxy.connect_to_signal("NeedApproval",
171
self.proxy.connect_to_signal("Rejected",
175
#self.logger('Created client {0}'
176
# .format(self.properties["Name"]))
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))
183
if property == "LastCheckerStatus":
184
using_timer(value != 0)
185
#self.logger('Checker for client {0} (command "{1}") was '
186
# ' successful'.format(self.properties["Name"],
189
def using_timer(self, flag):
190
"""Call this method with True or False when timer should be
191
activated or deactivated.
193
old = self._update_timer_callback_lock
195
self._update_timer_callback_lock += 1
138
if last_checked_ok is None:
139
self.last_checker_failed = True
197
self._update_timer_callback_lock -= 1
198
if old == 0 and self._update_timer_callback_lock:
199
# Will update the shown timer value every second
141
self.last_checker_failed = ((datetime.datetime.utcnow()
147
if self.last_checker_failed:
200
148
self._update_timer_callback_tag = (gobject.timeout_add
202
150
self.update_timer))
203
elif old and self._update_timer_callback_lock == 0:
204
gobject.source_remove(self._update_timer_callback_tag)
205
self._update_timer_callback_tag = None
207
152
def checker_completed(self, exitstatus, condition, command):
208
153
if exitstatus == 0:
154
if self.last_checker_failed:
155
self.last_checker_failed = False
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))
164
if not self.last_checker_failed:
165
self.last_checker_failed = True
166
self._update_timer_callback_tag = (gobject.timeout_add
212
169
if os.WIFEXITED(condition):
213
self.logger('Checker for client {0} (command "{1}")'
214
' failed with exit code {2}'
215
.format(self.properties["Name"], command,
216
os.WEXITSTATUS(condition)))
170
self.logger(u'Checker for client %s (command "%s")'
171
u' failed with exit code %s'
172
% (self.properties[u"Name"], command,
173
os.WEXITSTATUS(condition)))
217
174
elif os.WIFSIGNALED(condition):
218
self.logger('Checker for client {0} (command "{1}") was'
219
' killed by signal {2}'
220
.format(self.properties["Name"], command,
221
os.WTERMSIG(condition)))
175
self.logger(u'Checker for client %s (command "%s")'
176
u' was killed by signal %s'
177
% (self.properties[u"Name"], command,
178
os.WTERMSIG(condition)))
222
179
elif os.WCOREDUMP(condition):
223
self.logger('Checker for client {0} (command "{1}")'
225
.format(self.properties["Name"], command))
180
self.logger(u'Checker for client %s (command "%s")'
182
% (self.properties[u"Name"], command))
227
self.logger('Checker for client {0} completed'
229
.format(self.properties["Name"]))
184
self.logger(u'Checker for client %s completed'
232
188
def checker_started(self, command):
233
"""Server signals that a checker started. This could be useful
234
to log in the future. """
235
#self.logger('Client {0} started checker "{1}"'
236
# .format(self.properties["Name"],
189
#self.logger(u'Client %s started checker "%s"'
190
# % (self.properties[u"Name"], unicode(command)))
240
193
def got_secret(self):
241
self.logger('Client {0} received its secret'
242
.format(self.properties["Name"]))
194
self.last_checker_failed = False
195
self.logger(u'Client %s received its secret'
196
% self.properties[u"Name"])
244
198
def need_approval(self, timeout, default):
246
message = 'Client {0} needs approval within {1} seconds'
200
message = u'Client %s needs approval within %s seconds'
248
message = 'Client {0} will get its secret in {1} seconds'
249
self.logger(message.format(self.properties["Name"],
251
self.using_timer(True)
202
message = u'Client %s will get its secret in %s seconds'
204
% (self.properties[u"Name"], timeout/1000))
253
206
def rejected(self, reason):
254
self.logger('Client {0} was rejected; reason: {1}'
255
.format(self.properties["Name"], reason))
207
self.logger(u'Client %s was rejected; reason: %s'
208
% (self.properties[u"Name"], reason))
257
210
def selectable(self):
258
211
"""Make this a "selectable" widget.
259
212
This overrides the method from urwid.FlowWidget."""
262
def rows(self, maxcolrow, focus=False):
215
def rows(self, (maxcol,), focus=False):
263
216
"""How many rows this widget will occupy might depend on
264
217
whether we have focus or not.
265
218
This overrides the method from urwid.FlowWidget"""
266
return self.current_widget(focus).rows(maxcolrow, focus=focus)
219
return self.current_widget(focus).rows((maxcol,), focus=focus)
268
221
def current_widget(self, focus=False):
269
222
if focus or self.opened:
273
226
def update(self):
274
227
"Called when what is visible on the screen should be updated."
275
228
# 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",
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",
284
237
# Rebuild focus and non-focus widgets using current properties
286
239
# Base part of a client. Name!
287
base = '{name}: '.format(name=self.properties["Name"])
288
if not self.properties["Enabled"]:
290
elif self.properties["ApprovalPending"]:
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?"
249
elif self.last_checker_failed:
291
250
timeout = datetime.timedelta(milliseconds
292
251
= self.properties
294
last_approval_request = isoformat_to_datetime(
295
self.properties["LastApprovalRequest"])
296
if last_approval_request is not None:
297
timer = timeout - (datetime.datetime.utcnow()
298
- last_approval_request)
300
timer = datetime.timedelta()
301
if self.properties["ApprovedByDefault"]:
302
message = "Approval in {0}. (d)eny?"
304
message = "Denial in {0}. (a)pprove?"
305
message = message.format(unicode(timer).rsplit(".", 1)[0])
306
elif self.properties["LastCheckerStatus"] != 0:
307
# When checker has failed, print a timer until client expires
308
expires = self.properties["Expires"]
310
timer = datetime.timedelta(0)
312
expires = datetime.datetime.strptime(expires,
313
'%Y-%m-%dT%H:%M:%S.%f')
314
timer = expires - datetime.datetime.utcnow()
315
message = ('A checker has failed! Time until client'
316
' gets disabled: {0}'
317
.format(unicode(timer).rsplit(".", 1)[0]))
253
last_ok = isoformat_to_datetime(
254
max((self.properties[u"LastCheckedOK"]
255
or self.properties[u"Created"]),
256
self.properties[u"LastEnabled"]))
257
timer = timeout - (datetime.datetime.utcnow() - last_ok)
258
message = (u'A checker has failed! Time until client'
260
% unicode(timer).rsplit(".", 1)[0])
320
self._text = "{0}{1}".format(base, message)
263
self._text = "%s%s" % (base, message)
322
265
if not urwid.supports_unicode():
323
266
self._text = self._text.encode("ascii", "replace")
324
textlist = [("normal", self._text)]
267
textlist = [(u"normal", self._text)]
325
268
self._text_widget.set_text(textlist)
326
269
self._focus_text_widget.set_text([(with_standout[text[0]],
336
279
self.update_hook()
338
281
def update_timer(self):
339
"""called by gobject. Will indefinitely loop until
340
gobject.source_remove() on tag is called"""
342
284
return True # Keep calling this
344
def delete(self, *args, **kwargs):
345
287
if self._update_timer_callback_tag is not None:
346
288
gobject.source_remove(self._update_timer_callback_tag)
347
289
self._update_timer_callback_tag = None
348
for match in self.match_objects:
350
self.match_objects = ()
351
290
if self.delete_hook is not None:
352
291
self.delete_hook(self)
353
return super(MandosClientWidget, self).delete(*args, **kwargs)
355
def render(self, maxcolrow, focus=False):
293
def render(self, (maxcol,), focus=False):
356
294
"""Render differently if we have focus.
357
295
This overrides the method from urwid.FlowWidget"""
358
return self.current_widget(focus).render(maxcolrow,
296
return self.current_widget(focus).render((maxcol,),
361
def keypress(self, maxcolrow, key):
299
def keypress(self, (maxcol,), key):
363
301
This overrides the method from urwid.FlowWidget"""
365
self.proxy.Enable(dbus_interface = client_interface,
368
self.proxy.Disable(dbus_interface = client_interface,
303
self.proxy.Enable(dbus_interface = client_interface)
305
self.proxy.Disable(dbus_interface = client_interface)
371
307
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
372
dbus_interface = client_interface,
308
dbus_interface = client_interface)
375
310
self.proxy.Approve(dbus.Boolean(False, variant_level=1),
376
dbus_interface = client_interface,
378
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":
379
313
self.server_proxy_object.RemoveClient(self.proxy
383
self.proxy.StartChecker(dbus_interface = client_interface,
386
self.proxy.StopChecker(dbus_interface = client_interface,
389
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)
392
# elif key == "p" or key == "=":
322
# elif key == u"p" or key == "=":
393
323
# self.proxy.pause()
394
# elif key == "u" or key == ":":
324
# elif key == u"u" or key == ":":
395
325
# self.proxy.unpause()
326
# elif key == u"RET":
329
# self.proxy.Approve(True)
331
# self.proxy.Approve(False)
432
367
self.screen = urwid.curses_display.Screen()
434
369
self.screen.register_palette((
436
"default", "default", None),
438
"default", "default", "bold"),
440
"default", "default", "underline"),
442
"default", "default", "standout"),
443
("bold-underline-blink",
444
"default", "default", ("bold", "underline")),
446
"default", "default", ("bold", "standout")),
447
("underline-blink-standout",
448
"default", "default", ("underline", "standout")),
449
("bold-underline-blink-standout",
450
"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",
454
389
if urwid.supports_unicode():
455
self.divider = "─" # \u2500
456
#self.divider = "━" # \u2501
390
self.divider = u"─" # \u2500
391
#self.divider = u"━" # \u2501
458
#self.divider = "-" # \u002d
459
self.divider = "_" # \u005f
393
#self.divider = u"-" # \u002d
394
self.divider = u"_" # \u005f
461
396
self.screen.start()
476
411
# This keeps track of whether self.uilist currently has
477
412
# self.logbox in it or not
478
413
self.log_visible = True
479
self.log_wrap = "any"
414
self.log_wrap = u"any"
482
self.log_message_raw(("bold",
483
"Mandos Monitor version " + version))
484
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",
487
422
self.busname = domain + '.Mandos'
488
423
self.main_loop = gobject.MainLoop()
424
self.bus = dbus.SystemBus()
425
mandos_dbus_objc = self.bus.get_object(
426
self.busname, u"/", follow_name_owner_changes=True)
427
self.mandos_serv = dbus.Interface(mandos_dbus_objc,
431
mandos_clients = (self.mandos_serv
432
.GetAllClientsWithProperties())
433
except dbus.exceptions.DBusException:
434
mandos_clients = dbus.Dictionary()
437
.connect_to_signal(u"ClientRemoved",
438
self.find_and_remove_client,
439
dbus_interface=server_interface,
442
.connect_to_signal(u"ClientAdded",
444
dbus_interface=server_interface,
447
.connect_to_signal(u"ClientNotFound",
448
self.client_not_found,
449
dbus_interface=server_interface,
451
for path, client in mandos_clients.iteritems():
452
client_proxy_object = self.bus.get_object(self.busname,
454
self.add_client(MandosClientWidget(server_proxy_object
457
=client_proxy_object,
490
467
def client_not_found(self, fingerprint, address):
491
self.log_message("Client with address {0} and fingerprint"
492
" {1} could not be found"
493
.format(address, fingerprint))
468
self.log_message((u"Client with address %s and fingerprint %s"
469
u" could not be found" % (address,
495
472
def rebuild(self):
496
473
"""This rebuilds the User Interface.
519
497
and len(self.log) > self.max_log_length):
520
498
del self.log[0:len(self.log)-self.max_log_length-1]
521
499
self.logbox.set_focus(len(self.logbox.body.contents),
500
coming_from=u"above")
525
503
def toggle_log_display(self):
526
504
"""Toggle visibility of the log buffer."""
527
505
self.log_visible = not self.log_visible
529
#self.log_message("Log visibility changed to: "
530
# + unicode(self.log_visible))
507
self.log_message(u"Log visibility changed to: "
508
+ unicode(self.log_visible))
532
510
def change_log_display(self):
533
511
"""Change type of log display.
534
512
Currently, this toggles wrapping of text lines."""
535
if self.log_wrap == "clip":
536
self.log_wrap = "any"
513
if self.log_wrap == u"clip":
514
self.log_wrap = u"any"
538
self.log_wrap = "clip"
516
self.log_wrap = u"clip"
539
517
for textwidget in self.log:
540
518
textwidget.set_wrap_mode(self.log_wrap)
541
#self.log_message("Wrap mode: " + self.log_wrap)
519
self.log_message(u"Wrap mode: " + self.log_wrap)
543
521
def find_and_remove_client(self, path, name):
544
"""Find a client by its object path and remove it.
522
"""Find an client from its object path and remove it.
546
524
This is connected to the ClientRemoved signal from the
547
525
Mandos server object."""
597
573
"""Start the main loop and exit when it's done."""
598
self.bus = dbus.SystemBus()
599
mandos_dbus_objc = self.bus.get_object(
600
self.busname, "/", follow_name_owner_changes=True)
601
self.mandos_serv = dbus.Interface(mandos_dbus_objc,
605
mandos_clients = (self.mandos_serv
606
.GetAllClientsWithProperties())
607
except dbus.exceptions.DBusException:
608
mandos_clients = dbus.Dictionary()
611
.connect_to_signal("ClientRemoved",
612
self.find_and_remove_client,
613
dbus_interface=server_interface,
616
.connect_to_signal("ClientAdded",
618
dbus_interface=server_interface,
621
.connect_to_signal("ClientNotFound",
622
self.client_not_found,
623
dbus_interface=server_interface,
625
for path, client in mandos_clients.iteritems():
626
client_proxy_object = self.bus.get_object(self.busname,
628
self.add_client(MandosClientWidget(server_proxy_object
631
=client_proxy_object,
642
575
self._input_callback_tag = (gobject.io_add_watch
643
576
(sys.stdin.fileno(),
669
602
except KeyError: # :-)
672
if key == "q" or key == "Q":
605
if key == u"q" or key == u"Q":
675
elif key == "window resize":
608
elif key == u"window resize":
676
609
self.size = self.screen.get_cols_rows()
678
elif key == "\f": # Ctrl-L
611
elif key == u"\f": # Ctrl-L
680
elif key == "l" or key == "D":
613
elif key == u"l" or key == u"D":
681
614
self.toggle_log_display()
683
elif key == "w" or key == "i":
616
elif key == u"w" or key == u"i":
684
617
self.change_log_display()
686
elif key == "?" or key == "f1" or key == "esc":
619
elif key == u"?" or key == u"f1" or key == u"esc":
687
620
if not self.log_visible:
688
621
self.log_visible = True
690
self.log_message_raw(("bold",
694
"l: Log window toggle",
695
"TAB: Switch window",
697
self.log_message_raw(("bold",
703
"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",
710
643
if self.topwidget.get_focus() is self.logbox:
711
644
self.topwidget.set_focus(0)
713
646
self.topwidget.set_focus(self.logbox)
715
#elif (key == "end" or key == "meta >" or key == "G"
648
#elif (key == u"end" or key == u"meta >" or key == u"G"
717
650
# pass # xxx end-of-buffer
718
#elif (key == "home" or key == "meta <" or key == "g"
651
#elif (key == u"home" or key == u"meta <" or key == u"g"
720
653
# pass # xxx beginning-of-buffer
721
#elif key == "ctrl e" or key == "$":
654
#elif key == u"ctrl e" or key == u"$":
722
655
# pass # xxx move-end-of-line
723
#elif key == "ctrl a" or key == "^":
656
#elif key == u"ctrl a" or key == u"^":
724
657
# pass # xxx move-beginning-of-line
725
#elif key == "ctrl b" or key == "meta (" or key == "h":
658
#elif key == u"ctrl b" or key == u"meta (" or key == u"h":
726
659
# pass # xxx left
727
#elif key == "ctrl f" or key == "meta )" or key == "l":
660
#elif key == u"ctrl f" or key == u"meta )" or key == u"l":
728
661
# pass # xxx right
730
663
# pass # scroll up log
732
665
# pass # scroll down log
733
666
elif self.topwidget.selectable():
734
667
self.topwidget.keypress(self.size, key)