2
2
# -*- mode: python; coding: utf-8 -*-
 
 
4
# Mandos Monitor - Control and monitor the Mandos server
 
 
6
# Copyright © 2009-2011 Teddy Hogeborn
 
 
7
# Copyright © 2009-2011 Björn Påhlsson
 
 
9
# This program is free software: you can redistribute it and/or modify
 
 
10
# it under the terms of the GNU General Public License as published by
 
 
11
# the Free Software Foundation, either version 3 of the License, or
 
 
12
# (at your option) any later version.
 
 
14
#     This program is distributed in the hope that it will be useful,
 
 
15
#     but WITHOUT ANY WARRANTY; without even the implied warranty of
 
 
16
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
 
17
#     GNU General Public License for more details.
 
 
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/>.
 
 
22
# Contact the authors at <mandos@fukt.bsnet.se>.
 
4
 
from __future__ import division, absolute_import, with_statement
 
 
25
from __future__ import division, absolute_import, print_function, unicode_literals
 
 
102
123
        self.logger = logger
 
104
125
        self._update_timer_callback_tag = None
 
 
126
        self._update_timer_callback_lock = 0
 
105
127
        self.last_checker_failed = False
 
107
129
        # The widget shown normally
 
108
 
        self._text_widget = urwid.Text(u"")
 
 
130
        self._text_widget = urwid.Text("")
 
109
131
        # The widget shown when we have focus
 
110
 
        self._focus_text_widget = urwid.Text(u"")
 
 
132
        self._focus_text_widget = urwid.Text("")
 
