60
53
domain = 'se.recompile'
61
54
server_interface = domain + '.Mandos'
62
55
client_interface = domain + '.Mandos.Client'
66
dbus.OBJECT_MANAGER_IFACE
67
except AttributeError:
68
dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
58
# Always run in monochrome mode
59
urwid.curses_display.curses.has_colors = lambda : False
61
# Urwid doesn't support blinking, but we want it. Since we have no
62
# use for underline on its own, we make underline also always blink.
63
urwid.curses_display.curses.A_UNDERLINE |= (
64
urwid.curses_display.curses.A_BLINK)
70
66
def isoformat_to_datetime(iso):
71
67
"Parse an ISO 8601 date string to a datetime.datetime()"
88
84
properties and calls a hook function when any of them are
91
def __init__(self, proxy_object=None, properties=None, **kwargs):
87
def __init__(self, proxy_object=None, *args, **kwargs):
92
88
self.proxy = proxy_object # Mandos Client proxy object
93
self.properties = dict() if properties is None else properties
90
self.properties = dict()
94
91
self.property_changed_match = (
95
self.proxy.connect_to_signal("PropertiesChanged",
96
self.properties_changed,
97
dbus.PROPERTIES_IFACE,
92
self.proxy.connect_to_signal("PropertyChanged",
93
self.property_changed,
100
if properties is None:
101
self.properties.update(
102
self.proxy.GetAll(client_interface,
104
= dbus.PROPERTIES_IFACE))
106
super(MandosClientPropertyCache, self).__init__(**kwargs)
97
self.properties.update(
98
self.proxy.GetAll(client_interface,
99
dbus_interface = dbus.PROPERTIES_IFACE))
101
#XXX This breaks good super behaviour
102
# super(MandosClientPropertyCache, self).__init__(
108
def properties_changed(self, interface, properties, invalidated):
109
"""This is called whenever we get a PropertiesChanged signal
110
It updates the changed properties in the "properties" dict.
105
def property_changed(self, property=None, value=None):
106
"""This is called whenever we get a PropertyChanged signal
107
It updates the changed property in the "properties" dict.
112
109
# Update properties dict with new value
113
if interface == client_interface:
114
self.properties.update(properties)
110
self.properties[property] = value
112
def delete(self, *args, **kwargs):
117
113
self.property_changed_match.remove()
114
super(MandosClientPropertyCache, self).__init__(
120
118
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
133
131
self.logger = logger
135
133
self._update_timer_callback_tag = None
134
self._update_timer_callback_lock = 0
137
136
# The widget shown normally
138
137
self._text_widget = urwid.Text("")
139
138
# The widget shown when we have focus
140
139
self._focus_text_widget = urwid.Text("")
141
super(MandosClientWidget, self).__init__(**kwargs)
140
super(MandosClientWidget, self).__init__(
141
update_hook=update_hook, delete_hook=delete_hook,
143
144
self.opened = False
146
last_checked_ok = isoformat_to_datetime(self.properties
149
if self.properties ["LastCheckerStatus"] != 0:
150
self.using_timer(True)
152
if self.need_approval:
153
self.using_timer(True)
145
155
self.match_objects = (
146
156
self.proxy.connect_to_signal("CheckerCompleted",
147
157
self.checker_completed,
164
174
client_interface,
165
175
byte_arrays=True))
166
self.logger('Created client {}'
167
.format(self.properties["Name"]), level=0)
176
#self.logger('Created client {0}'
177
# .format(self.properties["Name"]))
179
def property_changed(self, property=None, value=None):
180
super(self, MandosClientWidget).property_changed(property,
182
if property == "ApprovalPending":
183
using_timer(bool(value))
184
if property == "LastCheckerStatus":
185
using_timer(value != 0)
186
#self.logger('Checker for client {0} (command "{1}") was '
187
# ' successful'.format(self.properties["Name"],
169
190
def using_timer(self, flag):
170
191
"""Call this method with True or False when timer should be
171
192
activated or deactivated.
173
if flag and self._update_timer_callback_tag is None:
194
old = self._update_timer_callback_lock
196
self._update_timer_callback_lock += 1
198
self._update_timer_callback_lock -= 1
199
if old == 0 and self._update_timer_callback_lock:
174
200
# Will update the shown timer value every second
175
201
self._update_timer_callback_tag = (gobject.timeout_add
177
203
self.update_timer))
178
elif not (flag or self._update_timer_callback_tag is None):
204
elif old and self._update_timer_callback_lock == 0:
179
205
gobject.source_remove(self._update_timer_callback_tag)
180
206
self._update_timer_callback_tag = None
182
208
def checker_completed(self, exitstatus, condition, command):
183
209
if exitstatus == 0:
184
self.logger('Checker for client {} (command "{}")'
185
' succeeded'.format(self.properties["Name"],
190
213
if os.WIFEXITED(condition):
191
self.logger('Checker for client {} (command "{}") failed'
214
self.logger('Checker for client {0} (command "{1}")'
215
' failed with exit code {2}'
193
216
.format(self.properties["Name"], command,
194
217
os.WEXITSTATUS(condition)))
195
218
elif os.WIFSIGNALED(condition):
196
self.logger('Checker for client {} (command "{}") was'
197
' killed by signal {}'
219
self.logger('Checker for client {0} (command "{1}") was'
220
' killed by signal {2}'
198
221
.format(self.properties["Name"], command,
199
222
os.WTERMSIG(condition)))
223
elif os.WCOREDUMP(condition):
224
self.logger('Checker for client {0} (command "{1}")'
226
.format(self.properties["Name"], command))
228
self.logger('Checker for client {0} completed'
230
.format(self.properties["Name"]))
202
233
def checker_started(self, command):
203
"""Server signals that a checker started."""
204
self.logger('Client {} started checker "{}"'
205
.format(self.properties["Name"],
234
"""Server signals that a checker started. This could be useful
235
to log in the future. """
236
#self.logger('Client {0} started checker "{1}"'
237
# .format(self.properties["Name"],
208
241
def got_secret(self):
209
self.logger('Client {} received its secret'
242
self.logger('Client {0} received its secret'
210
243
.format(self.properties["Name"]))
212
245
def need_approval(self, timeout, default):
214
message = 'Client {} needs approval within {} seconds'
247
message = 'Client {0} needs approval within {1} seconds'
216
message = 'Client {} will get its secret in {} seconds'
249
message = 'Client {0} will get its secret in {1} seconds'
217
250
self.logger(message.format(self.properties["Name"],
252
self.using_timer(True)
220
254
def rejected(self, reason):
221
self.logger('Client {} was rejected; reason: {}'
255
self.logger('Client {0} was rejected; reason: {1}'
222
256
.format(self.properties["Name"], reason))
224
258
def selectable(self):
262
295
last_approval_request = isoformat_to_datetime(
263
296
self.properties["LastApprovalRequest"])
264
297
if last_approval_request is not None:
265
timer = max(timeout - (datetime.datetime.utcnow()
266
- last_approval_request),
267
datetime.timedelta())
298
timer = timeout - (datetime.datetime.utcnow()
299
- last_approval_request)
269
301
timer = datetime.timedelta()
270
302
if self.properties["ApprovedByDefault"]:
271
message = "Approval in {}. (d)eny?"
303
message = "Approval in {0}. (d)eny?"
273
message = "Denial in {}. (a)pprove?"
274
message = message.format(str(timer).rsplit(".", 1)[0])
275
self.using_timer(True)
305
message = "Denial in {0}. (a)pprove?"
306
message = message.format(unicode(timer).rsplit(".", 1)[0])
276
307
elif self.properties["LastCheckerStatus"] != 0:
277
308
# When checker has failed, show timer until client expires
278
309
expires = self.properties["Expires"]
282
313
expires = (datetime.datetime.strptime
283
314
(expires, '%Y-%m-%dT%H:%M:%S.%f'))
284
timer = max(expires - datetime.datetime.utcnow(),
285
datetime.timedelta())
315
timer = expires - datetime.datetime.utcnow()
286
316
message = ('A checker has failed! Time until client'
288
.format(str(timer).rsplit(".", 1)[0]))
289
self.using_timer(True)
317
' gets disabled: {0}'
318
.format(unicode(timer).rsplit(".", 1)[0]))
291
320
message = "enabled"
292
self.using_timer(False)
293
self._text = "{}{}".format(base, message)
321
self._text = "{0}{1}".format(base, message)
295
323
if not urwid.supports_unicode():
296
324
self._text = self._text.encode("ascii", "replace")
297
325
textlist = [("normal", self._text)]
336
364
This overrides the method from urwid.FlowWidget"""
338
self.proxy.Set(client_interface, "Enabled",
339
dbus.Boolean(True), ignore_reply = True,
340
dbus_interface = dbus.PROPERTIES_IFACE)
366
self.proxy.Enable(dbus_interface = client_interface,
342
self.proxy.Set(client_interface, "Enabled", False,
344
dbus_interface = dbus.PROPERTIES_IFACE)
369
self.proxy.Disable(dbus_interface = client_interface,
346
372
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
347
373
dbus_interface = client_interface,
356
382
ignore_reply=True)
358
self.proxy.Set(client_interface, "CheckerRunning",
359
dbus.Boolean(True), ignore_reply = True,
360
dbus_interface = dbus.PROPERTIES_IFACE)
384
self.proxy.StartChecker(dbus_interface = client_interface,
362
self.proxy.Set(client_interface, "CheckerRunning",
363
dbus.Boolean(False), ignore_reply = True,
364
dbus_interface = dbus.PROPERTIES_IFACE)
387
self.proxy.StopChecker(dbus_interface = client_interface,
366
390
self.proxy.CheckedOK(dbus_interface = client_interface,
367
391
ignore_reply=True)
378
def properties_changed(self, interface, properties, invalidated):
379
"""Call self.update() if any properties changed.
402
def property_changed(self, property=None, value=None,
404
"""Call self.update() if old value is not new value.
380
405
This overrides the method from MandosClientPropertyCache"""
381
old_values = { key: self.properties.get(key)
382
for key in properties.keys() }
383
super(MandosClientWidget, self).properties_changed(
384
interface, properties, invalidated)
385
if any(old_values[key] != self.properties.get(key)
386
for key in old_values):
406
property_name = unicode(property)
407
old_value = self.properties.get(property_name)
408
super(MandosClientWidget, self).property_changed(
409
property=property, value=value, *args, **kwargs)
410
if self.properties.get(property_name) != old_value:
413
437
"default", "default", None),
415
"bold", "default", "bold"),
439
"default", "default", "bold"),
416
440
("underline-blink",
417
"underline,blink", "default", "underline,blink"),
441
"default", "default", "underline"),
419
"standout", "default", "standout"),
443
"default", "default", "standout"),
420
444
("bold-underline-blink",
421
"bold,underline,blink", "default", "bold,underline,blink"),
445
"default", "default", ("bold", "underline")),
422
446
("bold-standout",
423
"bold,standout", "default", "bold,standout"),
447
"default", "default", ("bold", "standout")),
424
448
("underline-blink-standout",
425
"underline,blink,standout", "default",
426
"underline,blink,standout"),
449
"default", "default", ("underline", "standout")),
427
450
("bold-underline-blink-standout",
428
"bold,underline,blink,standout", "default",
429
"bold,underline,blink,standout"),
451
"default", "default", ("bold", "underline",
432
455
if urwid.supports_unicode():
488
509
self.uilist.append(self.logbox)
489
510
self.topwidget = urwid.Pile(self.uilist)
491
def log_message(self, message, level=1):
492
"""Log message formatted with timestamp"""
493
if level < self.log_level:
512
def log_message(self, message):
495
513
timestamp = datetime.datetime.now().isoformat()
496
self.log_message_raw("{}: {}".format(timestamp, message),
514
self.log_message_raw(timestamp + ": " + message)
499
def log_message_raw(self, markup, level=1):
516
def log_message_raw(self, markup):
500
517
"""Add a log message to the log buffer."""
501
if level < self.log_level:
503
518
self.log.append(urwid.Text(markup, wrap=self.log_wrap))
504
519
if (self.max_log_length
505
520
and len(self.log) > self.max_log_length):
524
539
self.log_wrap = "clip"
525
540
for textwidget in self.log:
526
541
textwidget.set_wrap_mode(self.log_wrap)
527
self.log_message("Wrap mode: {}".format(self.log_wrap),
542
#self.log_message("Wrap mode: " + self.log_wrap)
530
def find_and_remove_client(self, path, interfaces):
544
def find_and_remove_client(self, path, name):
531
545
"""Find a client by its object path and remove it.
533
This is connected to the InterfacesRemoved signal from the
547
This is connected to the ClientRemoved signal from the
534
548
Mandos server object."""
535
if client_interface not in interfaces:
536
# Not a Mandos client object; ignore
539
550
client = self.clients_dict[path]
542
self.log_message("Unknown client {!r} removed"
553
self.log_message("Unknown client {0!r} ({1!r}) removed"
547
def add_new_client(self, path, ifs_and_props):
548
"""Find a client by its object path and remove it.
550
This is connected to the InterfacesAdded signal from the
551
Mandos server object.
553
if client_interface not in ifs_and_props:
554
# Not a Mandos client object; ignore
558
def add_new_client(self, path):
556
559
client_proxy_object = self.bus.get_object(self.busname, path)
557
560
self.add_client(MandosClientWidget(server_proxy_object
558
561
=self.mandos_serv,
601
606
mandos_clients = (self.mandos_serv
602
607
.GetAllClientsWithProperties())
603
if not mandos_clients:
604
self.log_message_raw(("bold", "Note: Server has no clients."))
605
608
except dbus.exceptions.DBusException:
606
self.log_message_raw(("bold", "Note: No Mandos server running."))
607
609
mandos_clients = dbus.Dictionary()
609
611
(self.mandos_serv
610
.connect_to_signal("InterfacesRemoved",
612
.connect_to_signal("ClientRemoved",
611
613
self.find_and_remove_client,
613
= dbus.OBJECT_MANAGER_IFACE,
614
dbus_interface=server_interface,
614
615
byte_arrays=True))
615
616
(self.mandos_serv
616
.connect_to_signal("InterfacesAdded",
617
.connect_to_signal("ClientAdded",
617
618
self.add_new_client,
619
= dbus.OBJECT_MANAGER_IFACE,
619
dbus_interface=server_interface,
620
620
byte_arrays=True))
621
621
(self.mandos_serv
622
622
.connect_to_signal("ClientNotFound",
623
623
self.client_not_found,
624
624
dbus_interface=server_interface,
625
625
byte_arrays=True))
626
for path, client in mandos_clients.items():
626
for path, client in mandos_clients.iteritems():
627
627
client_proxy_object = self.bus.get_object(self.busname,
629
629
self.add_client(MandosClientWidget(server_proxy_object