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
61
domain = 'se.recompile'
53
62
server_interface = domain + '.Mandos'
54
63
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
66
def isoformat_to_datetime(iso):
66
67
"Parse an ISO 8601 date string to a datetime.datetime()"
83
84
properties and calls a hook function when any of them are
86
def __init__(self, proxy_object=None, *args, **kwargs):
87
def __init__(self, proxy_object=None, properties=None, **kwargs):
87
88
self.proxy = proxy_object # Mandos Client proxy object
89
self.properties = dict()
89
self.properties = dict() if properties is None else properties
90
90
self.property_changed_match = (
91
91
self.proxy.connect_to_signal("PropertyChanged",
92
self.property_changed,
92
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__(
96
if properties is None:
97
self.properties.update(
98
self.proxy.GetAll(client_interface,
100
= dbus.PROPERTIES_IFACE))
102
super(MandosClientPropertyCache, self).__init__(**kwargs)
104
def _property_changed(self, property, value):
105
"""Helper which takes positional arguments"""
106
return self.property_changed(property=property, value=value)
104
108
def property_changed(self, property=None, value=None):
105
109
"""This is called whenever we get a PropertyChanged signal
130
132
self.logger = logger
132
134
self._update_timer_callback_tag = None
133
self._update_timer_callback_lock = 0
135
136
# The widget shown normally
136
137
self._text_widget = urwid.Text("")
137
138
# The widget shown when we have focus
138
139
self._focus_text_widget = urwid.Text("")
139
super(MandosClientWidget, self).__init__(
140
update_hook=update_hook, delete_hook=delete_hook,
140
super(MandosClientWidget, self).__init__(**kwargs)
143
142
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
144
self.match_objects = (
155
145
self.proxy.connect_to_signal("CheckerCompleted",
156
146
self.checker_completed,
173
163
client_interface,
174
164
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))
165
#self.logger('Created client {0}'
166
# .format(self.properties["Name"]))
188
168
def using_timer(self, flag):
189
169
"""Call this method with True or False when timer should be
190
170
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:
172
if flag and self._update_timer_callback_tag is None:
198
173
# Will update the shown timer value every second
199
174
self._update_timer_callback_tag = (gobject.timeout_add
201
176
self.update_timer))
202
elif old and self._update_timer_callback_lock == 0:
177
elif not (flag or self._update_timer_callback_tag is None):
203
178
gobject.source_remove(self._update_timer_callback_tag)
204
179
self._update_timer_callback_tag = None
211
186
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)))
187
self.logger('Checker for client {0} (command "{1}")'
188
' failed with exit code {2}'
189
.format(self.properties["Name"], command,
190
os.WEXITSTATUS(condition)))
216
191
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)))
192
self.logger('Checker for client {0} (command "{1}") was'
193
' killed by signal {2}'
194
.format(self.properties["Name"], command,
195
os.WTERMSIG(condition)))
221
196
elif os.WCOREDUMP(condition):
222
self.logger('Checker for client %s (command "%s")'
197
self.logger('Checker for client {0} (command "{1}")'
224
% (self.properties["Name"], command))
199
.format(self.properties["Name"], command))
226
self.logger('Checker for client %s completed'
201
self.logger('Checker for client {0} completed'
203
.format(self.properties["Name"]))
230
206
def checker_started(self, command):
231
207
"""Server signals that a checker started. This could be useful
232
208
to log in the future. """
233
#self.logger('Client %s started checker "%s"'
234
# % (self.properties["Name"], unicode(command)))
209
#self.logger('Client {0} started checker "{1}"'
210
# .format(self.properties["Name"],
237
214
def got_secret(self):
238
self.logger('Client %s received its secret'
239
% self.properties["Name"])
215
self.logger('Client {0} received its secret'
216
.format(self.properties["Name"]))
241
218
def need_approval(self, timeout, default):
243
message = 'Client %s needs approval within %s seconds'
220
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)
222
message = 'Client {0} will get its secret in {1} seconds'
223
self.logger(message.format(self.properties["Name"],
250
226
def rejected(self, reason):
251
self.logger('Client %s was rejected; reason: %s'
252
% (self.properties["Name"], reason))
227
self.logger('Client {0} was rejected; reason: {1}'
228
.format(self.properties["Name"], reason))
254
230
def selectable(self):
255
231
"""Make this a "selectable" widget.
277
253
"bold-underline-blink":
278
254
"bold-underline-blink-standout",
281
257
# Rebuild focus and non-focus widgets using current properties
283
259
# Base part of a client. Name!
285
% {"name": self.properties["Name"]})
260
base = '{name}: '.format(name=self.properties["Name"])
286
261
if not self.properties["Enabled"]:
287
262
message = "DISABLED"
263
self.using_timer(False)
288
264
elif self.properties["ApprovalPending"]:
289
265
timeout = datetime.timedelta(milliseconds
290
266
= self.properties
292
268
last_approval_request = isoformat_to_datetime(
293
269
self.properties["LastApprovalRequest"])
294
270
if last_approval_request is not None:
295
timer = timeout - (datetime.datetime.utcnow()
296
- last_approval_request)
271
timer = max(timeout - (datetime.datetime.utcnow()
272
- last_approval_request),
273
datetime.timedelta())
298
275
timer = datetime.timedelta()
299
276
if self.properties["ApprovedByDefault"]:
300
message = "Approval in %s. (d)eny?"
277
message = "Approval in {0}. (d)eny?"
302
message = "Denial in %s. (a)pprove?"
303
message = message % unicode(timer).rsplit(".", 1)[0]
279
message = "Denial in {0}. (a)pprove?"
280
message = message.format(str(timer).rsplit(".", 1)[0])
281
self.using_timer(True)
304
282
elif self.properties["LastCheckerStatus"] != 0:
305
# When checker has failed, print a timer until client expires
283
# When checker has failed, show timer until client expires
306
284
expires = self.properties["Expires"]
307
285
if expires == "":
308
286
timer = datetime.timedelta(0)
310
expires = datetime.datetime.strptime(expires,
311
'%Y-%m-%dT%H:%M:%S.%f')
312
timer = expires - datetime.datetime.utcnow()
288
expires = (datetime.datetime.strptime
289
(expires, '%Y-%m-%dT%H:%M:%S.%f'))
290
timer = max(expires - datetime.datetime.utcnow(),
291
datetime.timedelta())
313
292
message = ('A checker has failed! Time until client'
315
% unicode(timer).rsplit(".", 1)[0])
293
' gets disabled: {0}'
294
.format(str(timer).rsplit(".", 1)[0]))
295
self.using_timer(True)
317
297
message = "enabled"
318
self._text = "%s%s" % (base, message)
298
self.using_timer(False)
299
self._text = "{0}{1}".format(base, message)
320
301
if not urwid.supports_unicode():
321
302
self._text = self._text.encode("ascii", "replace")
322
303
textlist = [("normal", self._text)]
399
def property_changed(self, property=None, value=None,
380
def property_changed(self, property=None, **kwargs):
401
381
"""Call self.update() if old value is not new value.
402
382
This overrides the method from MandosClientPropertyCache"""
403
property_name = unicode(property)
383
property_name = str(property)
404
384
old_value = self.properties.get(property_name)
405
385
super(MandosClientWidget, self).property_changed(
406
property=property, value=value, *args, **kwargs)
386
property=property, **kwargs)
407
387
if self.properties.get(property_name) != old_value:
413
393
"down" key presses, thus not allowing any containing widgets to
414
394
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)
396
def keypress(self, *args, **kwargs):
397
ret = super(ConstrainedListBox, self).keypress(*args, **kwargs)
418
398
if ret in ("up", "down"):
434
414
"default", "default", None),
436
"default", "default", "bold"),
416
"bold", "default", "bold"),
437
417
("underline-blink",
438
"default", "default", "underline"),
418
"underline,blink", "default", "underline,blink"),
440
"default", "default", "standout"),
420
"standout", "default", "standout"),
441
421
("bold-underline-blink",
442
"default", "default", ("bold", "underline")),
422
"bold,underline,blink", "default", "bold,underline,blink"),
443
423
("bold-standout",
444
"default", "default", ("bold", "standout")),
424
"bold,standout", "default", "bold,standout"),
445
425
("underline-blink-standout",
446
"default", "default", ("underline", "standout")),
426
"underline,blink,standout", "default",
427
"underline,blink,standout"),
447
428
("bold-underline-blink-standout",
448
"default", "default", ("bold", "underline",
429
"bold,underline,blink,standout", "default",
430
"bold,underline,blink,standout"),
452
433
if urwid.supports_unicode():
486
467
self.main_loop = gobject.MainLoop()
488
469
def client_not_found(self, fingerprint, address):
489
self.log_message(("Client with address %s and fingerprint %s"
490
" could not be found" % (address,
470
self.log_message("Client with address {0} and fingerprint"
471
" {1} could not be found"
472
.format(address, fingerprint))
493
474
def rebuild(self):
494
475
"""This rebuilds the User Interface.