57
52
domain = 'se.recompile'
58
53
server_interface = domain + '.Mandos'
59
54
client_interface = domain + '.Mandos.Client'
63
dbus.OBJECT_MANAGER_IFACE
64
except AttributeError:
65
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
57
# Always run in monochrome mode
58
urwid.curses_display.curses.has_colors = lambda : False
60
# Urwid doesn't support blinking, but we want it. Since we have no
61
# use for underline on its own, we make underline also always blink.
62
urwid.curses_display.curses.A_UNDERLINE |= (
63
urwid.curses_display.curses.A_BLINK)
67
65
def isoformat_to_datetime(iso):
68
66
"Parse an ISO 8601 date string to a datetime.datetime()"
85
83
properties and calls a hook function when any of them are
88
def __init__(self, proxy_object=None, properties=None, **kwargs):
86
def __init__(self, proxy_object=None, *args, **kwargs):
89
87
self.proxy = proxy_object # Mandos Client proxy object
90
self.properties = dict() if properties is None else properties
89
self.properties = dict()
91
90
self.property_changed_match = (
92
self.proxy.connect_to_signal("PropertiesChanged",
93
self.properties_changed,
94
dbus.PROPERTIES_IFACE,
91
self.proxy.connect_to_signal("PropertyChanged",
92
self.property_changed,
97
if properties is None:
98
self.properties.update(
99
self.proxy.GetAll(client_interface,
101
= dbus.PROPERTIES_IFACE))
103
super(MandosClientPropertyCache, self).__init__(**kwargs)
96
self.properties.update(
97
self.proxy.GetAll(client_interface,
98
dbus_interface = dbus.PROPERTIES_IFACE))
100
#XXX This breaks good super behaviour
101
# super(MandosClientPropertyCache, self).__init__(
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.
104
def property_changed(self, property=None, value=None):
105
"""This is called whenever we get a PropertyChanged signal
106
It updates the changed property in the "properties" dict.
109
108
# Update properties dict with new value
110
if interface == client_interface:
111
self.properties.update(properties)
109
self.properties[property] = value
111
def delete(self, *args, **kwargs):
114
112
self.property_changed_match.remove()
113
super(MandosClientPropertyCache, self).__init__(
117
117
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
130
130
self.logger = logger
132
132
self._update_timer_callback_tag = None
133
self._update_timer_callback_lock = 0
134
135
# The widget shown normally
135
136
self._text_widget = urwid.Text("")
136
137
# The widget shown when we have focus
137
138
self._focus_text_widget = urwid.Text("")
138
super(MandosClientWidget, self).__init__(**kwargs)
139
super(MandosClientWidget, self).__init__(
140
update_hook=update_hook, delete_hook=delete_hook,
140
143
self.opened = False
145
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)
142
154
self.match_objects = (
143
155
self.proxy.connect_to_signal("CheckerCompleted",
144
156
self.checker_completed,
161
173
client_interface,
162
174
byte_arrays=True))
163
self.logger('Created client {}'
164
.format(self.properties["Name"]), level=0)
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"],
166
189
def using_timer(self, flag):
167
190
"""Call this method with True or False when timer should be
168
191
activated or deactivated.
170
if flag and self._update_timer_callback_tag is None:
193
old = self._update_timer_callback_lock
195
self._update_timer_callback_lock += 1
197
self._update_timer_callback_lock -= 1
198
if old == 0 and self._update_timer_callback_lock:
171
199
# Will update the shown timer value every second
172
self._update_timer_callback_tag = (GLib.timeout_add
200
self._update_timer_callback_tag = (gobject.timeout_add
174
202
self.update_timer))
175
elif not (flag or self._update_timer_callback_tag is None):
176
GLib.source_remove(self._update_timer_callback_tag)
203
elif old and self._update_timer_callback_lock == 0:
204
gobject.source_remove(self._update_timer_callback_tag)
177
205
self._update_timer_callback_tag = None
179
207
def checker_completed(self, exitstatus, condition, command):
180
208
if exitstatus == 0:
181
self.logger('Checker for client {} (command "{}")'
182
' succeeded'.format(self.properties["Name"],
187
212
if os.WIFEXITED(condition):
188
self.logger('Checker for client {} (command "{}") failed'
213
self.logger('Checker for client {0} (command "{1}")'
214
' failed with exit code {2}'
190
215
.format(self.properties["Name"], command,
191
216
os.WEXITSTATUS(condition)))
192
217
elif os.WIFSIGNALED(condition):
193
self.logger('Checker for client {} (command "{}") was'
194
' killed by signal {}'
218
self.logger('Checker for client {0} (command "{1}") was'
219
' killed by signal {2}'
195
220
.format(self.properties["Name"], command,
196
221
os.WTERMSIG(condition)))
222
elif os.WCOREDUMP(condition):
223
self.logger('Checker for client {0} (command "{1}")'
225
.format(self.properties["Name"], command))
227
self.logger('Checker for client {0} completed'
229
.format(self.properties["Name"]))
199
232
def checker_started(self, command):
200
"""Server signals that a checker started."""
201
self.logger('Client {} started checker "{}"'
202
.format(self.properties["Name"],
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"],
205
240
def got_secret(self):
206
self.logger('Client {} received its secret'
241
self.logger('Client {0} received its secret'
207
242
.format(self.properties["Name"]))
209
244
def need_approval(self, timeout, default):
211
message = 'Client {} needs approval within {} seconds'
246
message = 'Client {0} needs approval within {1} seconds'
213
message = 'Client {} will get its secret in {} seconds'
248
message = 'Client {0} will get its secret in {1} seconds'
214
249
self.logger(message.format(self.properties["Name"],
251
self.using_timer(True)
217
253
def rejected(self, reason):
218
self.logger('Client {} was rejected; reason: {}'
254
self.logger('Client {0} was rejected; reason: {1}'
219
255
.format(self.properties["Name"], reason))
221
257
def selectable(self):
259
294
last_approval_request = isoformat_to_datetime(
260
295
self.properties["LastApprovalRequest"])
261
296
if last_approval_request is not None:
262
timer = max(timeout - (datetime.datetime.utcnow()
263
- last_approval_request),
264
datetime.timedelta())
297
timer = timeout - (datetime.datetime.utcnow()
298
- last_approval_request)
266
300
timer = datetime.timedelta()
267
301
if self.properties["ApprovedByDefault"]:
268
message = "Approval in {}. (d)eny?"
302
message = "Approval in {0}. (d)eny?"
270
message = "Denial in {}. (a)pprove?"
271
message = message.format(str(timer).rsplit(".", 1)[0])
272
self.using_timer(True)
304
message = "Denial in {0}. (a)pprove?"
305
message = message.format(unicode(timer).rsplit(".", 1)[0])
273
306
elif self.properties["LastCheckerStatus"] != 0:
274
# When checker has failed, show timer until client expires
307
# When checker has failed, print a timer until client expires
275
308
expires = self.properties["Expires"]
276
309
if expires == "":
277
310
timer = datetime.timedelta(0)
279
expires = (datetime.datetime.strptime
280
(expires, '%Y-%m-%dT%H:%M:%S.%f'))
281
timer = max(expires - datetime.datetime.utcnow(),
282
datetime.timedelta())
312
expires = datetime.datetime.strptime(expires,
313
'%Y-%m-%dT%H:%M:%S.%f')
314
timer = expires - datetime.datetime.utcnow()
283
315
message = ('A checker has failed! Time until client'
285
.format(str(timer).rsplit(".", 1)[0]))
286
self.using_timer(True)
316
' gets disabled: {0}'
317
.format(unicode(timer).rsplit(".", 1)[0]))
288
319
message = "enabled"
289
self.using_timer(False)
290
self._text = "{}{}".format(base, message)
320
self._text = "{0}{1}".format(base, message)
292
322
if not urwid.supports_unicode():
293
323
self._text = self._text.encode("ascii", "replace")
294
324
textlist = [("normal", self._text)]
306
336
self.update_hook()
308
338
def update_timer(self):
309
"""called by GLib. Will indefinitely loop until
310
GLib.source_remove() on tag is called
339
"""called by gobject. Will indefinitely loop until
340
gobject.source_remove() on tag is called"""
313
342
return True # Keep calling this
315
def delete(self, **kwargs):
344
def delete(self, *args, **kwargs):
316
345
if self._update_timer_callback_tag is not None:
317
GLib.source_remove(self._update_timer_callback_tag)
346
gobject.source_remove(self._update_timer_callback_tag)
318
347
self._update_timer_callback_tag = None
319
348
for match in self.match_objects:
321
350
self.match_objects = ()
322
351
if self.delete_hook is not None:
323
352
self.delete_hook(self)
324
return super(MandosClientWidget, self).delete(**kwargs)
353
return super(MandosClientWidget, self).delete(*args, **kwargs)
326
355
def render(self, maxcolrow, focus=False):
327
356
"""Render differently if we have focus.
334
363
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)
365
self.proxy.Enable(dbus_interface = client_interface,
340
self.proxy.Set(client_interface, "Enabled", False,
342
dbus_interface = dbus.PROPERTIES_IFACE)
368
self.proxy.Disable(dbus_interface = client_interface,
344
371
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
345
372
dbus_interface = client_interface,
354
381
ignore_reply=True)
356
self.proxy.Set(client_interface, "CheckerRunning",
357
dbus.Boolean(True), ignore_reply = True,
358
dbus_interface = dbus.PROPERTIES_IFACE)
383
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)
386
self.proxy.StopChecker(dbus_interface = client_interface,
364
389
self.proxy.CheckedOK(dbus_interface = client_interface,
365
390
ignore_reply=True)
376
def properties_changed(self, interface, properties, invalidated):
377
"""Call self.update() if any properties changed.
401
def property_changed(self, property=None, value=None,
403
"""Call self.update() if old value is not new value.
378
404
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):
405
property_name = unicode(property)
406
old_value = self.properties.get(property_name)
407
super(MandosClientWidget, self).property_changed(
408
property=property, value=value, *args, **kwargs)
409
if self.properties.get(property_name) != old_value:
411
436
"default", "default", None),
413
"bold", "default", "bold"),
438
"default", "default", "bold"),
414
439
("underline-blink",
415
"underline,blink", "default", "underline,blink"),
440
"default", "default", "underline"),
417
"standout", "default", "standout"),
442
"default", "default", "standout"),
418
443
("bold-underline-blink",
419
"bold,underline,blink", "default", "bold,underline,blink"),
444
"default", "default", ("bold", "underline")),
420
445
("bold-standout",
421
"bold,standout", "default", "bold,standout"),
446
"default", "default", ("bold", "standout")),
422
447
("underline-blink-standout",
423
"underline,blink,standout", "default",
424
"underline,blink,standout"),
448
"default", "default", ("underline", "standout")),
425
449
("bold-underline-blink-standout",
426
"bold,underline,blink,standout", "default",
427
"bold,underline,blink,standout"),
450
"default", "default", ("bold", "underline",
430
454
if urwid.supports_unicode():
486
508
self.uilist.append(self.logbox)
487
509
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:
511
def log_message(self, message):
493
512
timestamp = datetime.datetime.now().isoformat()
494
self.log_message_raw("{}: {}".format(timestamp, message),
513
self.log_message_raw(timestamp + ": " + message)
497
def log_message_raw(self, markup, level=1):
515
def log_message_raw(self, markup):
498
516
"""Add a log message to the log buffer."""
499
if level < self.log_level:
501
517
self.log.append(urwid.Text(markup, wrap=self.log_wrap))
502
518
if (self.max_log_length
503
519
and len(self.log) > self.max_log_length):
522
538
self.log_wrap = "clip"
523
539
for textwidget in self.log:
524
540
textwidget.set_wrap_mode(self.log_wrap)
525
self.log_message("Wrap mode: {}".format(self.log_wrap),
541
#self.log_message("Wrap mode: " + self.log_wrap)
528
def find_and_remove_client(self, path, interfaces):
543
def find_and_remove_client(self, path, name):
529
544
"""Find a client by its object path and remove it.
531
This is connected to the InterfacesRemoved signal from the
546
This is connected to the ClientRemoved signal from the
532
547
Mandos server object."""
533
if client_interface not in interfaces:
534
# Not a Mandos client object; ignore
537
549
client = self.clients_dict[path]
540
self.log_message("Unknown client {!r} removed"
552
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
557
def add_new_client(self, path):
554
558
client_proxy_object = self.bus.get_object(self.busname, path)
555
559
self.add_client(MandosClientWidget(server_proxy_object
556
560
=self.mandos_serv,
599
605
mandos_clients = (self.mandos_serv
600
606
.GetAllClientsWithProperties())
601
if not mandos_clients:
602
self.log_message_raw(("bold", "Note: Server has no clients."))
603
607
except dbus.exceptions.DBusException:
604
self.log_message_raw(("bold", "Note: No Mandos server running."))
605
608
mandos_clients = dbus.Dictionary()
607
610
(self.mandos_serv
608
.connect_to_signal("InterfacesRemoved",
611
.connect_to_signal("ClientRemoved",
609
612
self.find_and_remove_client,
611
= dbus.OBJECT_MANAGER_IFACE,
613
dbus_interface=server_interface,
612
614
byte_arrays=True))
613
615
(self.mandos_serv
614
.connect_to_signal("InterfacesAdded",
616
.connect_to_signal("ClientAdded",
615
617
self.add_new_client,
617
= dbus.OBJECT_MANAGER_IFACE,
618
dbus_interface=server_interface,
618
619
byte_arrays=True))
619
620
(self.mandos_serv
620
621
.connect_to_signal("ClientNotFound",
621
622
self.client_not_found,
622
623
dbus_interface=server_interface,
623
624
byte_arrays=True))
624
for path, client in mandos_clients.items():
625
for path, client in mandos_clients.iteritems():
625
626
client_proxy_object = self.bus.get_object(self.busname,
627
628
self.add_client(MandosClientWidget(server_proxy_object