60
55
domain = 'se.recompile'
61
56
server_interface = domain + '.Mandos'
62
57
client_interface = domain + '.Mandos.Client'
66
dbus.OBJECT_MANAGER_IFACE
67
except AttributeError:
68
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)
70
68
def isoformat_to_datetime(iso):
71
69
"Parse an ISO 8601 date string to a datetime.datetime()"
88
86
properties and calls a hook function when any of them are
91
def __init__(self, proxy_object=None, properties=None, **kwargs):
89
def __init__(self, proxy_object=None, *args, **kwargs):
92
90
self.proxy = proxy_object # Mandos Client proxy object
93
self.properties = dict() if properties is None else properties
92
self.properties = dict()
94
93
self.property_changed_match = (
95
self.proxy.connect_to_signal("PropertiesChanged",
96
self.properties_changed,
97
dbus.PROPERTIES_IFACE,
94
self.proxy.connect_to_signal("PropertyChanged",
95
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)
99
self.properties.update(
100
self.proxy.GetAll(client_interface,
101
dbus_interface = dbus.PROPERTIES_IFACE))
103
#XXX This breaks good super behaviour
104
# 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.
107
def property_changed(self, property=None, value=None):
108
"""This is called whenever we get a PropertyChanged signal
109
It updates the changed property in the "properties" dict.
112
111
# Update properties dict with new value
113
if interface == client_interface:
114
self.properties.update(properties)
112
self.properties[property] = value
114
def delete(self, *args, **kwargs):
117
115
self.property_changed_match.remove()
116
super(MandosClientPropertyCache, self).__init__(
120
120
class MandosClientWidget(urwid.FlowWidget, MandosClientPropertyCache):
133
133
self.logger = logger
135
135
self._update_timer_callback_tag = None
136
self._update_timer_callback_lock = 0
137
138
# The widget shown normally
138
139
self._text_widget = urwid.Text("")
139
140
# The widget shown when we have focus
140
141
self._focus_text_widget = urwid.Text("")
141
super(MandosClientWidget, self).__init__(**kwargs)
142
super(MandosClientWidget, self).__init__(
143
update_hook=update_hook, delete_hook=delete_hook,
143
146
self.opened = False
148
last_checked_ok = isoformat_to_datetime(self.properties
151
if self.properties ["LastCheckerStatus"] != 0:
152
self.using_timer(True)
154
if self.need_approval:
155
self.using_timer(True)
145
157
self.match_objects = (
146
158
self.proxy.connect_to_signal("CheckerCompleted",
147
159
self.checker_completed,
164
176
client_interface,
165
177
byte_arrays=True))
166
self.logger('Created client {}'
167
.format(self.properties["Name"]), level=0)
178
#self.logger('Created client {0}'
179
# .format(self.properties["Name"]))
181
def property_changed(self, property=None, value=None):
182
super(self, MandosClientWidget).property_changed(property,
184
if property == "ApprovalPending":
185
using_timer(bool(value))
186
if property == "LastCheckerStatus":
187
using_timer(value != 0)
188
#self.logger('Checker for client {0} (command "{1}") was '
189
# ' successful'.format(self.properties["Name"],
169
192
def using_timer(self, flag):
170
193
"""Call this method with True or False when timer should be
171
194
activated or deactivated.
173
if flag and self._update_timer_callback_tag is None:
196
old = self._update_timer_callback_lock
198
self._update_timer_callback_lock += 1
200
self._update_timer_callback_lock -= 1
201
if old == 0 and self._update_timer_callback_lock:
174
202
# Will update the shown timer value every second
175
203
self._update_timer_callback_tag = (gobject.timeout_add
177
205
self.update_timer))
178
elif not (flag or self._update_timer_callback_tag is None):
206
elif old and self._update_timer_callback_lock == 0:
179
207
gobject.source_remove(self._update_timer_callback_tag)
180
208
self._update_timer_callback_tag = None
182
210
def checker_completed(self, exitstatus, condition, command):
183
211
if exitstatus == 0:
184
self.logger('Checker for client {} (command "{}")'
185
' succeeded'.format(self.properties["Name"],
190
215
if os.WIFEXITED(condition):
191
self.logger('Checker for client {} (command "{}") failed'
216
self.logger('Checker for client {0} (command "{1}")'
217
' failed with exit code {2}'
193
218
.format(self.properties["Name"], command,
194
219
os.WEXITSTATUS(condition)))
195
220
elif os.WIFSIGNALED(condition):
196
self.logger('Checker for client {} (command "{}") was'
197
' killed by signal {}'
221
self.logger('Checker for client {0} (command "{1}") was'
222
' killed by signal {2}'
198
223
.format(self.properties["Name"], command,
199
224
os.WTERMSIG(condition)))
225
elif os.WCOREDUMP(condition):
226
self.logger('Checker for client {0} (command "{1}")'
228
.format(self.properties["Name"], command))
230
self.logger('Checker for client {0} completed'
232
.format(self.properties["Name"]))
202
235
def checker_started(self, command):
203
"""Server signals that a checker started."""
204
self.logger('Client {} started checker "{}"'
205
.format(self.properties["Name"],
236
"""Server signals that a checker started. This could be useful
237
to log in the future. """
238
#self.logger('Client {0} started checker "{1}"'
239
# .format(self.properties["Name"],
208
243
def got_secret(self):
209
self.logger('Client {} received its secret'
244
self.logger('Client {0} received its secret'
210
245
.format(self.properties["Name"]))
212
247
def need_approval(self, timeout, default):
214
message = 'Client {} needs approval within {} seconds'
249
message = 'Client {0} needs approval within {1} seconds'
216
message = 'Client {} will get its secret in {} seconds'
251
message = 'Client {0} will get its secret in {1} seconds'
217
252
self.logger(message.format(self.properties["Name"],
254
self.using_timer(True)
220
256
def rejected(self, reason):
221
self.logger('Client {} was rejected; reason: {}'
257
self.logger('Client {0} was rejected; reason: {1}'
222
258
.format(self.properties["Name"], reason))
224
260
def selectable(self):
262
297
last_approval_request = isoformat_to_datetime(
263
298
self.properties["LastApprovalRequest"])
264
299
if last_approval_request is not None:
265
timer = max(timeout - (datetime.datetime.utcnow()
266
- last_approval_request),
267
datetime.timedelta())
300
timer = timeout - (datetime.datetime.utcnow()
301
- last_approval_request)
269
303
timer = datetime.timedelta()
270
304
if self.properties["ApprovedByDefault"]:
271
message = "Approval in {}. (d)eny?"
305
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)
307
message = "Denial in {0}. (a)pprove?"
308
message = message.format(unicode(timer).rsplit(".", 1)[0])
276
309
elif self.properties["LastCheckerStatus"] != 0:
277
310
# When checker has failed, show timer until client expires
278
311
expires = self.properties["Expires"]
282
315
expires = (datetime.datetime.strptime
283
316
(expires, '%Y-%m-%dT%H:%M:%S.%f'))
284
timer = max(expires - datetime.datetime.utcnow(),
285
datetime.timedelta())
317
timer = expires - datetime.datetime.utcnow()
286
318
message = ('A checker has failed! Time until client'
288
.format(str(timer).rsplit(".", 1)[0]))
289
self.using_timer(True)
319
' gets disabled: {0}'
320
.format(unicode(timer).rsplit(".", 1)[0]))
291
322
message = "enabled"
292
self.using_timer(False)
293
self._text = "{}{}".format(base, message)
323
self._text = "{0}{1}".format(base, message)
295
325
if not urwid.supports_unicode():
296
326
self._text = self._text.encode("ascii", "replace")
297
327
textlist = [("normal", self._text)]
336
366
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)
368
self.proxy.Enable(dbus_interface = client_interface,
342
self.proxy.Set(client_interface, "Enabled", False,
344
dbus_interface = dbus.PROPERTIES_IFACE)
371
self.proxy.Disable(dbus_interface = client_interface,
346
374
self.proxy.Approve(dbus.Boolean(True, variant_level=1),
347
375
dbus_interface = client_interface,
356
384
ignore_reply=True)
358
self.proxy.Set(client_interface, "CheckerRunning",
359
dbus.Boolean(True), ignore_reply = True,
360
dbus_interface = dbus.PROPERTIES_IFACE)
386
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)
389
self.proxy.StopChecker(dbus_interface = client_interface,
366
392
self.proxy.CheckedOK(dbus_interface = client_interface,
367
393
ignore_reply=True)
378
def properties_changed(self, interface, properties, invalidated):
379
"""Call self.update() if any properties changed.
404
def property_changed(self, property=None, value=None,
406
"""Call self.update() if old value is not new value.
380
407
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):
408
property_name = unicode(property)
409
old_value = self.properties.get(property_name)
410
super(MandosClientWidget, self).property_changed(
411
property=property, value=value, *args, **kwargs)
412
if self.properties.get(property_name) != old_value:
413
439
"default", "default", None),
415
"bold", "default", "bold"),
441
"default", "default", "bold"),
416
442
("underline-blink",
417
"underline,blink", "default", "underline,blink"),
443
"default", "default", "underline"),
419
"standout", "default", "standout"),
445
"default", "default", "standout"),
420
446
("bold-underline-blink",
421
"bold,underline,blink", "default", "bold,underline,blink"),
447
"default", "default", ("bold", "underline")),
422
448
("bold-standout",
423
"bold,standout", "default", "bold,standout"),
449
"default", "default", ("bold", "standout")),
424
450
("underline-blink-standout",
425
"underline,blink,standout", "default",
426
"underline,blink,standout"),
451
"default", "default", ("underline", "standout")),
427
452
("bold-underline-blink-standout",
428
"bold,underline,blink,standout", "default",
429
"bold,underline,blink,standout"),
453
"default", "default", ("bold", "underline",
432
457
if urwid.supports_unicode():
488
511
self.uilist.append(self.logbox)
489
512
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:
514
def log_message(self, message):
495
515
timestamp = datetime.datetime.now().isoformat()
496
self.log_message_raw("{}: {}".format(timestamp, message),
516
self.log_message_raw(timestamp + ": " + message)
499
def log_message_raw(self, markup, level=1):
518
def log_message_raw(self, markup):
500
519
"""Add a log message to the log buffer."""
501
if level < self.log_level:
503
520
self.log.append(urwid.Text(markup, wrap=self.log_wrap))
504
521
if (self.max_log_length
505
522
and len(self.log) > self.max_log_length):
524
541
self.log_wrap = "clip"
525
542
for textwidget in self.log:
526
543
textwidget.set_wrap_mode(self.log_wrap)
527
self.log_message("Wrap mode: {}".format(self.log_wrap),
544
#self.log_message("Wrap mode: " + self.log_wrap)
530
def find_and_remove_client(self, path, interfaces):
546
def find_and_remove_client(self, path, name):
531
547
"""Find a client by its object path and remove it.
533
This is connected to the InterfacesRemoved signal from the
549
This is connected to the ClientRemoved signal from the
534
550
Mandos server object."""
535
if client_interface not in interfaces:
536
# Not a Mandos client object; ignore
539
552
client = self.clients_dict[path]
542
self.log_message("Unknown client {!r} removed"
555
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
560
def add_new_client(self, path):
556
561
client_proxy_object = self.bus.get_object(self.busname, path)
557
562
self.add_client(MandosClientWidget(server_proxy_object
558
563
=self.mandos_serv,
601
608
mandos_clients = (self.mandos_serv
602
609
.GetAllClientsWithProperties())
603
if not mandos_clients:
604
self.log_message_raw(("bold", "Note: Server has no clients."))
605
610
except dbus.exceptions.DBusException:
606
self.log_message_raw(("bold", "Note: No Mandos server running."))
607
611
mandos_clients = dbus.Dictionary()
609
613
(self.mandos_serv
610
.connect_to_signal("InterfacesRemoved",
614
.connect_to_signal("ClientRemoved",
611
615
self.find_and_remove_client,
613
= dbus.OBJECT_MANAGER_IFACE,
616
dbus_interface=server_interface,
614
617
byte_arrays=True))
615
618
(self.mandos_serv
616
.connect_to_signal("InterfacesAdded",
619
.connect_to_signal("ClientAdded",
617
620
self.add_new_client,
619
= dbus.OBJECT_MANAGER_IFACE,
621
dbus_interface=server_interface,
620
622
byte_arrays=True))
621
623
(self.mandos_serv
622
624
.connect_to_signal("ClientNotFound",
623
625
self.client_not_found,
624
626
dbus_interface=server_interface,
625
627
byte_arrays=True))
626
for path, client in mandos_clients.items():
628
for path, client in mandos_clients.iteritems():
627
629
client_proxy_object = self.bus.get_object(self.busname,
629
631
self.add_client(MandosClientWidget(server_proxy_object