57
55
domain = 'se.recompile'
58
56
server_interface = domain + '.Mandos'
59
57
client_interface = domain + '.Mandos.Client'
63
dbus.OBJECT_MANAGER_IFACE
64
except AttributeError:
65
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
60
# Always run in monochrome mode
61
urwid.curses_display.curses.has_colors = lambda : False
63
# Urwid doesn't support blinking, but we want it. Since we have no
64
# use for underline on its own, we make underline also always blink.
65
urwid.curses_display.curses.A_UNDERLINE |= (
66
urwid.curses_display.curses.A_BLINK)
67
68
def isoformat_to_datetime(iso):
68
69
"Parse an ISO 8601 date string to a datetime.datetime()"
103
104
super(MandosClientPropertyCache, self).__init__(**kwargs)
105
def properties_changed(self, interface, properties, invalidated):
106
"""This is called whenever we get a PropertiesChanged signal
107
It updates the changed properties in the "properties" dict.
106
def _property_changed(self, property, value):
107
"""Helper which takes positional arguments"""
108
return self.property_changed(property=property, value=value)
110
def property_changed(self, property=None, value=None):
111
"""This is called whenever we get a PropertyChanged signal
112
It updates the changed property in the "properties" dict.
109
114
# Update properties dict with new value
110
if interface == client_interface:
111
self.properties.update(properties)
115
self.properties[property] = value
113
117
def delete(self):
114
118
self.property_changed_match.remove()
170
174
if flag and self._update_timer_callback_tag is None:
171
175
# Will update the shown timer value every second
172
self._update_timer_callback_tag = (GLib.timeout_add
176
self._update_timer_callback_tag = (gobject.timeout_add
174
178
self.update_timer))
175
179
elif not (flag or self._update_timer_callback_tag is None):
176
GLib.source_remove(self._update_timer_callback_tag)
180
gobject.source_remove(self._update_timer_callback_tag)
177
181
self._update_timer_callback_tag = None
179
183
def checker_completed(self, exitstatus, condition, command):
180
184
if exitstatus == 0:
181
self.logger('Checker for client {} (command "{}")'
182
' succeeded'.format(self.properties["Name"],
187
188
if os.WIFEXITED(condition):
188
self.logger('Checker for client {} (command "{}") failed'
189
self.logger('Checker for client {0} (command "{1}")'
190
' failed with exit code {2}'
190
191
.format(self.properties["Name"], command,
191
192
os.WEXITSTATUS(condition)))
192
193
elif os.WIFSIGNALED(condition):
193
self.logger('Checker for client {} (command "{}") was'
194
' killed by signal {}'
194
self.logger('Checker for client {0} (command "{1}") was'
195
' killed by signal {2}'
195
196
.format(self.properties["Name"], command,
196
197
os.WTERMSIG(condition)))
198
elif os.WCOREDUMP(condition):
199
self.logger('Checker for client {0} (command "{1}")'
201
.format(self.properties["Name"], command))
203
self.logger('Checker for client {0} completed'
205
.format(self.properties["Name"]))
199
208
def checker_started(self, command):
200
"""Server signals that a checker started."""
201
self.logger('Client {} started checker "{}"'
202
.format(self.properties["Name"],
209
"""Server signals that a checker started. This could be useful
210
to log in the future. """
211
#self.logger('Client {0} started checker "{1}"'
212
# .format(self.properties["Name"],
205
216
def got_secret(self):
206
self.logger('Client {} received its secret'
217
self.logger('Client {0} received its secret'
207
218
.format(self.properties["Name"]))
209
220
def need_approval(self, timeout, default):
211
message = 'Client {} needs approval within {} seconds'
222
message = 'Client {0} needs approval within {1} seconds'
213
message = 'Client {} will get its secret in {} seconds'
224
message = 'Client {0} will get its secret in {1} seconds'
214
225
self.logger(message.format(self.properties["Name"],
217
228
def rejected(self, reason):
218
self.logger('Client {} was rejected; reason: {}'
229
self.logger('Client {0} was rejected; reason: {1}'
219
230
.format(self.properties["Name"], reason))
221
232
def selectable(self):
266
277
timer = datetime.timedelta()
267
278
if self.properties["ApprovedByDefault"]:
268
message = "Approval in {}. (d)eny?"
279
message = "Approval in {0}. (d)eny?"
270
message = "Denial in {}. (a)pprove?"
271
message = message.format(str(timer).rsplit(".", 1)[0])
281
message = "Denial in {0}. (a)pprove?"
282
message = message.format(unicode(timer).rsplit(".", 1)[0])
272
283
self.using_timer(True)
273
284
elif self.properties["LastCheckerStatus"] != 0:
274
285
# When checker has failed, show timer until client expires
281
292
timer = max(expires - datetime.datetime.utcnow(),
282
293
datetime.timedelta())
283
294
message = ('A checker has failed! Time until client'
285
.format(str(timer).rsplit(".", 1)[0]))
295
' gets disabled: {0}'
296
.format(unicode(timer).rsplit(".", 1)[0]))
286
297
self.using_timer(True)
288
299
message = "enabled"
289
300
self.using_timer(False)
290
self._text = "{}{}".format(base, message)
301
self._text = "{0}{1}".format(base, message)
292
303
if not urwid.supports_unicode():
293
304
self._text = self._text.encode("ascii", "replace")
306
317
self.update_hook()
308
319
def update_timer(self):
309
"""called by GLib. Will indefinitely loop until
310
GLib.source_remove() on tag is called
320
"""called by gobject. Will indefinitely loop until
321
gobject.source_remove() on tag is called"""
313
323
return True # Keep calling this
315
325
def delete(self, **kwargs):
316
326
if self._update_timer_callback_tag is not None:
317
GLib.source_remove(self._update_timer_callback_tag)
327
gobject.source_remove(self._update_timer_callback_tag)
318
328
self._update_timer_callback_tag = None
319
329
for match in self.match_objects:
334
344
This overrides the method from urwid.FlowWidget"""
336
self.proxy.Set(client_interface, "Enabled",
337
dbus.Boolean(True), ignore_reply = True,
338
dbus_interface = dbus.PROPERTIES_IFACE)
346
self.proxy.Enable(dbus_interface = client_interface,
340
self.proxy.Set(client_interface, "Enabled", False,
342
dbus_interface = dbus.PROPERTIES_IFACE)
349
self.proxy.Disable(dbus_interface = client_interface,
344
352
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
345
353
dbus_interface = client_interface,
354
362
ignore_reply=True)
356
self.proxy.Set(client_interface, "CheckerRunning",
357
dbus.Boolean(True), ignore_reply = True,
358
dbus_interface = dbus.PROPERTIES_IFACE)
364
self.proxy.StartChecker(dbus_interface = client_interface,
360
self.proxy.Set(client_interface, "CheckerRunning",
361
dbus.Boolean(False), ignore_reply = True,
362
dbus_interface = dbus.PROPERTIES_IFACE)
367
self.proxy.StopChecker(dbus_interface = client_interface,
364
370
self.proxy.CheckedOK(dbus_interface = client_interface,
365
371
ignore_reply=True)
376
def properties_changed(self, interface, properties, invalidated):
377
"""Call self.update() if any properties changed.
382
def property_changed(self, property=None, **kwargs):
383
"""Call self.update() if old value is not new value.
378
384
This overrides the method from MandosClientPropertyCache"""
379
old_values = { key: self.properties.get(key)
380
for key in properties.keys() }
381
super(MandosClientWidget, self).properties_changed(
382
interface, properties, invalidated)
383
if any(old_values[key] != self.properties.get(key)
384
for key in old_values):
385
property_name = unicode(property)
386
old_value = self.properties.get(property_name)
387
super(MandosClientWidget, self).property_changed(
388
property=property, **kwargs)
389
if self.properties.get(property_name) != old_value:
401
406
"""This is the entire user interface - the whole screen
402
407
with boxes, lists of client widgets, etc.
404
def __init__(self, max_log_length=1000, log_level=1):
409
def __init__(self, max_log_length=1000):
405
410
DBusGMainLoop(set_as_default=True)
407
412
self.screen = urwid.curses_display.Screen()
411
416
"default", "default", None),
413
"bold", "default", "bold"),
418
"default", "default", "bold"),
414
419
("underline-blink",
415
"underline,blink", "default", "underline,blink"),
420
"default", "default", "underline"),
417
"standout", "default", "standout"),
422
"default", "default", "standout"),
418
423
("bold-underline-blink",
419
"bold,underline,blink", "default", "bold,underline,blink"),
424
"default", "default", ("bold", "underline")),
420
425
("bold-standout",
421
"bold,standout", "default", "bold,standout"),
426
"default", "default", ("bold", "standout")),
422
427
("underline-blink-standout",
423
"underline,blink,standout", "default",
424
"underline,blink,standout"),
428
"default", "default", ("underline", "standout")),
425
429
("bold-underline-blink-standout",
426
"bold,underline,blink,standout", "default",
427
"bold,underline,blink,standout"),
430
"default", "default", ("bold", "underline",
430
434
if urwid.supports_unicode():
463
465
"q: Quit ?: Help"))
465
467
self.busname = domain + '.Mandos'
466
self.main_loop = GLib.MainLoop()
468
self.main_loop = gobject.MainLoop()
468
470
def client_not_found(self, fingerprint, address):
469
self.log_message("Client with address {} and fingerprint {}"
470
" could not be found"
471
self.log_message("Client with address {0} and fingerprint"
472
" {1} could not be found"
471
473
.format(address, fingerprint))
473
475
def rebuild(self):
486
488
self.uilist.append(self.logbox)
487
489
self.topwidget = urwid.Pile(self.uilist)
489
def log_message(self, message, level=1):
490
"""Log message formatted with timestamp"""
491
if level < self.log_level:
491
def log_message(self, message):
493
492
timestamp = datetime.datetime.now().isoformat()
494
self.log_message_raw("{}: {}".format(timestamp, message),
493
self.log_message_raw(timestamp + ": " + message)
497
def log_message_raw(self, markup, level=1):
495
def log_message_raw(self, markup):
498
496
"""Add a log message to the log buffer."""
499
if level < self.log_level:
501
497
self.log.append(urwid.Text(markup, wrap=self.log_wrap))
502
498
if (self.max_log_length
503
499
and len(self.log) > self.max_log_length):
510
506
"""Toggle visibility of the log buffer."""
511
507
self.log_visible = not self.log_visible
513
self.log_message("Log visibility changed to: {}"
514
.format(self.log_visible), level=0)
509
#self.log_message("Log visibility changed to: "
510
# + unicode(self.log_visible))
516
512
def change_log_display(self):
517
513
"""Change type of log display.
522
518
self.log_wrap = "clip"
523
519
for textwidget in self.log:
524
520
textwidget.set_wrap_mode(self.log_wrap)
525
self.log_message("Wrap mode: {}".format(self.log_wrap),
521
#self.log_message("Wrap mode: " + self.log_wrap)
528
def find_and_remove_client(self, path, interfaces):
523
def find_and_remove_client(self, path, name):
529
524
"""Find a client by its object path and remove it.
531
This is connected to the InterfacesRemoved signal from the
526
This is connected to the ClientRemoved signal from the
532
527
Mandos server object."""
533
if client_interface not in interfaces:
534
# Not a Mandos client object; ignore
537
529
client = self.clients_dict[path]
540
self.log_message("Unknown client {!r} removed"
532
self.log_message("Unknown client {0!r} ({1!r}) removed"
545
def add_new_client(self, path, ifs_and_props):
546
"""Find a client by its object path and remove it.
548
This is connected to the InterfacesAdded signal from the
549
Mandos server object.
551
if client_interface not in ifs_and_props:
552
# Not a Mandos client object; ignore
537
def add_new_client(self, path):
554
538
client_proxy_object = self.bus.get_object(self.busname, path)
555
539
self.add_client(MandosClientWidget(server_proxy_object
556
540
=self.mandos_serv,
599
585
mandos_clients = (self.mandos_serv
600
586
.GetAllClientsWithProperties())
601
if not mandos_clients:
602
self.log_message_raw(("bold", "Note: Server has no clients."))
603
587
except dbus.exceptions.DBusException:
604
self.log_message_raw(("bold", "Note: No Mandos server running."))
605
588
mandos_clients = dbus.Dictionary()
607
590
(self.mandos_serv
608
.connect_to_signal("InterfacesRemoved",
591
.connect_to_signal("ClientRemoved",
609
592
self.find_and_remove_client,
611
= dbus.OBJECT_MANAGER_IFACE,
593
dbus_interface=server_interface,
612
594
byte_arrays=True))
613
595
(self.mandos_serv
614
.connect_to_signal("InterfacesAdded",
596
.connect_to_signal("ClientAdded",
615
597
self.add_new_client,
617
= dbus.OBJECT_MANAGER_IFACE,
598
dbus_interface=server_interface,
618
599
byte_arrays=True))
619
600
(self.mandos_serv
620
601
.connect_to_signal("ClientNotFound",
621
602
self.client_not_found,
622
603
dbus_interface=server_interface,
623
604
byte_arrays=True))
624
for path, client in mandos_clients.items():
605
for path, client in mandos_clients.iteritems():
625
606
client_proxy_object = self.bus.get_object(self.busname,
627
608
self.add_client(MandosClientWidget(server_proxy_object