111
133
        super(MandosClientWidget, self).__init__(
 
112
134
            update_hook=update_hook, delete_hook=delete_hook,
 
115
137
        self.opened = False
 
116
 
        self.proxy.connect_to_signal(u"CheckerCompleted",
 
117
 
                                     self.checker_completed,
 
120
 
        self.proxy.connect_to_signal(u"CheckerStarted",
 
121
 
                                     self.checker_started,
 
124
 
        self.proxy.connect_to_signal(u"GotSecret",
 
128
 
        self.proxy.connect_to_signal(u"NeedApproval",
 
132
 
        self.proxy.connect_to_signal(u"Rejected",
 
136
139
        last_checked_ok = isoformat_to_datetime(self.properties
 
138
141
        if last_checked_ok is None:
 
139
142
            self.last_checker_failed = True
 
 
143
146
                                        > datetime.timedelta
 
147
151
        if self.last_checker_failed:
 
 
152
            self.using_timer(True)
 
 
154
        if self.need_approval:
 
 
155
            self.using_timer(True)
 
 
157
        self.proxy.connect_to_signal("CheckerCompleted",
 
 
158
                                     self.checker_completed,
 
 
161
        self.proxy.connect_to_signal("CheckerStarted",
 
 
162
                                     self.checker_started,
 
 
165
        self.proxy.connect_to_signal("GotSecret",
 
 
169
        self.proxy.connect_to_signal("NeedApproval",
 
 
173
        self.proxy.connect_to_signal("Rejected",
 
 
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))
 
 
184
    def using_timer(self, flag):
 
 
185
        """Call this method with True or False when timer should be
 
 
186
        activated or deactivated.
 
 
188
        old = self._update_timer_callback_lock
 
 
190
            self._update_timer_callback_lock += 1
 
 
192
            self._update_timer_callback_lock -= 1
 
 
193
        if old == 0 and self._update_timer_callback_lock:
 
148
194
            self._update_timer_callback_tag = (gobject.timeout_add
 
150
196
                                                self.update_timer))
 
 
197
        elif old and self._update_timer_callback_lock == 0:
 
 
198
            gobject.source_remove(self._update_timer_callback_tag)
 
 
199
            self._update_timer_callback_tag = None
 
152
201
    def checker_completed(self, exitstatus, condition, command):
 
153
202
        if exitstatus == 0:
 
154
203
            if self.last_checker_failed:
 
155
204
                self.last_checker_failed = False
 
156
 
                gobject.source_remove(self._update_timer_callback_tag)
 
157
 
                self._update_timer_callback_tag = None
 
158
 
            #self.logger(u'Checker for client %s (command "%s")'
 
160
 
            #            % (self.properties[u"Name"], command))
 
 
205
                self.using_timer(False)
 
 
206
            #self.logger('Checker for client %s (command "%s")'
 
 
208
            #            % (self.properties["Name"], command))
 
164
212
        if not self.last_checker_failed:
 
165
213
            self.last_checker_failed = True
 
166
 
            self._update_timer_callback_tag = (gobject.timeout_add
 
 
214
            self.using_timer(True)
 
169
215
        if os.WIFEXITED(condition):
 
170
 
            self.logger(u'Checker for client %s (command "%s")'
 
171
 
                        u' failed with exit code %s'
 
172
 
                        % (self.properties[u"Name"], command,
 
 
216
            self.logger('Checker for client %s (command "%s")'
 
 
217
                        ' failed with exit code %s'
 
 
218
                        % (self.properties["Name"], command,
 
173
219
                           os.WEXITSTATUS(condition)))
 
174
220
        elif os.WIFSIGNALED(condition):
 
175
 
            self.logger(u'Checker for client %s (command "%s")'
 
176
 
                        u' was killed by signal %s'
 
177
 
                        % (self.properties[u"Name"], command,
 
 
221
            self.logger('Checker for client %s (command "%s")'
 
 
222
                        ' was killed by signal %s'
 
 
223
                        % (self.properties["Name"], command,
 
178
224
                           os.WTERMSIG(condition)))
 
179
225
        elif os.WCOREDUMP(condition):
 
180
 
            self.logger(u'Checker for client %s (command "%s")'
 
182
 
                        % (self.properties[u"Name"], command))
 
 
226
            self.logger('Checker for client %s (command "%s")'
 
 
228
                        % (self.properties["Name"], command))
 
184
 
            self.logger(u'Checker for client %s completed'
 
 
230
            self.logger('Checker for client %s completed'
 
188
234
    def checker_started(self, command):
 
189
 
        #self.logger(u'Client %s started checker "%s"'
 
190
 
        #            % (self.properties[u"Name"], unicode(command)))
 
 
235
        #self.logger('Client %s started checker "%s"'
 
 
236
        #            % (self.properties["Name"], unicode(command)))
 
193
239
    def got_secret(self):
 
194
240
        self.last_checker_failed = False
 
195
 
        self.logger(u'Client %s received its secret'
 
196
 
                    % self.properties[u"Name"])
 
 
241
        self.logger('Client %s received its secret'
 
 
242
                    % self.properties["Name"])
 
198
244
    def need_approval(self, timeout, default):
 
200
 
            message = u'Client %s needs approval within %s seconds'
 
 
246
            message = 'Client %s needs approval within %s seconds'
 
202
 
            message = u'Client %s will get its secret in %s seconds'
 
 
248
            message = 'Client %s will get its secret in %s seconds'
 
203
249
        self.logger(message
 
204
 
                    % (self.properties[u"Name"], timeout/1000))
 
 
250
                    % (self.properties["Name"], timeout/1000))
 
 
251
        self.using_timer(True)
 
206
253
    def rejected(self, reason):
 
207
 
        self.logger(u'Client %s was rejected; reason: %s'
 
208
 
                    % (self.properties[u"Name"], reason))
 
 
254
        self.logger('Client %s was rejected; reason: %s'
 
 
255
                    % (self.properties["Name"], reason))
 
210
257
    def selectable(self):
 
211
258
        """Make this a "selectable" widget.
 
212
259
        This overrides the method from urwid.FlowWidget."""
 
215
 
    def rows(self, (maxcol,), focus=False):
 
 
262
    def rows(self, maxcolrow, focus=False):
 
216
263
        """How many rows this widget will occupy might depend on
 
217
264
        whether we have focus or not.
 
218
265
        This overrides the method from urwid.FlowWidget"""
 
219
 
        return self.current_widget(focus).rows((maxcol,), focus=focus)
 
 
266
        return self.current_widget(focus).rows(maxcolrow, focus=focus)
 
221
268
    def current_widget(self, focus=False):
 
222
269
        if focus or self.opened:
 
 
226
273
    def update(self):
 
227
274
        "Called when what is visible on the screen should be updated."
 
228
275
        # How to add standout mode to a style
 
229
 
        with_standout = { u"normal": u"standout",
 
230
 
                          u"bold": u"bold-standout",
 
232
 
                              u"underline-blink-standout",
 
233
 
                          u"bold-underline-blink":
 
234
 
                              u"bold-underline-blink-standout",
 
 
276
        with_standout = { "normal": "standout",
 
 
277
                          "bold": "bold-standout",
 
 
279
                              "underline-blink-standout",
 
 
280
                          "bold-underline-blink":
 
 
281
                              "bold-underline-blink-standout",
 
237
284
        # Rebuild focus and non-focus widgets using current properties
 
239
286
        # Base part of a client. Name!
 
240
 
        base = (u'%(name)s: '
 
241
 
                      % {u"name": self.properties[u"Name"]})
 
242
 
        if not self.properties[u"Enabled"]:
 
243
 
            message = u"DISABLED"
 
244
 
        elif self.properties[u"ApprovalPending"]:
 
245
 
            if self.properties[u"ApprovedByDefault"]:
 
246
 
                message = u"Connection established to client. (d)eny?"
 
248
 
                message = u"Seeks approval to send secret. (a)pprove?"
 
 
288
                      % {"name": self.properties["Name"]})
 
 
289
        if not self.properties["Enabled"]:
 
 
291
        elif self.properties["ApprovalPending"]:
 
 
292
            timeout = datetime.timedelta(milliseconds
 
 
295
            last_approval_request = isoformat_to_datetime(
 
 
296
                self.properties["LastApprovalRequest"])
 
 
297
            if last_approval_request is not None:
 
 
298
                timer = timeout - (datetime.datetime.utcnow()
 
 
299
                                   - last_approval_request)
 
 
301
                timer = datetime.timedelta()
 
 
302
            if self.properties["ApprovedByDefault"]:
 
 
303
                message = "Approval in %s. (d)eny?"
 
 
305
                message = "Denial in %s. (a)pprove?"
 
 
306
            message = message % unicode(timer).rsplit(".", 1)[0]
 
249
307
        elif self.last_checker_failed:
 
250
308
            timeout = datetime.timedelta(milliseconds
 
251
309
                                         = self.properties
 
253
311
            last_ok = isoformat_to_datetime(
 
254
 
                max((self.properties[u"LastCheckedOK"]
 
255
 
                     or self.properties[u"Created"]),
 
256
 
                    self.properties[u"LastEnabled"]))
 
 
312
                max((self.properties["LastCheckedOK"]
 
 
313
                     or self.properties["Created"]),
 
 
314
                    self.properties["LastEnabled"]))
 
257
315
            timer = timeout - (datetime.datetime.utcnow() - last_ok)
 
258
 
            message = (u'A checker has failed! Time until client'
 
 
316
            message = ('A checker has failed! Time until client'
 
260
318
                           % unicode(timer).rsplit(".", 1)[0])
 
263
321
        self._text = "%s%s" % (base, message)
 
265
323
        if not urwid.supports_unicode():
 
266
324
            self._text = self._text.encode("ascii", "replace")
 
267
 
        textlist = [(u"normal", self._text)]
 
 
325
        textlist = [("normal", self._text)]
 
268
326
        self._text_widget.set_text(textlist)
 
269
327
        self._focus_text_widget.set_text([(with_standout[text[0]],
 
 
290
348
        if self.delete_hook is not None:
 
291
349
            self.delete_hook(self)
 
293
 
    def render(self, (maxcol,), focus=False):
 
 
351
    def render(self, maxcolrow, focus=False):
 
294
352
        """Render differently if we have focus.
 
295
353
        This overrides the method from urwid.FlowWidget"""
 
296
 
        return self.current_widget(focus).render((maxcol,),
 
 
354
        return self.current_widget(focus).render(maxcolrow,
 
299
 
    def keypress(self, (maxcol,), key):
 
 
357
    def keypress(self, maxcolrow, key):
 
301
359
        This overrides the method from urwid.FlowWidget"""
 
303
 
            self.proxy.Enable(dbus_interface = client_interface)
 
305
 
            self.proxy.Disable(dbus_interface = client_interface)
 
 
361
            self.proxy.Enable(dbus_interface = client_interface,
 
 
364
            self.proxy.Disable(dbus_interface = client_interface,
 
307
367
            self.proxy.Approve(dbus.Boolean(True, variant_level=1),
 
308
 
                               dbus_interface = client_interface)
 
 
368
                               dbus_interface = client_interface,
 
310
371
            self.proxy.Approve(dbus.Boolean(False, variant_level=1),
 
311
 
                                  dbus_interface = client_interface)
 
312
 
        elif key == u"r" or key == u"_" or key == u"ctrl k":
 
 
372
                                  dbus_interface = client_interface,
 
 
374
        elif key == "R" or key == "_" or key == "ctrl k":
 
313
375
            self.server_proxy_object.RemoveClient(self.proxy
 
316
 
            self.proxy.StartChecker(dbus_interface = client_interface)
 
318
 
            self.proxy.StopChecker(dbus_interface = client_interface)
 
320
 
            self.proxy.CheckedOK(dbus_interface = client_interface)
 
 
379
            self.proxy.StartChecker(dbus_interface = client_interface,
 
 
382
            self.proxy.StopChecker(dbus_interface = client_interface,
 
 
385
            self.proxy.CheckedOK(dbus_interface = client_interface,
 
322
 
#         elif key == u"p" or key == "=":
 
 
388
#         elif key == "p" or key == "=":
 
323
389
#             self.proxy.pause()
 
324
 
#         elif key == u"u" or key == ":":
 
 
390
#         elif key == "u" or key == ":":
 
325
391
#             self.proxy.unpause()
 
326
 
#         elif key == u"RET":
 
329
 
#            self.proxy.Approve(True)
 
331
 
#            self.proxy.Approve(False)
 
 
367
428
        self.screen = urwid.curses_display.Screen()
 
369
430
        self.screen.register_palette((
 
371
 
                 u"default", u"default", None),
 
373
 
                 u"default", u"default", u"bold"),
 
375
 
                 u"default", u"default", u"underline"),
 
377
 
                 u"default", u"default", u"standout"),
 
378
 
                (u"bold-underline-blink",
 
379
 
                 u"default", u"default", (u"bold", u"underline")),
 
381
 
                 u"default", u"default", (u"bold", u"standout")),
 
382
 
                (u"underline-blink-standout",
 
383
 
                 u"default", u"default", (u"underline", u"standout")),
 
384
 
                (u"bold-underline-blink-standout",
 
385
 
                 u"default", u"default", (u"bold", u"underline",
 
 
432
                 "default", "default", None),
 
 
434
                 "default", "default", "bold"),
 
 
436
                 "default", "default", "underline"),
 
 
438
                 "default", "default", "standout"),
 
 
439
                ("bold-underline-blink",
 
 
440
                 "default", "default", ("bold", "underline")),
 
 
442
                 "default", "default", ("bold", "standout")),
 
 
443
                ("underline-blink-standout",
 
 
444
                 "default", "default", ("underline", "standout")),
 
 
445
                ("bold-underline-blink-standout",
 
 
446
                 "default", "default", ("bold", "underline",
 
389
450
        if urwid.supports_unicode():
 
390
 
            self.divider = u"─" # \u2500
 
391
 
            #self.divider = u"━" # \u2501
 
 
451
            self.divider = "─" # \u2500
 
 
452
            #self.divider = "━" # \u2501
 
393
 
            #self.divider = u"-" # \u002d
 
394
 
            self.divider = u"_" # \u005f
 
 
454
            #self.divider = "-" # \u002d
 
 
455
            self.divider = "_" # \u005f
 
396
457
        self.screen.start()
 
 
411
472
        # This keeps track of whether self.uilist currently has
 
412
473
        # self.logbox in it or not
 
413
474
        self.log_visible = True
 
414
 
        self.log_wrap = u"any"
 
 
475
        self.log_wrap = "any"
 
417
 
        self.log_message_raw((u"bold",
 
418
 
                              u"Mandos Monitor version " + version))
 
419
 
        self.log_message_raw((u"bold",
 
 
478
        self.log_message_raw(("bold",
 
 
479
                              "Mandos Monitor version " + version))
 
 
480
        self.log_message_raw(("bold",
 
422
483
        self.busname = domain + '.Mandos'
 
423
484
        self.main_loop = gobject.MainLoop()
 
424
485
        self.bus = dbus.SystemBus()
 
425
486
        mandos_dbus_objc = self.bus.get_object(
 
426
 
            self.busname, u"/", follow_name_owner_changes=True)
 
 
487
            self.busname, "/", follow_name_owner_changes=True)
 
427
488
        self.mandos_serv = dbus.Interface(mandos_dbus_objc,
 
429
490
                                          = server_interface)
 
 
497
558
            and len(self.log) > self.max_log_length):
 
498
559
            del self.log[0:len(self.log)-self.max_log_length-1]
 
499
560
        self.logbox.set_focus(len(self.logbox.body.contents),
 
500
 
                              coming_from=u"above")
 
503
564
    def toggle_log_display(self):
 
504
565
        """Toggle visibility of the log buffer."""
 
505
566
        self.log_visible = not self.log_visible
 
507
 
        self.log_message(u"Log visibility changed to: "
 
508
 
                         + unicode(self.log_visible))
 
 
568
        #self.log_message("Log visibility changed to: "
 
 
569
        #                 + unicode(self.log_visible))
 
510
571
    def change_log_display(self):
 
511
572
        """Change type of log display.
 
512
573
        Currently, this toggles wrapping of text lines."""
 
513
 
        if self.log_wrap == u"clip":
 
514
 
            self.log_wrap = u"any"
 
 
574
        if self.log_wrap == "clip":
 
 
575
            self.log_wrap = "any"
 
516
 
            self.log_wrap = u"clip"
 
 
577
            self.log_wrap = "clip"
 
517
578
        for textwidget in self.log:
 
518
579
            textwidget.set_wrap_mode(self.log_wrap)
 
519
 
        self.log_message(u"Wrap mode: " + self.log_wrap)
 
 
580
        #self.log_message("Wrap mode: " + self.log_wrap)
 
521
582
    def find_and_remove_client(self, path, name):
 
522
583
        """Find an client from its object path and remove it.
 
 
602
663
            except KeyError:    # :-)
 
605
 
            if key == u"q" or key == u"Q":
 
 
666
            if key == "q" or key == "Q":
 
608
 
            elif key == u"window resize":
 
 
669
            elif key == "window resize":
 
609
670
                self.size = self.screen.get_cols_rows()
 
611
 
            elif key == u"\f":  # Ctrl-L
 
 
672
            elif key == "\f":  # Ctrl-L
 
613
 
            elif key == u"l" or key == u"D":
 
 
674
            elif key == "l" or key == "D":
 
614
675
                self.toggle_log_display()
 
616
 
            elif key == u"w" or key == u"i":
 
 
677
            elif key == "w" or key == "i":
 
617
678
                self.change_log_display()
 
619
 
            elif key == u"?" or key == u"f1" or key == u"esc":
 
 
680
            elif key == "?" or key == "f1" or key == "esc":
 
620
681
                if not self.log_visible:
 
621
682
                    self.log_visible = True
 
623
 
                self.log_message_raw((u"bold",
 
627
 
                                            u"l: Log window toggle",
 
628
 
                                            u"TAB: Switch window",
 
630
 
                self.log_message_raw((u"bold",
 
636
 
                                             u"s: Start new checker",
 
 
684
                self.log_message_raw(("bold",
 
 
688
                                            "l: Log window toggle",
 
 
689
                                            "TAB: Switch window",
 
 
691
                self.log_message_raw(("bold",
 
 
697
                                             "s: Start new checker",
 
643
704
                if self.topwidget.get_focus() is self.logbox:
 
644
705
                    self.topwidget.set_focus(0)
 
646
707
                    self.topwidget.set_focus(self.logbox)
 
648
 
            #elif (key == u"end" or key == u"meta >" or key == u"G"
 
 
709
            #elif (key == "end" or key == "meta >" or key == "G"
 
650
711
            #    pass            # xxx end-of-buffer
 
651
 
            #elif (key == u"home" or key == u"meta <" or key == u"g"
 
 
712
            #elif (key == "home" or key == "meta <" or key == "g"
 
653
714
            #    pass            # xxx beginning-of-buffer
 
654
 
            #elif key == u"ctrl e" or key == u"$":
 
 
715
            #elif key == "ctrl e" or key == "$":
 
655
716
            #    pass            # xxx move-end-of-line
 
656
 
            #elif key == u"ctrl a" or key == u"^":
 
 
717
            #elif key == "ctrl a" or key == "^":
 
657
718
            #    pass            # xxx move-beginning-of-line
 
658
 
            #elif key == u"ctrl b" or key == u"meta (" or key == u"h":
 
 
719
            #elif key == "ctrl b" or key == "meta (" or key == "h":
 
659
720
            #    pass            # xxx left
 
660
 
            #elif key == u"ctrl f" or key == u"meta )" or key == u"l":
 
 
721
            #elif key == "ctrl f" or key == "meta )" or key == "l":
 
661
722
            #    pass            # xxx right
 
663
724
            #    pass            # scroll up log
 
665
726
            #    pass            # scroll down log
 
666
727
            elif self.topwidget.selectable():
 
667
728
                self.topwidget.keypress(self.size, key)