17
17
# GNU General Public License for more details.
19
19
# You should have received a copy of the GNU General Public License
20
# along with this program. If not, see <http://www.gnu.org/licenses/>.
20
# along with this program. If not, see
21
# <http://www.gnu.org/licenses/>.
22
23
# Contact the authors at <mandos@recompile.se>.
25
26
from __future__ import (division, absolute_import, print_function,
29
from future_builtins import *
52
60
domain = 'se.recompile'
53
61
server_interface = domain + '.Mandos'
54
62
client_interface = domain + '.Mandos.Client'
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)
65
65
def isoformat_to_datetime(iso):
66
66
"Parse an ISO 8601 date string to a datetime.datetime()"
83
83
properties and calls a hook function when any of them are
86
def __init__(self, proxy_object=None, *args, **kwargs):
86
def __init__(self, proxy_object=None, properties=None, **kwargs):
87
87
self.proxy = proxy_object # Mandos Client proxy object
89
self.properties = dict()
88
self.properties = dict() if properties is None else properties
90
89
self.property_changed_match = (
91
90
self.proxy.connect_to_signal("PropertyChanged",
92
self.property_changed,
91
self._property_changed,
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__(
95
if properties is None:
96
self.properties.update(
97
self.proxy.GetAll(client_interface,
99
= dbus.PROPERTIES_IFACE))
101
super(MandosClientPropertyCache, self).__init__(**kwargs)
103
def _property_changed(self, property, value):
104
"""Helper which takes positional arguments"""
105
return self.property_changed(property=property, value=value)
104
107
def property_changed(self, property=None, value=None):
105
108
"""This is called whenever we get a PropertyChanged signal
130
131
self.logger = logger
132
133
self._update_timer_callback_tag = None
133
self._update_timer_callback_lock = 0
135
135
# The widget shown normally
136
136
self._text_widget = urwid.Text("")
137
137
# The widget shown when we have focus
138
138
self._focus_text_widget = urwid.Text("")
139
super(MandosClientWidget, self).__init__(
140
update_hook=update_hook, delete_hook=delete_hook,
139
super(MandosClientWidget, self).__init__(**kwargs)
143
141
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)
154
143
self.match_objects = (
155
144
self.proxy.connect_to_signal("CheckerCompleted",
156
145
self.checker_completed,
173
162
client_interface,
174
163
byte_arrays=True))
175
#self.logger('Created client %s' % (self.properties["Name"]))
177
def property_changed(self, property=None, value=None):
178
super(self, MandosClientWidget).property_changed(property,
180
if property == "ApprovalPending":
181
using_timer(bool(value))
182
if property == "LastCheckerStatus":
183
using_timer(value != 0)
184
#self.logger('Checker for client %s (command "%s")'
186
# % (self.properties["Name"], command))
164
#self.logger('Created client {0}'
165
# .format(self.properties["Name"]))
188
167
def using_timer(self, flag):
189
168
"""Call this method with True or False when timer should be
190
169
activated or deactivated.
192
old = self._update_timer_callback_lock
194
self._update_timer_callback_lock += 1
196
self._update_timer_callback_lock -= 1
197
if old == 0 and self._update_timer_callback_lock:
171
if flag and self._update_timer_callback_tag is None:
198
172
# Will update the shown timer value every second
199
173
self._update_timer_callback_tag = (gobject.timeout_add
201
175
self.update_timer))
202
elif old and self._update_timer_callback_lock == 0:
176
elif not (flag or self._update_timer_callback_tag is None):
203
177
gobject.source_remove(self._update_timer_callback_tag)
204
178
self._update_timer_callback_tag = None
211
185
if os.WIFEXITED(condition):
212
self.logger('Checker for client %s (command "%s")'
213
' failed with exit code %s'
214
% (self.properties["Name"], command,
215
os.WEXITSTATUS(condition)))
186
self.logger('Checker for client {0} (command "{1}")'
187
' failed with exit code {2}'
188
.format(self.properties["Name"], command,
189
os.WEXITSTATUS(condition)))
216
190
elif os.WIFSIGNALED(condition):
217
self.logger('Checker for client %s (command "%s")'
218
' was killed by signal %s'
219
% (self.properties["Name"], command,
220
os.WTERMSIG(condition)))
191
self.logger('Checker for client {0} (command "{1}") was'
192
' killed by signal {2}'
193
.format(self.properties["Name"], command,
194
os.WTERMSIG(condition)))
221
195
elif os.WCOREDUMP(condition):
222
self.logger('Checker for client %s (command "%s")'
196
self.logger('Checker for client {0} (command "{1}")'
224
% (self.properties["Name"], command))
198
.format(self.properties["Name"], command))
226
self.logger('Checker for client %s completed'
200
self.logger('Checker for client {0} completed'
202
.format(self.properties["Name"]))
230
205
def checker_started(self, command):
231
206
"""Server signals that a checker started. This could be useful
232
207
to log in the future. """
233
#self.logger('Client %s started checker "%s"'
234
# % (self.properties["Name"], unicode(command)))
208
#self.logger('Client {0} started checker "{1}"'
209
# .format(self.properties["Name"],
237
213
def got_secret(self):
238
self.logger('Client %s received its secret'
239
% self.properties["Name"])
214
self.logger('Client {0} received its secret'
215
.format(self.properties["Name"]))
241
217
def need_approval(self, timeout, default):
243
message = 'Client %s needs approval within %s seconds'
219
message = 'Client {0} needs approval within {1} seconds'
245
message = 'Client %s will get its secret in %s seconds'
247
% (self.properties["Name"], timeout/1000))
248
self.using_timer(True)
221
message = 'Client {0} will get its secret in {1} seconds'
222
self.logger(message.format(self.properties["Name"],
250
225
def rejected(self, reason):
251
self.logger('Client %s was rejected; reason: %s'
252
% (self.properties["Name"], reason))
226
self.logger('Client {0} was rejected; reason: {1}'
227
.format(self.properties["Name"], reason))
254
229
def selectable(self):
255
230
"""Make this a "selectable" widget.
277
252
"bold-underline-blink":
278
253
"bold-underline-blink-standout",
281
256
# Rebuild focus and non-focus widgets using current properties
283
258
# Base part of a client. Name!
285
% {"name": self.properties["Name"]})
259
base = '{name}: '.format(name=self.properties["Name"])
286
260
if not self.properties["Enabled"]:
287
261
message = "DISABLED"
262
self.using_timer(False)
288
263
elif self.properties["ApprovalPending"]:
289
264
timeout = datetime.timedelta(milliseconds
290
265
= self.properties
292
267
last_approval_request = isoformat_to_datetime(
293
268
self.properties["LastApprovalRequest"])
294
269
if last_approval_request is not None:
295
timer = timeout - (datetime.datetime.utcnow()
296
- last_approval_request)
270
timer = max(timeout - (datetime.datetime.utcnow()
271
- last_approval_request),
272
datetime.timedelta())
298
274
timer = datetime.timedelta()
299
275
if self.properties["ApprovedByDefault"]:
300
message = "Approval in %s. (d)eny?"
276
message = "Approval in {0}. (d)eny?"
302
message = "Denial in %s. (a)pprove?"
303
message = message % unicode(timer).rsplit(".", 1)[0]
278
message = "Denial in {0}. (a)pprove?"
279
message = message.format(str(timer).rsplit(".", 1)[0])
280
self.using_timer(True)
304
281
elif self.properties["LastCheckerStatus"] != 0:
305
# When checker has failed, print a timer until client expires
282
# When checker has failed, show timer until client expires
306
283
expires = self.properties["Expires"]
307
284
if expires == "":
308
285
timer = datetime.timedelta(0)
310
expires = datetime.datetime.strptime(expires,
311
'%Y-%m-%dT%H:%M:%S.%f')
312
timer = expires - datetime.datetime.utcnow()
287
expires = (datetime.datetime.strptime
288
(expires, '%Y-%m-%dT%H:%M:%S.%f'))
289
timer = max(expires - datetime.datetime.utcnow(),
290
datetime.timedelta())
313
291
message = ('A checker has failed! Time until client'
315
% unicode(timer).rsplit(".", 1)[0])
292
' gets disabled: {0}'
293
.format(str(timer).rsplit(".", 1)[0]))
294
self.using_timer(True)
317
296
message = "enabled"
318
self._text = "%s%s" % (base, message)
297
self.using_timer(False)
298
self._text = "{0}{1}".format(base, message)
320
300
if not urwid.supports_unicode():
321
301
self._text = self._text.encode("ascii", "replace")
322
302
textlist = [("normal", self._text)]
399
def property_changed(self, property=None, value=None,
379
def property_changed(self, property=None, **kwargs):
401
380
"""Call self.update() if old value is not new value.
402
381
This overrides the method from MandosClientPropertyCache"""
403
property_name = unicode(property)
382
property_name = str(property)
404
383
old_value = self.properties.get(property_name)
405
384
super(MandosClientWidget, self).property_changed(
406
property=property, value=value, *args, **kwargs)
385
property=property, **kwargs)
407
386
if self.properties.get(property_name) != old_value:
413
392
"down" key presses, thus not allowing any containing widgets to
414
393
use them as an excuse to shift focus away from this widget.
416
def keypress(self, maxcolrow, key):
417
ret = super(ConstrainedListBox, self).keypress(maxcolrow, key)
395
def keypress(self, *args, **kwargs):
396
ret = super(ConstrainedListBox, self).keypress(*args, **kwargs)
418
397
if ret in ("up", "down"):
434
413
"default", "default", None),
436
"default", "default", "bold"),
415
"bold", "default", "bold"),
437
416
("underline-blink",
438
"default", "default", "underline"),
417
"underline,blink", "default", "underline,blink"),
440
"default", "default", "standout"),
419
"standout", "default", "standout"),
441
420
("bold-underline-blink",
442
"default", "default", ("bold", "underline")),
421
"bold,underline,blink", "default", "bold,underline,blink"),
443
422
("bold-standout",
444
"default", "default", ("bold", "standout")),
423
"bold,standout", "default", "bold,standout"),
445
424
("underline-blink-standout",
446
"default", "default", ("underline", "standout")),
425
"underline,blink,standout", "default",
426
"underline,blink,standout"),
447
427
("bold-underline-blink-standout",
448
"default", "default", ("bold", "underline",
428
"bold,underline,blink,standout", "default",
429
"bold,underline,blink,standout"),
452
432
if urwid.supports_unicode():
486
466
self.main_loop = gobject.MainLoop()
488
468
def client_not_found(self, fingerprint, address):
489
self.log_message(("Client with address %s and fingerprint %s"
490
" could not be found" % (address,
469
self.log_message("Client with address {0} and fingerprint"
470
" {1} could not be found"
471
.format(address, fingerprint))
493
473
def rebuild(self):
494
474
"""This rebuilds the User Interface.