/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2016-02-21 13:00:15 UTC
  • Revision ID: teddy@recompile.se-20160221130015-b32z5wnkw9swjg2s
mandos: Remove unused import and an errant space character.

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
# "AvahiService" class, and some lines in "main".
12
12
13
13
# Everything else is
14
 
# Copyright © 2008-2014 Teddy Hogeborn
15
 
# Copyright © 2008-2014 Björn Påhlsson
 
14
# Copyright © 2008-2015 Teddy Hogeborn
 
15
# Copyright © 2008-2015 Björn Påhlsson
16
16
17
17
# This program is free software: you can redistribute it and/or modify
18
18
# it under the terms of the GNU General Public License as published by
36
36
 
37
37
from future_builtins import *
38
38
 
39
 
import SocketServer as socketserver
 
39
try:
 
40
    import SocketServer as socketserver
 
41
except ImportError:
 
42
    import socketserver
40
43
import socket
41
44
import argparse
42
45
import datetime
43
46
import errno
44
 
import gnutls.crypto
45
47
import gnutls.connection
46
48
import gnutls.errors
47
49
import gnutls.library.functions
48
50
import gnutls.library.constants
49
51
import gnutls.library.types
50
 
import ConfigParser as configparser
 
52
try:
 
53
    import ConfigParser as configparser
 
54
except ImportError:
 
55
    import configparser
51
56
import sys
52
57
import re
53
58
import os
62
67
import struct
63
68
import fcntl
64
69
import functools
65
 
import cPickle as pickle
 
70
try:
 
71
    import cPickle as pickle
 
72
except ImportError:
 
73
    import pickle
66
74
import multiprocessing
67
75
import types
68
76
import binascii
69
77
import tempfile
70
78
import itertools
71
79
import collections
 
80
import codecs
72
81
 
73
82
import dbus
74
83
import dbus.service
75
 
import gobject
 
84
try:
 
85
    import gobject
 
86
except ImportError:
 
87
    from gi.repository import GObject as gobject
76
88
import avahi
77
89
from dbus.mainloop.glib import DBusGMainLoop
78
90
import ctypes
88
100
    except ImportError:
89
101
        SO_BINDTODEVICE = None
90
102
 
91
 
version = "1.6.8"
 
103
if sys.version_info.major == 2:
 
104
    str = unicode
 
105
 
 
106
version = "1.7.1"
92
107
stored_state_file = "clients.pickle"
93
108
 
94
109
logger = logging.getLogger()
95
110
syslogger = None
96
111
 
97
112
try:
98
 
    if_nametoindex = (ctypes.cdll.LoadLibrary
99
 
                      (ctypes.util.find_library("c"))
100
 
                      .if_nametoindex)
 
113
    if_nametoindex = ctypes.cdll.LoadLibrary(
 
114
        ctypes.util.find_library("c")).if_nametoindex
101
115
except (OSError, AttributeError):
 
116
    
102
117
    def if_nametoindex(interface):
103
118
        "Get an interface index the hard way, i.e. using fcntl()"
104
119
        SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
105
120
        with contextlib.closing(socket.socket()) as s:
106
121
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
107
 
                                struct.pack(str("16s16x"),
108
 
                                            interface))
109
 
        interface_index = struct.unpack(str("I"),
110
 
                                        ifreq[16:20])[0]
 
122
                                struct.pack(b"16s16x", interface))
 
123
        interface_index = struct.unpack("I", ifreq[16:20])[0]
111
124
        return interface_index
112
125
 
113
126
 
115
128
    """init logger and add loglevel"""
116
129
    
117
130
    global syslogger
118
 
    syslogger = (logging.handlers.SysLogHandler
119
 
                 (facility =
120
 
                  logging.handlers.SysLogHandler.LOG_DAEMON,
121
 
                  address = str("/dev/log")))
 
131
    syslogger = (logging.handlers.SysLogHandler(
 
132
        facility = logging.handlers.SysLogHandler.LOG_DAEMON,
 
133
        address = "/dev/log"))
122
134
    syslogger.setFormatter(logging.Formatter
123
135
                           ('Mandos [%(process)d]: %(levelname)s:'
124
136
                            ' %(message)s'))
141
153
 
142
154
class PGPEngine(object):
143
155
    """A simple class for OpenPGP symmetric encryption & decryption"""
 
156
    
144
157
    def __init__(self):
145
158
        self.tempdir = tempfile.mkdtemp(prefix="mandos-")
146
159
        self.gnupgargs = ['--batch',
185
198
    
186
199
    def encrypt(self, data, password):
187
200
        passphrase = self.password_encode(password)
188
 
        with tempfile.NamedTemporaryFile(dir=self.tempdir
189
 
                                         ) as passfile:
 
201
        with tempfile.NamedTemporaryFile(
 
202
                dir=self.tempdir) as passfile:
190
203
            passfile.write(passphrase)
191
204
            passfile.flush()
192
205
            proc = subprocess.Popen(['gpg', '--symmetric',
203
216
    
204
217
    def decrypt(self, data, password):
205
218
        passphrase = self.password_encode(password)
206
 
        with tempfile.NamedTemporaryFile(dir = self.tempdir
207
 
                                         ) as passfile:
 
219
        with tempfile.NamedTemporaryFile(
 
220
                dir = self.tempdir) as passfile:
208
221
            passfile.write(passphrase)
209
222
            passfile.flush()
210
223
            proc = subprocess.Popen(['gpg', '--decrypt',
214
227
                                    stdin = subprocess.PIPE,
215
228
                                    stdout = subprocess.PIPE,
216
229
                                    stderr = subprocess.PIPE)
217
 
            decrypted_plaintext, err = proc.communicate(input
218
 
                                                        = data)
 
230
            decrypted_plaintext, err = proc.communicate(input = data)
219
231
        if proc.returncode != 0:
220
232
            raise PGPError(err)
221
233
        return decrypted_plaintext
224
236
class AvahiError(Exception):
225
237
    def __init__(self, value, *args, **kwargs):
226
238
        self.value = value
227
 
        super(AvahiError, self).__init__(value, *args, **kwargs)
228
 
    def __unicode__(self):
229
 
        return unicode(repr(self.value))
 
239
        return super(AvahiError, self).__init__(value, *args,
 
240
                                                **kwargs)
 
241
 
230
242
 
231
243
class AvahiServiceError(AvahiError):
232
244
    pass
233
245
 
 
246
 
234
247
class AvahiGroupError(AvahiError):
235
248
    pass
236
249
 
256
269
    bus: dbus.SystemBus()
257
270
    """
258
271
    
259
 
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
260
 
                 servicetype = None, port = None, TXT = None,
261
 
                 domain = "", host = "", max_renames = 32768,
262
 
                 protocol = avahi.PROTO_UNSPEC, bus = None):
 
272
    def __init__(self,
 
273
                 interface = avahi.IF_UNSPEC,
 
274
                 name = None,
 
275
                 servicetype = None,
 
276
                 port = None,
 
277
                 TXT = None,
 
278
                 domain = "",
 
279
                 host = "",
 
280
                 max_renames = 32768,
 
281
                 protocol = avahi.PROTO_UNSPEC,
 
282
                 bus = None):
263
283
        self.interface = interface
264
284
        self.name = name
265
285
        self.type = servicetype
275
295
        self.bus = bus
276
296
        self.entry_group_state_changed_match = None
277
297
    
278
 
    def rename(self):
 
298
    def rename(self, remove=True):
279
299
        """Derived from the Avahi example code"""
280
300
        if self.rename_count >= self.max_renames:
281
301
            logger.critical("No suitable Zeroconf service name found"
282
302
                            " after %i retries, exiting.",
283
303
                            self.rename_count)
284
304
            raise AvahiServiceError("Too many renames")
285
 
        self.name = unicode(self.server
286
 
                            .GetAlternativeServiceName(self.name))
 
305
        self.name = str(
 
306
            self.server.GetAlternativeServiceName(self.name))
 
307
        self.rename_count += 1
287
308
        logger.info("Changing Zeroconf service name to %r ...",
288
309
                    self.name)
289
 
        self.remove()
 
310
        if remove:
 
311
            self.remove()
290
312
        try:
291
313
            self.add()
292
314
        except dbus.exceptions.DBusException as error:
293
 
            logger.critical("D-Bus Exception", exc_info=error)
294
 
            self.cleanup()
295
 
            os._exit(1)
296
 
        self.rename_count += 1
 
315
            if (error.get_dbus_name()
 
316
                == "org.freedesktop.Avahi.CollisionError"):
 
317
                logger.info("Local Zeroconf service name collision.")
 
318
                return self.rename(remove=False)
 
319
            else:
 
320
                logger.critical("D-Bus Exception", exc_info=error)
 
321
                self.cleanup()
 
322
                os._exit(1)
297
323
    
298
324
    def remove(self):
299
325
        """Derived from the Avahi example code"""
337
363
            self.rename()
338
364
        elif state == avahi.ENTRY_GROUP_FAILURE:
339
365
            logger.critical("Avahi: Error in group state changed %s",
340
 
                            unicode(error))
341
 
            raise AvahiGroupError("State changed: {!s}"
342
 
                                  .format(error))
 
366
                            str(error))
 
367
            raise AvahiGroupError("State changed: {!s}".format(error))
343
368
    
344
369
    def cleanup(self):
345
370
        """Derived from the Avahi example code"""
355
380
    def server_state_changed(self, state, error=None):
356
381
        """Derived from the Avahi example code"""
357
382
        logger.debug("Avahi server state change: %i", state)
358
 
        bad_states = { avahi.SERVER_INVALID:
359
 
                           "Zeroconf server invalid",
360
 
                       avahi.SERVER_REGISTERING: None,
361
 
                       avahi.SERVER_COLLISION:
362
 
                           "Zeroconf server name collision",
363
 
                       avahi.SERVER_FAILURE:
364
 
                           "Zeroconf server failure" }
 
383
        bad_states = {
 
384
            avahi.SERVER_INVALID: "Zeroconf server invalid",
 
385
            avahi.SERVER_REGISTERING: None,
 
386
            avahi.SERVER_COLLISION: "Zeroconf server name collision",
 
387
            avahi.SERVER_FAILURE: "Zeroconf server failure",
 
388
        }
365
389
        if state in bad_states:
366
390
            if bad_states[state] is not None:
367
391
                if error is None:
370
394
                    logger.error(bad_states[state] + ": %r", error)
371
395
            self.cleanup()
372
396
        elif state == avahi.SERVER_RUNNING:
373
 
            self.add()
 
397
            try:
 
398
                self.add()
 
399
            except dbus.exceptions.DBusException as error:
 
400
                if (error.get_dbus_name()
 
401
                    == "org.freedesktop.Avahi.CollisionError"):
 
402
                    logger.info("Local Zeroconf service name"
 
403
                                " collision.")
 
404
                    return self.rename(remove=False)
 
405
                else:
 
406
                    logger.critical("D-Bus Exception", exc_info=error)
 
407
                    self.cleanup()
 
408
                    os._exit(1)
374
409
        else:
375
410
            if error is None:
376
411
                logger.debug("Unknown state: %r", state)
386
421
                                    follow_name_owner_changes=True),
387
422
                avahi.DBUS_INTERFACE_SERVER)
388
423
        self.server.connect_to_signal("StateChanged",
389
 
                                 self.server_state_changed)
 
424
                                      self.server_state_changed)
390
425
        self.server_state_changed(self.server.GetState())
391
426
 
392
427
 
393
428
class AvahiServiceToSyslog(AvahiService):
394
 
    def rename(self):
 
429
    def rename(self, *args, **kwargs):
395
430
        """Add the new name to the syslog messages"""
396
 
        ret = AvahiService.rename(self)
397
 
        syslogger.setFormatter(logging.Formatter
398
 
                               ('Mandos ({}) [%(process)d]:'
399
 
                                ' %(levelname)s: %(message)s'
400
 
                                .format(self.name)))
 
431
        ret = AvahiService.rename(self, *args, **kwargs)
 
432
        syslogger.setFormatter(logging.Formatter(
 
433
            'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
 
434
            .format(self.name)))
401
435
        return ret
402
436
 
 
437
def call_pipe(connection,       # : multiprocessing.Connection
 
438
              func, *args, **kwargs):
 
439
    """This function is meant to be called by multiprocessing.Process
 
440
    
 
441
    This function runs func(*args, **kwargs), and writes the resulting
 
442
    return value on the provided multiprocessing.Connection.
 
443
    """
 
444
    connection.send(func(*args, **kwargs))
 
445
    connection.close()
403
446
 
404
447
class Client(object):
405
448
    """A representation of a client host served by this server.
432
475
    last_checker_status: integer between 0 and 255 reflecting exit
433
476
                         status of last checker. -1 reflects crashed
434
477
                         checker, -2 means no checker completed yet.
 
478
    last_checker_signal: The signal which killed the last checker, if
 
479
                         last_checker_status is -1
435
480
    last_enabled: datetime.datetime(); (UTC) or None
436
481
    name:       string; from the config file, used in log messages and
437
482
                        D-Bus identifiers
450
495
                          "fingerprint", "host", "interval",
451
496
                          "last_approval_request", "last_checked_ok",
452
497
                          "last_enabled", "name", "timeout")
453
 
    client_defaults = { "timeout": "PT5M",
454
 
                        "extended_timeout": "PT15M",
455
 
                        "interval": "PT2M",
456
 
                        "checker": "fping -q -- %%(host)s",
457
 
                        "host": "",
458
 
                        "approval_delay": "PT0S",
459
 
                        "approval_duration": "PT1S",
460
 
                        "approved_by_default": "True",
461
 
                        "enabled": "True",
462
 
                        }
 
498
    client_defaults = {
 
499
        "timeout": "PT5M",
 
500
        "extended_timeout": "PT15M",
 
501
        "interval": "PT2M",
 
502
        "checker": "fping -q -- %%(host)s",
 
503
        "host": "",
 
504
        "approval_delay": "PT0S",
 
505
        "approval_duration": "PT1S",
 
506
        "approved_by_default": "True",
 
507
        "enabled": "True",
 
508
    }
463
509
    
464
510
    @staticmethod
465
511
    def config_parser(config):
481
527
            client["enabled"] = config.getboolean(client_name,
482
528
                                                  "enabled")
483
529
            
 
530
            # Uppercase and remove spaces from fingerprint for later
 
531
            # comparison purposes with return value from the
 
532
            # fingerprint() function
484
533
            client["fingerprint"] = (section["fingerprint"].upper()
485
534
                                     .replace(" ", ""))
486
535
            if "secret" in section:
528
577
            self.expires = None
529
578
        
530
579
        logger.debug("Creating client %r", self.name)
531
 
        # Uppercase and remove spaces from fingerprint for later
532
 
        # comparison purposes with return value from the fingerprint()
533
 
        # function
534
580
        logger.debug("  Fingerprint: %s", self.fingerprint)
535
581
        self.created = settings.get("created",
536
582
                                    datetime.datetime.utcnow())
543
589
        self.current_checker_command = None
544
590
        self.approved = None
545
591
        self.approvals_pending = 0
546
 
        self.changedstate = (multiprocessing_manager
547
 
                             .Condition(multiprocessing_manager
548
 
                                        .Lock()))
549
 
        self.client_structure = [attr for attr in
550
 
                                 self.__dict__.iterkeys()
 
592
        self.changedstate = multiprocessing_manager.Condition(
 
593
            multiprocessing_manager.Lock())
 
594
        self.client_structure = [attr
 
595
                                 for attr in self.__dict__.iterkeys()
551
596
                                 if not attr.startswith("_")]
552
597
        self.client_structure.append("client_structure")
553
598
        
554
 
        for name, t in inspect.getmembers(type(self),
555
 
                                          lambda obj:
556
 
                                              isinstance(obj,
557
 
                                                         property)):
 
599
        for name, t in inspect.getmembers(
 
600
                type(self), lambda obj: isinstance(obj, property)):
558
601
            if not name.startswith("_"):
559
602
                self.client_structure.append(name)
560
603
    
602
645
        # and every interval from then on.
603
646
        if self.checker_initiator_tag is not None:
604
647
            gobject.source_remove(self.checker_initiator_tag)
605
 
        self.checker_initiator_tag = (gobject.timeout_add
606
 
                                      (int(self.interval
607
 
                                           .total_seconds() * 1000),
608
 
                                       self.start_checker))
 
648
        self.checker_initiator_tag = gobject.timeout_add(
 
649
            int(self.interval.total_seconds() * 1000),
 
650
            self.start_checker)
609
651
        # Schedule a disable() when 'timeout' has passed
610
652
        if self.disable_initiator_tag is not None:
611
653
            gobject.source_remove(self.disable_initiator_tag)
612
 
        self.disable_initiator_tag = (gobject.timeout_add
613
 
                                      (int(self.timeout
614
 
                                           .total_seconds() * 1000),
615
 
                                       self.disable))
 
654
        self.disable_initiator_tag = gobject.timeout_add(
 
655
            int(self.timeout.total_seconds() * 1000), self.disable)
616
656
        # Also start a new checker *right now*.
617
657
        self.start_checker()
618
658
    
619
 
    def checker_callback(self, pid, condition, command):
 
659
    def checker_callback(self, source, condition, connection,
 
660
                         command):
620
661
        """The checker has completed, so take appropriate actions."""
621
662
        self.checker_callback_tag = None
622
663
        self.checker = None
623
 
        if os.WIFEXITED(condition):
624
 
            self.last_checker_status = os.WEXITSTATUS(condition)
 
664
        # Read return code from connection (see call_pipe)
 
665
        returncode = connection.recv()
 
666
        connection.close()
 
667
        
 
668
        if returncode >= 0:
 
669
            self.last_checker_status = returncode
 
670
            self.last_checker_signal = None
625
671
            if self.last_checker_status == 0:
626
672
                logger.info("Checker for %(name)s succeeded",
627
673
                            vars(self))
628
674
                self.checked_ok()
629
675
            else:
630
 
                logger.info("Checker for %(name)s failed",
631
 
                            vars(self))
 
676
                logger.info("Checker for %(name)s failed", vars(self))
632
677
        else:
633
678
            self.last_checker_status = -1
 
679
            self.last_checker_signal = -returncode
634
680
            logger.warning("Checker for %(name)s crashed?",
635
681
                           vars(self))
 
682
        return False
636
683
    
637
684
    def checked_ok(self):
638
685
        """Assert that the client has been seen, alive and well."""
639
686
        self.last_checked_ok = datetime.datetime.utcnow()
640
687
        self.last_checker_status = 0
 
688
        self.last_checker_signal = None
641
689
        self.bump_timeout()
642
690
    
643
691
    def bump_timeout(self, timeout=None):
648
696
            gobject.source_remove(self.disable_initiator_tag)
649
697
            self.disable_initiator_tag = None
650
698
        if getattr(self, "enabled", False):
651
 
            self.disable_initiator_tag = (gobject.timeout_add
652
 
                                          (int(timeout.total_seconds()
653
 
                                               * 1000), self.disable))
 
699
            self.disable_initiator_tag = gobject.timeout_add(
 
700
                int(timeout.total_seconds() * 1000), self.disable)
654
701
            self.expires = datetime.datetime.utcnow() + timeout
655
702
    
656
703
    def need_approval(self):
670
717
        # than 'timeout' for the client to be disabled, which is as it
671
718
        # should be.
672
719
        
673
 
        # If a checker exists, make sure it is not a zombie
674
 
        try:
675
 
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
676
 
        except AttributeError:
677
 
            pass
678
 
        except OSError as error:
679
 
            if error.errno != errno.ECHILD:
680
 
                raise
681
 
        else:
682
 
            if pid:
683
 
                logger.warning("Checker was a zombie")
684
 
                gobject.source_remove(self.checker_callback_tag)
685
 
                self.checker_callback(pid, status,
686
 
                                      self.current_checker_command)
 
720
        if self.checker is not None and not self.checker.is_alive():
 
721
            logger.warning("Checker was not alive; joining")
 
722
            self.checker.join()
 
723
            self.checker = None
687
724
        # Start a new checker if needed
688
725
        if self.checker is None:
689
726
            # Escape attributes for the shell
690
 
            escaped_attrs = { attr:
691
 
                                  re.escape(unicode(getattr(self,
692
 
                                                            attr)))
693
 
                              for attr in self.runtime_expansions }
 
727
            escaped_attrs = {
 
728
                attr: re.escape(str(getattr(self, attr)))
 
729
                for attr in self.runtime_expansions }
694
730
            try:
695
731
                command = self.checker_command % escaped_attrs
696
732
            except TypeError as error:
697
733
                logger.error('Could not format string "%s"',
698
 
                             self.checker_command, exc_info=error)
699
 
                return True # Try again later
 
734
                             self.checker_command,
 
735
                             exc_info=error)
 
736
                return True     # Try again later
700
737
            self.current_checker_command = command
701
 
            try:
702
 
                logger.info("Starting checker %r for %s",
703
 
                            command, self.name)
704
 
                # We don't need to redirect stdout and stderr, since
705
 
                # in normal mode, that is already done by daemon(),
706
 
                # and in debug mode we don't want to.  (Stdin is
707
 
                # always replaced by /dev/null.)
708
 
                # The exception is when not debugging but nevertheless
709
 
                # running in the foreground; use the previously
710
 
                # created wnull.
711
 
                popen_args = {}
712
 
                if (not self.server_settings["debug"]
713
 
                    and self.server_settings["foreground"]):
714
 
                    popen_args.update({"stdout": wnull,
715
 
                                       "stderr": wnull })
716
 
                self.checker = subprocess.Popen(command,
717
 
                                                close_fds=True,
718
 
                                                shell=True, cwd="/",
719
 
                                                **popen_args)
720
 
            except OSError as error:
721
 
                logger.error("Failed to start subprocess",
722
 
                             exc_info=error)
723
 
                return True
724
 
            self.checker_callback_tag = (gobject.child_watch_add
725
 
                                         (self.checker.pid,
726
 
                                          self.checker_callback,
727
 
                                          data=command))
728
 
            # The checker may have completed before the gobject
729
 
            # watch was added.  Check for this.
730
 
            try:
731
 
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
732
 
            except OSError as error:
733
 
                if error.errno == errno.ECHILD:
734
 
                    # This should never happen
735
 
                    logger.error("Child process vanished",
736
 
                                 exc_info=error)
737
 
                    return True
738
 
                raise
739
 
            if pid:
740
 
                gobject.source_remove(self.checker_callback_tag)
741
 
                self.checker_callback(pid, status, command)
 
738
            logger.info("Starting checker %r for %s", command,
 
739
                        self.name)
 
740
            # We don't need to redirect stdout and stderr, since
 
741
            # in normal mode, that is already done by daemon(),
 
742
            # and in debug mode we don't want to.  (Stdin is
 
743
            # always replaced by /dev/null.)
 
744
            # The exception is when not debugging but nevertheless
 
745
            # running in the foreground; use the previously
 
746
            # created wnull.
 
747
            popen_args = { "close_fds": True,
 
748
                           "shell": True,
 
749
                           "cwd": "/" }
 
750
            if (not self.server_settings["debug"]
 
751
                and self.server_settings["foreground"]):
 
752
                popen_args.update({"stdout": wnull,
 
753
                                   "stderr": wnull })
 
754
            pipe = multiprocessing.Pipe(duplex = False)
 
755
            self.checker = multiprocessing.Process(
 
756
                target = call_pipe,
 
757
                args = (pipe[1], subprocess.call, command),
 
758
                kwargs = popen_args)
 
759
            self.checker.start()
 
760
            self.checker_callback_tag = gobject.io_add_watch(
 
761
                pipe[0].fileno(), gobject.IO_IN,
 
762
                self.checker_callback, pipe[0], command)
742
763
        # Re-run this periodically if run by gobject.timeout_add
743
764
        return True
744
765
    
750
771
        if getattr(self, "checker", None) is None:
751
772
            return
752
773
        logger.debug("Stopping checker for %(name)s", vars(self))
753
 
        try:
754
 
            self.checker.terminate()
755
 
            #time.sleep(0.5)
756
 
            #if self.checker.poll() is None:
757
 
            #    self.checker.kill()
758
 
        except OSError as error:
759
 
            if error.errno != errno.ESRCH: # No such process
760
 
                raise
 
774
        self.checker.terminate()
761
775
        self.checker = None
762
776
 
763
777
 
764
 
def dbus_service_property(dbus_interface, signature="v",
765
 
                          access="readwrite", byte_arrays=False):
 
778
def dbus_service_property(dbus_interface,
 
779
                          signature="v",
 
780
                          access="readwrite",
 
781
                          byte_arrays=False):
766
782
    """Decorators for marking methods of a DBusObjectWithProperties to
767
783
    become properties on the D-Bus.
768
784
    
778
794
    if byte_arrays and signature != "ay":
779
795
        raise ValueError("Byte arrays not supported for non-'ay'"
780
796
                         " signature {!r}".format(signature))
 
797
    
781
798
    def decorator(func):
782
799
        func._dbus_is_property = True
783
800
        func._dbus_interface = dbus_interface
788
805
            func._dbus_name = func._dbus_name[:-14]
789
806
        func._dbus_get_args_options = {'byte_arrays': byte_arrays }
790
807
        return func
 
808
    
791
809
    return decorator
792
810
 
793
811
 
802
820
                "org.freedesktop.DBus.Property.EmitsChangedSignal":
803
821
                    "false"}
804
822
    """
 
823
    
805
824
    def decorator(func):
806
825
        func._dbus_is_interface = True
807
826
        func._dbus_interface = dbus_interface
808
827
        func._dbus_name = dbus_interface
809
828
        return func
 
829
    
810
830
    return decorator
811
831
 
812
832
 
814
834
    """Decorator to annotate D-Bus methods, signals or properties
815
835
    Usage:
816
836
    
 
837
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
 
838
                       "org.freedesktop.DBus.Property."
 
839
                       "EmitsChangedSignal": "false"})
817
840
    @dbus_service_property("org.example.Interface", signature="b",
818
841
                           access="r")
819
 
    @dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
820
 
                        "org.freedesktop.DBus.Property."
821
 
                        "EmitsChangedSignal": "false"})
822
842
    def Property_dbus_property(self):
823
843
        return dbus.Boolean(False)
 
844
    
 
845
    See also the DBusObjectWithAnnotations class.
824
846
    """
 
847
    
825
848
    def decorator(func):
826
849
        func._dbus_annotations = annotations
827
850
        return func
 
851
    
828
852
    return decorator
829
853
 
830
854
 
831
855
class DBusPropertyException(dbus.exceptions.DBusException):
832
856
    """A base class for D-Bus property-related exceptions
833
857
    """
834
 
    def __unicode__(self):
835
 
        return unicode(str(self))
 
858
    pass
836
859
 
837
860
 
838
861
class DBusPropertyAccessException(DBusPropertyException):
847
870
    pass
848
871
 
849
872
 
850
 
class DBusObjectWithProperties(dbus.service.Object):
851
 
    """A D-Bus object with properties.
 
873
class DBusObjectWithAnnotations(dbus.service.Object):
 
874
    """A D-Bus object with annotations.
852
875
    
853
 
    Classes inheriting from this can use the dbus_service_property
854
 
    decorator to expose methods as D-Bus properties.  It exposes the
855
 
    standard Get(), Set(), and GetAll() methods on the D-Bus.
 
876
    Classes inheriting from this can use the dbus_annotations
 
877
    decorator to add annotations to methods or signals.
856
878
    """
857
879
    
858
880
    @staticmethod
868
890
    def _get_all_dbus_things(self, thing):
869
891
        """Returns a generator of (name, attribute) pairs
870
892
        """
871
 
        return ((getattr(athing.__get__(self), "_dbus_name",
872
 
                         name),
 
893
        return ((getattr(athing.__get__(self), "_dbus_name", name),
873
894
                 athing.__get__(self))
874
895
                for cls in self.__class__.__mro__
875
896
                for name, athing in
876
 
                inspect.getmembers(cls,
877
 
                                   self._is_dbus_thing(thing)))
 
897
                inspect.getmembers(cls, self._is_dbus_thing(thing)))
 
898
    
 
899
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
 
900
                         out_signature = "s",
 
901
                         path_keyword = 'object_path',
 
902
                         connection_keyword = 'connection')
 
903
    def Introspect(self, object_path, connection):
 
904
        """Overloading of standard D-Bus method.
 
905
        
 
906
        Inserts annotation tags on methods and signals.
 
907
        """
 
908
        xmlstring = dbus.service.Object.Introspect(self, object_path,
 
909
                                                   connection)
 
910
        try:
 
911
            document = xml.dom.minidom.parseString(xmlstring)
 
912
            
 
913
            for if_tag in document.getElementsByTagName("interface"):
 
914
                # Add annotation tags
 
915
                for typ in ("method", "signal"):
 
916
                    for tag in if_tag.getElementsByTagName(typ):
 
917
                        annots = dict()
 
918
                        for name, prop in (self.
 
919
                                           _get_all_dbus_things(typ)):
 
920
                            if (name == tag.getAttribute("name")
 
921
                                and prop._dbus_interface
 
922
                                == if_tag.getAttribute("name")):
 
923
                                annots.update(getattr(
 
924
                                    prop, "_dbus_annotations", {}))
 
925
                        for name, value in annots.items():
 
926
                            ann_tag = document.createElement(
 
927
                                "annotation")
 
928
                            ann_tag.setAttribute("name", name)
 
929
                            ann_tag.setAttribute("value", value)
 
930
                            tag.appendChild(ann_tag)
 
931
                # Add interface annotation tags
 
932
                for annotation, value in dict(
 
933
                    itertools.chain.from_iterable(
 
934
                        annotations().items()
 
935
                        for name, annotations
 
936
                        in self._get_all_dbus_things("interface")
 
937
                        if name == if_tag.getAttribute("name")
 
938
                        )).items():
 
939
                    ann_tag = document.createElement("annotation")
 
940
                    ann_tag.setAttribute("name", annotation)
 
941
                    ann_tag.setAttribute("value", value)
 
942
                    if_tag.appendChild(ann_tag)
 
943
                # Fix argument name for the Introspect method itself
 
944
                if (if_tag.getAttribute("name")
 
945
                                == dbus.INTROSPECTABLE_IFACE):
 
946
                    for cn in if_tag.getElementsByTagName("method"):
 
947
                        if cn.getAttribute("name") == "Introspect":
 
948
                            for arg in cn.getElementsByTagName("arg"):
 
949
                                if (arg.getAttribute("direction")
 
950
                                    == "out"):
 
951
                                    arg.setAttribute("name",
 
952
                                                     "xml_data")
 
953
            xmlstring = document.toxml("utf-8")
 
954
            document.unlink()
 
955
        except (AttributeError, xml.dom.DOMException,
 
956
                xml.parsers.expat.ExpatError) as error:
 
957
            logger.error("Failed to override Introspection method",
 
958
                         exc_info=error)
 
959
        return xmlstring
 
960
 
 
961
 
 
962
class DBusObjectWithProperties(DBusObjectWithAnnotations):
 
963
    """A D-Bus object with properties.
 
964
    
 
965
    Classes inheriting from this can use the dbus_service_property
 
966
    decorator to expose methods as D-Bus properties.  It exposes the
 
967
    standard Get(), Set(), and GetAll() methods on the D-Bus.
 
968
    """
878
969
    
879
970
    def _get_dbus_property(self, interface_name, property_name):
880
971
        """Returns a bound method if one exists which is a D-Bus
881
972
        property with the specified name and interface.
882
973
        """
883
 
        for cls in  self.__class__.__mro__:
884
 
            for name, value in (inspect.getmembers
885
 
                                (cls,
886
 
                                 self._is_dbus_thing("property"))):
 
974
        for cls in self.__class__.__mro__:
 
975
            for name, value in inspect.getmembers(
 
976
                    cls, self._is_dbus_thing("property")):
887
977
                if (value._dbus_name == property_name
888
978
                    and value._dbus_interface == interface_name):
889
979
                    return value.__get__(self)
890
980
        
891
981
        # No such property
892
 
        raise DBusPropertyNotFound(self.dbus_object_path + ":"
893
 
                                   + interface_name + "."
894
 
                                   + property_name)
895
 
    
896
 
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
 
982
        raise DBusPropertyNotFound("{}:{}.{}".format(
 
983
            self.dbus_object_path, interface_name, property_name))
 
984
    
 
985
    @classmethod
 
986
    def _get_all_interface_names(cls):
 
987
        """Get a sequence of all interfaces supported by an object"""
 
988
        return (name for name in set(getattr(getattr(x, attr),
 
989
                                             "_dbus_interface", None)
 
990
                                     for x in (inspect.getmro(cls))
 
991
                                     for attr in dir(x))
 
992
                if name is not None)
 
993
    
 
994
    @dbus.service.method(dbus.PROPERTIES_IFACE,
 
995
                         in_signature="ss",
897
996
                         out_signature="v")
898
997
    def Get(self, interface_name, property_name):
899
998
        """Standard D-Bus property Get() method, see D-Bus standard.
924
1023
                                            for byte in value))
925
1024
        prop(value)
926
1025
    
927
 
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
 
1026
    @dbus.service.method(dbus.PROPERTIES_IFACE,
 
1027
                         in_signature="s",
928
1028
                         out_signature="a{sv}")
929
1029
    def GetAll(self, interface_name):
930
1030
        """Standard D-Bus property GetAll() method, see D-Bus
945
1045
            if not hasattr(value, "variant_level"):
946
1046
                properties[name] = value
947
1047
                continue
948
 
            properties[name] = type(value)(value, variant_level=
949
 
                                           value.variant_level+1)
 
1048
            properties[name] = type(value)(
 
1049
                value, variant_level = value.variant_level + 1)
950
1050
        return dbus.Dictionary(properties, signature="sv")
951
1051
    
 
1052
    @dbus.service.signal(dbus.PROPERTIES_IFACE, signature="sa{sv}as")
 
1053
    def PropertiesChanged(self, interface_name, changed_properties,
 
1054
                          invalidated_properties):
 
1055
        """Standard D-Bus PropertiesChanged() signal, see D-Bus
 
1056
        standard.
 
1057
        """
 
1058
        pass
 
1059
    
952
1060
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
953
1061
                         out_signature="s",
954
1062
                         path_keyword='object_path',
958
1066
        
959
1067
        Inserts property tags and interface annotation tags.
960
1068
        """
961
 
        xmlstring = dbus.service.Object.Introspect(self, object_path,
962
 
                                                   connection)
 
1069
        xmlstring = DBusObjectWithAnnotations.Introspect(self,
 
1070
                                                         object_path,
 
1071
                                                         connection)
963
1072
        try:
964
1073
            document = xml.dom.minidom.parseString(xmlstring)
 
1074
            
965
1075
            def make_tag(document, name, prop):
966
1076
                e = document.createElement("property")
967
1077
                e.setAttribute("name", name)
968
1078
                e.setAttribute("type", prop._dbus_signature)
969
1079
                e.setAttribute("access", prop._dbus_access)
970
1080
                return e
 
1081
            
971
1082
            for if_tag in document.getElementsByTagName("interface"):
972
1083
                # Add property tags
973
1084
                for tag in (make_tag(document, name, prop)
976
1087
                            if prop._dbus_interface
977
1088
                            == if_tag.getAttribute("name")):
978
1089
                    if_tag.appendChild(tag)
979
 
                # Add annotation tags
980
 
                for typ in ("method", "signal", "property"):
981
 
                    for tag in if_tag.getElementsByTagName(typ):
982
 
                        annots = dict()
983
 
                        for name, prop in (self.
984
 
                                           _get_all_dbus_things(typ)):
985
 
                            if (name == tag.getAttribute("name")
986
 
                                and prop._dbus_interface
987
 
                                == if_tag.getAttribute("name")):
988
 
                                annots.update(getattr
989
 
                                              (prop,
990
 
                                               "_dbus_annotations",
991
 
                                               {}))
992
 
                        for name, value in annots.items():
993
 
                            ann_tag = document.createElement(
994
 
                                "annotation")
995
 
                            ann_tag.setAttribute("name", name)
996
 
                            ann_tag.setAttribute("value", value)
997
 
                            tag.appendChild(ann_tag)
998
 
                # Add interface annotation tags
999
 
                for annotation, value in dict(
1000
 
                    itertools.chain.from_iterable(
1001
 
                        annotations().items()
1002
 
                        for name, annotations in
1003
 
                        self._get_all_dbus_things("interface")
1004
 
                        if name == if_tag.getAttribute("name")
1005
 
                        )).items():
1006
 
                    ann_tag = document.createElement("annotation")
1007
 
                    ann_tag.setAttribute("name", annotation)
1008
 
                    ann_tag.setAttribute("value", value)
1009
 
                    if_tag.appendChild(ann_tag)
 
1090
                # Add annotation tags for properties
 
1091
                for tag in if_tag.getElementsByTagName("property"):
 
1092
                    annots = dict()
 
1093
                    for name, prop in self._get_all_dbus_things(
 
1094
                            "property"):
 
1095
                        if (name == tag.getAttribute("name")
 
1096
                            and prop._dbus_interface
 
1097
                            == if_tag.getAttribute("name")):
 
1098
                            annots.update(getattr(
 
1099
                                prop, "_dbus_annotations", {}))
 
1100
                    for name, value in annots.items():
 
1101
                        ann_tag = document.createElement(
 
1102
                            "annotation")
 
1103
                        ann_tag.setAttribute("name", name)
 
1104
                        ann_tag.setAttribute("value", value)
 
1105
                        tag.appendChild(ann_tag)
1010
1106
                # Add the names to the return values for the
1011
1107
                # "org.freedesktop.DBus.Properties" methods
1012
1108
                if (if_tag.getAttribute("name")
1030
1126
                         exc_info=error)
1031
1127
        return xmlstring
1032
1128
 
 
1129
try:
 
1130
    dbus.OBJECT_MANAGER_IFACE
 
1131
except AttributeError:
 
1132
    dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
 
1133
 
 
1134
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
 
1135
    """A D-Bus object with an ObjectManager.
 
1136
    
 
1137
    Classes inheriting from this exposes the standard
 
1138
    GetManagedObjects call and the InterfacesAdded and
 
1139
    InterfacesRemoved signals on the standard
 
1140
    "org.freedesktop.DBus.ObjectManager" interface.
 
1141
    
 
1142
    Note: No signals are sent automatically; they must be sent
 
1143
    manually.
 
1144
    """
 
1145
    @dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
 
1146
                         out_signature = "a{oa{sa{sv}}}")
 
1147
    def GetManagedObjects(self):
 
1148
        """This function must be overridden"""
 
1149
        raise NotImplementedError()
 
1150
    
 
1151
    @dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
 
1152
                         signature = "oa{sa{sv}}")
 
1153
    def InterfacesAdded(self, object_path, interfaces_and_properties):
 
1154
        pass
 
1155
    
 
1156
    @dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
 
1157
    def InterfacesRemoved(self, object_path, interfaces):
 
1158
        pass
 
1159
    
 
1160
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
 
1161
                         out_signature = "s",
 
1162
                         path_keyword = 'object_path',
 
1163
                         connection_keyword = 'connection')
 
1164
    def Introspect(self, object_path, connection):
 
1165
        """Overloading of standard D-Bus method.
 
1166
        
 
1167
        Override return argument name of GetManagedObjects to be
 
1168
        "objpath_interfaces_and_properties"
 
1169
        """
 
1170
        xmlstring = DBusObjectWithAnnotations.Introspect(self,
 
1171
                                                         object_path,
 
1172
                                                         connection)
 
1173
        try:
 
1174
            document = xml.dom.minidom.parseString(xmlstring)
 
1175
            
 
1176
            for if_tag in document.getElementsByTagName("interface"):
 
1177
                # Fix argument name for the GetManagedObjects method
 
1178
                if (if_tag.getAttribute("name")
 
1179
                                == dbus.OBJECT_MANAGER_IFACE):
 
1180
                    for cn in if_tag.getElementsByTagName("method"):
 
1181
                        if (cn.getAttribute("name")
 
1182
                            == "GetManagedObjects"):
 
1183
                            for arg in cn.getElementsByTagName("arg"):
 
1184
                                if (arg.getAttribute("direction")
 
1185
                                    == "out"):
 
1186
                                    arg.setAttribute(
 
1187
                                        "name",
 
1188
                                        "objpath_interfaces"
 
1189
                                        "_and_properties")
 
1190
            xmlstring = document.toxml("utf-8")
 
1191
            document.unlink()
 
1192
        except (AttributeError, xml.dom.DOMException,
 
1193
                xml.parsers.expat.ExpatError) as error:
 
1194
            logger.error("Failed to override Introspection method",
 
1195
                         exc_info = error)
 
1196
        return xmlstring
1033
1197
 
1034
1198
def datetime_to_dbus(dt, variant_level=0):
1035
1199
    """Convert a UTC datetime.datetime() to a D-Bus type."""
1036
1200
    if dt is None:
1037
1201
        return dbus.String("", variant_level = variant_level)
1038
 
    return dbus.String(dt.isoformat(),
1039
 
                       variant_level=variant_level)
 
1202
    return dbus.String(dt.isoformat(), variant_level=variant_level)
1040
1203
 
1041
1204
 
1042
1205
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
1062
1225
    (from DBusObjectWithProperties) and interfaces (from the
1063
1226
    dbus_interface_annotations decorator).
1064
1227
    """
 
1228
    
1065
1229
    def wrapper(cls):
1066
1230
        for orig_interface_name, alt_interface_name in (
1067
 
            alt_interface_names.items()):
 
1231
                alt_interface_names.items()):
1068
1232
            attr = {}
1069
1233
            interface_names = set()
1070
1234
            # Go though all attributes of the class
1072
1236
                # Ignore non-D-Bus attributes, and D-Bus attributes
1073
1237
                # with the wrong interface name
1074
1238
                if (not hasattr(attribute, "_dbus_interface")
1075
 
                    or not attribute._dbus_interface
1076
 
                    .startswith(orig_interface_name)):
 
1239
                    or not attribute._dbus_interface.startswith(
 
1240
                        orig_interface_name)):
1077
1241
                    continue
1078
1242
                # Create an alternate D-Bus interface name based on
1079
1243
                # the current name
1080
 
                alt_interface = (attribute._dbus_interface
1081
 
                                 .replace(orig_interface_name,
1082
 
                                          alt_interface_name))
 
1244
                alt_interface = attribute._dbus_interface.replace(
 
1245
                    orig_interface_name, alt_interface_name)
1083
1246
                interface_names.add(alt_interface)
1084
1247
                # Is this a D-Bus signal?
1085
1248
                if getattr(attribute, "_dbus_is_signal", False):
1086
 
                    # Extract the original non-method undecorated
1087
 
                    # function by black magic
1088
 
                    nonmethod_func = (dict(
 
1249
                    if sys.version_info.major == 2:
 
1250
                        # Extract the original non-method undecorated
 
1251
                        # function by black magic
 
1252
                        nonmethod_func = (dict(
1089
1253
                            zip(attribute.func_code.co_freevars,
1090
 
                                attribute.__closure__))["func"]
1091
 
                                      .cell_contents)
 
1254
                                attribute.__closure__))
 
1255
                                          ["func"].cell_contents)
 
1256
                    else:
 
1257
                        nonmethod_func = attribute
1092
1258
                    # Create a new, but exactly alike, function
1093
1259
                    # object, and decorate it to be a new D-Bus signal
1094
1260
                    # with the alternate D-Bus interface name
1095
 
                    new_function = (dbus.service.signal
1096
 
                                    (alt_interface,
1097
 
                                     attribute._dbus_signature)
1098
 
                                    (types.FunctionType(
1099
 
                                nonmethod_func.func_code,
1100
 
                                nonmethod_func.func_globals,
1101
 
                                nonmethod_func.func_name,
1102
 
                                nonmethod_func.func_defaults,
1103
 
                                nonmethod_func.func_closure)))
 
1261
                    if sys.version_info.major == 2:
 
1262
                        new_function = types.FunctionType(
 
1263
                            nonmethod_func.func_code,
 
1264
                            nonmethod_func.func_globals,
 
1265
                            nonmethod_func.func_name,
 
1266
                            nonmethod_func.func_defaults,
 
1267
                            nonmethod_func.func_closure)
 
1268
                    else:
 
1269
                        new_function = types.FunctionType(
 
1270
                            nonmethod_func.__code__,
 
1271
                            nonmethod_func.__globals__,
 
1272
                            nonmethod_func.__name__,
 
1273
                            nonmethod_func.__defaults__,
 
1274
                            nonmethod_func.__closure__)
 
1275
                    new_function = (dbus.service.signal(
 
1276
                        alt_interface,
 
1277
                        attribute._dbus_signature)(new_function))
1104
1278
                    # Copy annotations, if any
1105
1279
                    try:
1106
 
                        new_function._dbus_annotations = (
1107
 
                            dict(attribute._dbus_annotations))
 
1280
                        new_function._dbus_annotations = dict(
 
1281
                            attribute._dbus_annotations)
1108
1282
                    except AttributeError:
1109
1283
                        pass
1110
1284
                    # Define a creator of a function to call both the
1115
1289
                        """This function is a scope container to pass
1116
1290
                        func1 and func2 to the "call_both" function
1117
1291
                        outside of its arguments"""
 
1292
                        
 
1293
                        @functools.wraps(func2)
1118
1294
                        def call_both(*args, **kwargs):
1119
1295
                            """This function will emit two D-Bus
1120
1296
                            signals by calling func1 and func2"""
1121
1297
                            func1(*args, **kwargs)
1122
1298
                            func2(*args, **kwargs)
 
1299
                        # Make wrapper function look like a D-Bus signal
 
1300
                        for name, attr in inspect.getmembers(func2):
 
1301
                            if name.startswith("_dbus_"):
 
1302
                                setattr(call_both, name, attr)
 
1303
                        
1123
1304
                        return call_both
1124
1305
                    # Create the "call_both" function and add it to
1125
1306
                    # the class
1130
1311
                    # object.  Decorate it to be a new D-Bus method
1131
1312
                    # with the alternate D-Bus interface name.  Add it
1132
1313
                    # to the class.
1133
 
                    attr[attrname] = (dbus.service.method
1134
 
                                      (alt_interface,
1135
 
                                       attribute._dbus_in_signature,
1136
 
                                       attribute._dbus_out_signature)
1137
 
                                      (types.FunctionType
1138
 
                                       (attribute.func_code,
1139
 
                                        attribute.func_globals,
1140
 
                                        attribute.func_name,
1141
 
                                        attribute.func_defaults,
1142
 
                                        attribute.func_closure)))
 
1314
                    attr[attrname] = (
 
1315
                        dbus.service.method(
 
1316
                            alt_interface,
 
1317
                            attribute._dbus_in_signature,
 
1318
                            attribute._dbus_out_signature)
 
1319
                        (types.FunctionType(attribute.func_code,
 
1320
                                            attribute.func_globals,
 
1321
                                            attribute.func_name,
 
1322
                                            attribute.func_defaults,
 
1323
                                            attribute.func_closure)))
1143
1324
                    # Copy annotations, if any
1144
1325
                    try:
1145
 
                        attr[attrname]._dbus_annotations = (
1146
 
                            dict(attribute._dbus_annotations))
 
1326
                        attr[attrname]._dbus_annotations = dict(
 
1327
                            attribute._dbus_annotations)
1147
1328
                    except AttributeError:
1148
1329
                        pass
1149
1330
                # Is this a D-Bus property?
1152
1333
                    # object, and decorate it to be a new D-Bus
1153
1334
                    # property with the alternate D-Bus interface
1154
1335
                    # name.  Add it to the class.
1155
 
                    attr[attrname] = (dbus_service_property
1156
 
                                      (alt_interface,
1157
 
                                       attribute._dbus_signature,
1158
 
                                       attribute._dbus_access,
1159
 
                                       attribute
1160
 
                                       ._dbus_get_args_options
1161
 
                                       ["byte_arrays"])
1162
 
                                      (types.FunctionType
1163
 
                                       (attribute.func_code,
1164
 
                                        attribute.func_globals,
1165
 
                                        attribute.func_name,
1166
 
                                        attribute.func_defaults,
1167
 
                                        attribute.func_closure)))
 
1336
                    attr[attrname] = (dbus_service_property(
 
1337
                        alt_interface, attribute._dbus_signature,
 
1338
                        attribute._dbus_access,
 
1339
                        attribute._dbus_get_args_options
 
1340
                        ["byte_arrays"])
 
1341
                                      (types.FunctionType(
 
1342
                                          attribute.func_code,
 
1343
                                          attribute.func_globals,
 
1344
                                          attribute.func_name,
 
1345
                                          attribute.func_defaults,
 
1346
                                          attribute.func_closure)))
1168
1347
                    # Copy annotations, if any
1169
1348
                    try:
1170
 
                        attr[attrname]._dbus_annotations = (
1171
 
                            dict(attribute._dbus_annotations))
 
1349
                        attr[attrname]._dbus_annotations = dict(
 
1350
                            attribute._dbus_annotations)
1172
1351
                    except AttributeError:
1173
1352
                        pass
1174
1353
                # Is this a D-Bus interface?
1177
1356
                    # object.  Decorate it to be a new D-Bus interface
1178
1357
                    # with the alternate D-Bus interface name.  Add it
1179
1358
                    # to the class.
1180
 
                    attr[attrname] = (dbus_interface_annotations
1181
 
                                      (alt_interface)
1182
 
                                      (types.FunctionType
1183
 
                                       (attribute.func_code,
1184
 
                                        attribute.func_globals,
1185
 
                                        attribute.func_name,
1186
 
                                        attribute.func_defaults,
1187
 
                                        attribute.func_closure)))
 
1359
                    attr[attrname] = (
 
1360
                        dbus_interface_annotations(alt_interface)
 
1361
                        (types.FunctionType(attribute.func_code,
 
1362
                                            attribute.func_globals,
 
1363
                                            attribute.func_name,
 
1364
                                            attribute.func_defaults,
 
1365
                                            attribute.func_closure)))
1188
1366
            if deprecate:
1189
1367
                # Deprecate all alternate interfaces
1190
1368
                iname="_AlternateDBusNames_interface_annotation{}"
1191
1369
                for interface_name in interface_names:
 
1370
                    
1192
1371
                    @dbus_interface_annotations(interface_name)
1193
1372
                    def func(self):
1194
1373
                        return { "org.freedesktop.DBus.Deprecated":
1195
 
                                     "true" }
 
1374
                                 "true" }
1196
1375
                    # Find an unused name
1197
1376
                    for aname in (iname.format(i)
1198
1377
                                  for i in itertools.count()):
1203
1382
                # Replace the class with a new subclass of it with
1204
1383
                # methods, signals, etc. as created above.
1205
1384
                cls = type(b"{}Alternate".format(cls.__name__),
1206
 
                           (cls,), attr)
 
1385
                           (cls, ), attr)
1207
1386
        return cls
 
1387
    
1208
1388
    return wrapper
1209
1389
 
1210
1390
 
1211
1391
@alternate_dbus_interfaces({"se.recompile.Mandos":
1212
 
                                "se.bsnet.fukt.Mandos"})
 
1392
                            "se.bsnet.fukt.Mandos"})
1213
1393
class ClientDBus(Client, DBusObjectWithProperties):
1214
1394
    """A Client class using D-Bus
1215
1395
    
1219
1399
    """
1220
1400
    
1221
1401
    runtime_expansions = (Client.runtime_expansions
1222
 
                          + ("dbus_object_path",))
 
1402
                          + ("dbus_object_path", ))
 
1403
    
 
1404
    _interface = "se.recompile.Mandos.Client"
1223
1405
    
1224
1406
    # dbus.service.Object doesn't use super(), so we can't either.
1225
1407
    
1228
1410
        Client.__init__(self, *args, **kwargs)
1229
1411
        # Only now, when this client is initialized, can it show up on
1230
1412
        # the D-Bus
1231
 
        client_object_name = unicode(self.name).translate(
 
1413
        client_object_name = str(self.name).translate(
1232
1414
            {ord("."): ord("_"),
1233
1415
             ord("-"): ord("_")})
1234
 
        self.dbus_object_path = (dbus.ObjectPath
1235
 
                                 ("/clients/" + client_object_name))
 
1416
        self.dbus_object_path = dbus.ObjectPath(
 
1417
            "/clients/" + client_object_name)
1236
1418
        DBusObjectWithProperties.__init__(self, self.bus,
1237
1419
                                          self.dbus_object_path)
1238
1420
    
1239
 
    def notifychangeproperty(transform_func,
1240
 
                             dbus_name, type_func=lambda x: x,
1241
 
                             variant_level=1):
 
1421
    def notifychangeproperty(transform_func, dbus_name,
 
1422
                             type_func=lambda x: x,
 
1423
                             variant_level=1,
 
1424
                             invalidate_only=False,
 
1425
                             _interface=_interface):
1242
1426
        """ Modify a variable so that it's a property which announces
1243
1427
        its changes to DBus.
1244
1428
        
1250
1434
        variant_level: D-Bus variant level.  Default: 1
1251
1435
        """
1252
1436
        attrname = "_{}".format(dbus_name)
 
1437
        
1253
1438
        def setter(self, value):
1254
1439
            if hasattr(self, "dbus_object_path"):
1255
1440
                if (not hasattr(self, attrname) or
1256
1441
                    type_func(getattr(self, attrname, None))
1257
1442
                    != type_func(value)):
1258
 
                    dbus_value = transform_func(type_func(value),
1259
 
                                                variant_level
1260
 
                                                =variant_level)
1261
 
                    self.PropertyChanged(dbus.String(dbus_name),
1262
 
                                         dbus_value)
 
1443
                    if invalidate_only:
 
1444
                        self.PropertiesChanged(
 
1445
                            _interface, dbus.Dictionary(),
 
1446
                            dbus.Array((dbus_name, )))
 
1447
                    else:
 
1448
                        dbus_value = transform_func(
 
1449
                            type_func(value),
 
1450
                            variant_level = variant_level)
 
1451
                        self.PropertyChanged(dbus.String(dbus_name),
 
1452
                                             dbus_value)
 
1453
                        self.PropertiesChanged(
 
1454
                            _interface,
 
1455
                            dbus.Dictionary({ dbus.String(dbus_name):
 
1456
                                              dbus_value }),
 
1457
                            dbus.Array())
1263
1458
            setattr(self, attrname, value)
1264
1459
        
1265
1460
        return property(lambda self: getattr(self, attrname), setter)
1271
1466
    enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1272
1467
    last_enabled = notifychangeproperty(datetime_to_dbus,
1273
1468
                                        "LastEnabled")
1274
 
    checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1275
 
                                   type_func = lambda checker:
1276
 
                                       checker is not None)
 
1469
    checker = notifychangeproperty(
 
1470
        dbus.Boolean, "CheckerRunning",
 
1471
        type_func = lambda checker: checker is not None)
1277
1472
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
1278
1473
                                           "LastCheckedOK")
1279
1474
    last_checker_status = notifychangeproperty(dbus.Int16,
1282
1477
        datetime_to_dbus, "LastApprovalRequest")
1283
1478
    approved_by_default = notifychangeproperty(dbus.Boolean,
1284
1479
                                               "ApprovedByDefault")
1285
 
    approval_delay = notifychangeproperty(dbus.UInt64,
1286
 
                                          "ApprovalDelay",
1287
 
                                          type_func =
1288
 
                                          lambda td: td.total_seconds()
1289
 
                                          * 1000)
 
1480
    approval_delay = notifychangeproperty(
 
1481
        dbus.UInt64, "ApprovalDelay",
 
1482
        type_func = lambda td: td.total_seconds() * 1000)
1290
1483
    approval_duration = notifychangeproperty(
1291
1484
        dbus.UInt64, "ApprovalDuration",
1292
1485
        type_func = lambda td: td.total_seconds() * 1000)
1293
1486
    host = notifychangeproperty(dbus.String, "Host")
1294
 
    timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1295
 
                                   type_func = lambda td:
1296
 
                                       td.total_seconds() * 1000)
 
1487
    timeout = notifychangeproperty(
 
1488
        dbus.UInt64, "Timeout",
 
1489
        type_func = lambda td: td.total_seconds() * 1000)
1297
1490
    extended_timeout = notifychangeproperty(
1298
1491
        dbus.UInt64, "ExtendedTimeout",
1299
1492
        type_func = lambda td: td.total_seconds() * 1000)
1300
 
    interval = notifychangeproperty(dbus.UInt64,
1301
 
                                    "Interval",
1302
 
                                    type_func =
1303
 
                                    lambda td: td.total_seconds()
1304
 
                                    * 1000)
 
1493
    interval = notifychangeproperty(
 
1494
        dbus.UInt64, "Interval",
 
1495
        type_func = lambda td: td.total_seconds() * 1000)
1305
1496
    checker_command = notifychangeproperty(dbus.String, "Checker")
 
1497
    secret = notifychangeproperty(dbus.ByteArray, "Secret",
 
1498
                                  invalidate_only=True)
1306
1499
    
1307
1500
    del notifychangeproperty
1308
1501
    
1315
1508
            DBusObjectWithProperties.__del__(self, *args, **kwargs)
1316
1509
        Client.__del__(self, *args, **kwargs)
1317
1510
    
1318
 
    def checker_callback(self, pid, condition, command,
1319
 
                         *args, **kwargs):
1320
 
        self.checker_callback_tag = None
1321
 
        self.checker = None
1322
 
        if os.WIFEXITED(condition):
1323
 
            exitstatus = os.WEXITSTATUS(condition)
 
1511
    def checker_callback(self, source, condition,
 
1512
                         connection, command, *args, **kwargs):
 
1513
        ret = Client.checker_callback(self, source, condition,
 
1514
                                      connection, command, *args,
 
1515
                                      **kwargs)
 
1516
        exitstatus = self.last_checker_status
 
1517
        if exitstatus >= 0:
1324
1518
            # Emit D-Bus signal
1325
1519
            self.CheckerCompleted(dbus.Int16(exitstatus),
1326
 
                                  dbus.Int64(condition),
 
1520
                                  # This is specific to GNU libC
 
1521
                                  dbus.Int64(exitstatus << 8),
1327
1522
                                  dbus.String(command))
1328
1523
        else:
1329
1524
            # Emit D-Bus signal
1330
1525
            self.CheckerCompleted(dbus.Int16(-1),
1331
 
                                  dbus.Int64(condition),
 
1526
                                  dbus.Int64(
 
1527
                                      # This is specific to GNU libC
 
1528
                                      (exitstatus << 8)
 
1529
                                      | self.last_checker_signal),
1332
1530
                                  dbus.String(command))
1333
 
        
1334
 
        return Client.checker_callback(self, pid, condition, command,
1335
 
                                       *args, **kwargs)
 
1531
        return ret
1336
1532
    
1337
1533
    def start_checker(self, *args, **kwargs):
1338
1534
        old_checker_pid = getattr(self.checker, "pid", None)
1355
1551
        self.send_changedstate()
1356
1552
    
1357
1553
    ## D-Bus methods, signals & properties
1358
 
    _interface = "se.recompile.Mandos.Client"
1359
1554
    
1360
1555
    ## Interfaces
1361
1556
    
1362
 
    @dbus_interface_annotations(_interface)
1363
 
    def _foo(self):
1364
 
        return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
1365
 
                     "false"}
1366
 
    
1367
1557
    ## Signals
1368
1558
    
1369
1559
    # CheckerCompleted - signal
1379
1569
        pass
1380
1570
    
1381
1571
    # PropertyChanged - signal
 
1572
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1382
1573
    @dbus.service.signal(_interface, signature="sv")
1383
1574
    def PropertyChanged(self, property, value):
1384
1575
        "D-Bus signal"
1418
1609
        self.checked_ok()
1419
1610
    
1420
1611
    # Enable - method
 
1612
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1421
1613
    @dbus.service.method(_interface)
1422
1614
    def Enable(self):
1423
1615
        "D-Bus method"
1424
1616
        self.enable()
1425
1617
    
1426
1618
    # StartChecker - method
 
1619
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1427
1620
    @dbus.service.method(_interface)
1428
1621
    def StartChecker(self):
1429
1622
        "D-Bus method"
1430
1623
        self.start_checker()
1431
1624
    
1432
1625
    # Disable - method
 
1626
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1433
1627
    @dbus.service.method(_interface)
1434
1628
    def Disable(self):
1435
1629
        "D-Bus method"
1436
1630
        self.disable()
1437
1631
    
1438
1632
    # StopChecker - method
 
1633
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1439
1634
    @dbus.service.method(_interface)
1440
1635
    def StopChecker(self):
1441
1636
        self.stop_checker()
1448
1643
        return dbus.Boolean(bool(self.approvals_pending))
1449
1644
    
1450
1645
    # ApprovedByDefault - property
1451
 
    @dbus_service_property(_interface, signature="b",
 
1646
    @dbus_service_property(_interface,
 
1647
                           signature="b",
1452
1648
                           access="readwrite")
1453
1649
    def ApprovedByDefault_dbus_property(self, value=None):
1454
1650
        if value is None:       # get
1456
1652
        self.approved_by_default = bool(value)
1457
1653
    
1458
1654
    # ApprovalDelay - property
1459
 
    @dbus_service_property(_interface, signature="t",
 
1655
    @dbus_service_property(_interface,
 
1656
                           signature="t",
1460
1657
                           access="readwrite")
1461
1658
    def ApprovalDelay_dbus_property(self, value=None):
1462
1659
        if value is None:       # get
1465
1662
        self.approval_delay = datetime.timedelta(0, 0, 0, value)
1466
1663
    
1467
1664
    # ApprovalDuration - property
1468
 
    @dbus_service_property(_interface, signature="t",
 
1665
    @dbus_service_property(_interface,
 
1666
                           signature="t",
1469
1667
                           access="readwrite")
1470
1668
    def ApprovalDuration_dbus_property(self, value=None):
1471
1669
        if value is None:       # get
1474
1672
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
1475
1673
    
1476
1674
    # Name - property
 
1675
    @dbus_annotations(
 
1676
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1477
1677
    @dbus_service_property(_interface, signature="s", access="read")
1478
1678
    def Name_dbus_property(self):
1479
1679
        return dbus.String(self.name)
1480
1680
    
1481
1681
    # Fingerprint - property
 
1682
    @dbus_annotations(
 
1683
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1482
1684
    @dbus_service_property(_interface, signature="s", access="read")
1483
1685
    def Fingerprint_dbus_property(self):
1484
1686
        return dbus.String(self.fingerprint)
1485
1687
    
1486
1688
    # Host - property
1487
 
    @dbus_service_property(_interface, signature="s",
 
1689
    @dbus_service_property(_interface,
 
1690
                           signature="s",
1488
1691
                           access="readwrite")
1489
1692
    def Host_dbus_property(self, value=None):
1490
1693
        if value is None:       # get
1491
1694
            return dbus.String(self.host)
1492
 
        self.host = unicode(value)
 
1695
        self.host = str(value)
1493
1696
    
1494
1697
    # Created - property
 
1698
    @dbus_annotations(
 
1699
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1495
1700
    @dbus_service_property(_interface, signature="s", access="read")
1496
1701
    def Created_dbus_property(self):
1497
1702
        return datetime_to_dbus(self.created)
1502
1707
        return datetime_to_dbus(self.last_enabled)
1503
1708
    
1504
1709
    # Enabled - property
1505
 
    @dbus_service_property(_interface, signature="b",
 
1710
    @dbus_service_property(_interface,
 
1711
                           signature="b",
1506
1712
                           access="readwrite")
1507
1713
    def Enabled_dbus_property(self, value=None):
1508
1714
        if value is None:       # get
1513
1719
            self.disable()
1514
1720
    
1515
1721
    # LastCheckedOK - property
1516
 
    @dbus_service_property(_interface, signature="s",
 
1722
    @dbus_service_property(_interface,
 
1723
                           signature="s",
1517
1724
                           access="readwrite")
1518
1725
    def LastCheckedOK_dbus_property(self, value=None):
1519
1726
        if value is not None:
1522
1729
        return datetime_to_dbus(self.last_checked_ok)
1523
1730
    
1524
1731
    # LastCheckerStatus - property
1525
 
    @dbus_service_property(_interface, signature="n",
1526
 
                           access="read")
 
1732
    @dbus_service_property(_interface, signature="n", access="read")
1527
1733
    def LastCheckerStatus_dbus_property(self):
1528
1734
        return dbus.Int16(self.last_checker_status)
1529
1735
    
1538
1744
        return datetime_to_dbus(self.last_approval_request)
1539
1745
    
1540
1746
    # Timeout - property
1541
 
    @dbus_service_property(_interface, signature="t",
 
1747
    @dbus_service_property(_interface,
 
1748
                           signature="t",
1542
1749
                           access="readwrite")
1543
1750
    def Timeout_dbus_property(self, value=None):
1544
1751
        if value is None:       # get
1557
1764
                    is None):
1558
1765
                    return
1559
1766
                gobject.source_remove(self.disable_initiator_tag)
1560
 
                self.disable_initiator_tag = (
1561
 
                    gobject.timeout_add(
1562
 
                        int((self.expires - now).total_seconds()
1563
 
                            * 1000), self.disable))
 
1767
                self.disable_initiator_tag = gobject.timeout_add(
 
1768
                    int((self.expires - now).total_seconds() * 1000),
 
1769
                    self.disable)
1564
1770
    
1565
1771
    # ExtendedTimeout - property
1566
 
    @dbus_service_property(_interface, signature="t",
 
1772
    @dbus_service_property(_interface,
 
1773
                           signature="t",
1567
1774
                           access="readwrite")
1568
1775
    def ExtendedTimeout_dbus_property(self, value=None):
1569
1776
        if value is None:       # get
1572
1779
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1573
1780
    
1574
1781
    # Interval - property
1575
 
    @dbus_service_property(_interface, signature="t",
 
1782
    @dbus_service_property(_interface,
 
1783
                           signature="t",
1576
1784
                           access="readwrite")
1577
1785
    def Interval_dbus_property(self, value=None):
1578
1786
        if value is None:       # get
1583
1791
        if self.enabled:
1584
1792
            # Reschedule checker run
1585
1793
            gobject.source_remove(self.checker_initiator_tag)
1586
 
            self.checker_initiator_tag = (gobject.timeout_add
1587
 
                                          (value, self.start_checker))
1588
 
            self.start_checker()    # Start one now, too
 
1794
            self.checker_initiator_tag = gobject.timeout_add(
 
1795
                value, self.start_checker)
 
1796
            self.start_checker() # Start one now, too
1589
1797
    
1590
1798
    # Checker - property
1591
 
    @dbus_service_property(_interface, signature="s",
 
1799
    @dbus_service_property(_interface,
 
1800
                           signature="s",
1592
1801
                           access="readwrite")
1593
1802
    def Checker_dbus_property(self, value=None):
1594
1803
        if value is None:       # get
1595
1804
            return dbus.String(self.checker_command)
1596
 
        self.checker_command = unicode(value)
 
1805
        self.checker_command = str(value)
1597
1806
    
1598
1807
    # CheckerRunning - property
1599
 
    @dbus_service_property(_interface, signature="b",
 
1808
    @dbus_service_property(_interface,
 
1809
                           signature="b",
1600
1810
                           access="readwrite")
1601
1811
    def CheckerRunning_dbus_property(self, value=None):
1602
1812
        if value is None:       # get
1607
1817
            self.stop_checker()
1608
1818
    
1609
1819
    # ObjectPath - property
 
1820
    @dbus_annotations(
 
1821
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const",
 
1822
         "org.freedesktop.DBus.Deprecated": "true"})
1610
1823
    @dbus_service_property(_interface, signature="o", access="read")
1611
1824
    def ObjectPath_dbus_property(self):
1612
1825
        return self.dbus_object_path # is already a dbus.ObjectPath
1613
1826
    
1614
1827
    # Secret = property
1615
 
    @dbus_service_property(_interface, signature="ay",
1616
 
                           access="write", byte_arrays=True)
 
1828
    @dbus_annotations(
 
1829
        {"org.freedesktop.DBus.Property.EmitsChangedSignal":
 
1830
         "invalidates"})
 
1831
    @dbus_service_property(_interface,
 
1832
                           signature="ay",
 
1833
                           access="write",
 
1834
                           byte_arrays=True)
1617
1835
    def Secret_dbus_property(self, value):
1618
 
        self.secret = str(value)
 
1836
        self.secret = bytes(value)
1619
1837
    
1620
1838
    del _interface
1621
1839
 
1625
1843
        self._pipe = child_pipe
1626
1844
        self._pipe.send(('init', fpr, address))
1627
1845
        if not self._pipe.recv():
1628
 
            raise KeyError()
 
1846
            raise KeyError(fpr)
1629
1847
    
1630
1848
    def __getattribute__(self, name):
1631
1849
        if name == '_pipe':
1635
1853
        if data[0] == 'data':
1636
1854
            return data[1]
1637
1855
        if data[0] == 'function':
 
1856
            
1638
1857
            def func(*args, **kwargs):
1639
1858
                self._pipe.send(('funcall', name, args, kwargs))
1640
1859
                return self._pipe.recv()[1]
 
1860
            
1641
1861
            return func
1642
1862
    
1643
1863
    def __setattr__(self, name, value):
1655
1875
    def handle(self):
1656
1876
        with contextlib.closing(self.server.child_pipe) as child_pipe:
1657
1877
            logger.info("TCP connection from: %s",
1658
 
                        unicode(self.client_address))
 
1878
                        str(self.client_address))
1659
1879
            logger.debug("Pipe FD: %d",
1660
1880
                         self.server.child_pipe.fileno())
1661
1881
            
1662
 
            session = (gnutls.connection
1663
 
                       .ClientSession(self.request,
1664
 
                                      gnutls.connection
1665
 
                                      .X509Credentials()))
 
1882
            session = gnutls.connection.ClientSession(
 
1883
                self.request, gnutls.connection.X509Credentials())
1666
1884
            
1667
1885
            # Note: gnutls.connection.X509Credentials is really a
1668
1886
            # generic GnuTLS certificate credentials object so long as
1677
1895
            priority = self.server.gnutls_priority
1678
1896
            if priority is None:
1679
1897
                priority = "NORMAL"
1680
 
            (gnutls.library.functions
1681
 
             .gnutls_priority_set_direct(session._c_object,
1682
 
                                         priority, None))
 
1898
            gnutls.library.functions.gnutls_priority_set_direct(
 
1899
                session._c_object, priority, None)
1683
1900
            
1684
1901
            # Start communication using the Mandos protocol
1685
1902
            # Get protocol number
1705
1922
            approval_required = False
1706
1923
            try:
1707
1924
                try:
1708
 
                    fpr = self.fingerprint(self.peer_certificate
1709
 
                                           (session))
 
1925
                    fpr = self.fingerprint(
 
1926
                        self.peer_certificate(session))
1710
1927
                except (TypeError,
1711
1928
                        gnutls.errors.GNUTLSError) as error:
1712
1929
                    logger.warning("Bad certificate: %s", error)
1727
1944
                while True:
1728
1945
                    if not client.enabled:
1729
1946
                        logger.info("Client %s is disabled",
1730
 
                                       client.name)
 
1947
                                    client.name)
1731
1948
                        if self.server.use_dbus:
1732
1949
                            # Emit D-Bus signal
1733
1950
                            client.Rejected("Disabled")
1780
1997
                        logger.warning("gnutls send failed",
1781
1998
                                       exc_info=error)
1782
1999
                        return
1783
 
                    logger.debug("Sent: %d, remaining: %d",
1784
 
                                 sent, len(client.secret)
1785
 
                                 - (sent_size + sent))
 
2000
                    logger.debug("Sent: %d, remaining: %d", sent,
 
2001
                                 len(client.secret) - (sent_size
 
2002
                                                       + sent))
1786
2003
                    sent_size += sent
1787
2004
                
1788
2005
                logger.info("Sending secret to %s", client.name)
1805
2022
    def peer_certificate(session):
1806
2023
        "Return the peer's OpenPGP certificate as a bytestring"
1807
2024
        # If not an OpenPGP certificate...
1808
 
        if (gnutls.library.functions
1809
 
            .gnutls_certificate_type_get(session._c_object)
 
2025
        if (gnutls.library.functions.gnutls_certificate_type_get(
 
2026
                session._c_object)
1810
2027
            != gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1811
2028
            # ...do the normal thing
1812
2029
            return session.peer_certificate
1826
2043
    def fingerprint(openpgp):
1827
2044
        "Convert an OpenPGP bytestring to a hexdigit fingerprint"
1828
2045
        # New GnuTLS "datum" with the OpenPGP public key
1829
 
        datum = (gnutls.library.types
1830
 
                 .gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1831
 
                                             ctypes.POINTER
1832
 
                                             (ctypes.c_ubyte)),
1833
 
                                 ctypes.c_uint(len(openpgp))))
 
2046
        datum = gnutls.library.types.gnutls_datum_t(
 
2047
            ctypes.cast(ctypes.c_char_p(openpgp),
 
2048
                        ctypes.POINTER(ctypes.c_ubyte)),
 
2049
            ctypes.c_uint(len(openpgp)))
1834
2050
        # New empty GnuTLS certificate
1835
2051
        crt = gnutls.library.types.gnutls_openpgp_crt_t()
1836
 
        (gnutls.library.functions
1837
 
         .gnutls_openpgp_crt_init(ctypes.byref(crt)))
 
2052
        gnutls.library.functions.gnutls_openpgp_crt_init(
 
2053
            ctypes.byref(crt))
1838
2054
        # Import the OpenPGP public key into the certificate
1839
 
        (gnutls.library.functions
1840
 
         .gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1841
 
                                    gnutls.library.constants
1842
 
                                    .GNUTLS_OPENPGP_FMT_RAW))
 
2055
        gnutls.library.functions.gnutls_openpgp_crt_import(
 
2056
            crt, ctypes.byref(datum),
 
2057
            gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
1843
2058
        # Verify the self signature in the key
1844
2059
        crtverify = ctypes.c_uint()
1845
 
        (gnutls.library.functions
1846
 
         .gnutls_openpgp_crt_verify_self(crt, 0,
1847
 
                                         ctypes.byref(crtverify)))
 
2060
        gnutls.library.functions.gnutls_openpgp_crt_verify_self(
 
2061
            crt, 0, ctypes.byref(crtverify))
1848
2062
        if crtverify.value != 0:
1849
2063
            gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1850
 
            raise (gnutls.errors.CertificateSecurityError
1851
 
                   ("Verify failed"))
 
2064
            raise gnutls.errors.CertificateSecurityError(
 
2065
                "Verify failed")
1852
2066
        # New buffer for the fingerprint
1853
2067
        buf = ctypes.create_string_buffer(20)
1854
2068
        buf_len = ctypes.c_size_t()
1855
2069
        # Get the fingerprint from the certificate into the buffer
1856
 
        (gnutls.library.functions
1857
 
         .gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1858
 
                                             ctypes.byref(buf_len)))
 
2070
        gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint(
 
2071
            crt, ctypes.byref(buf), ctypes.byref(buf_len))
1859
2072
        # Deinit the certificate
1860
2073
        gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1861
2074
        # Convert the buffer to a Python bytestring
1867
2080
 
1868
2081
class MultiprocessingMixIn(object):
1869
2082
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
 
2083
    
1870
2084
    def sub_process_main(self, request, address):
1871
2085
        try:
1872
2086
            self.finish_request(request, address)
1884
2098
 
1885
2099
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1886
2100
    """ adds a pipe to the MixIn """
 
2101
    
1887
2102
    def process_request(self, request, client_address):
1888
2103
        """Overrides and wraps the original process_request().
1889
2104
        
1910
2125
        interface:      None or a network interface name (string)
1911
2126
        use_ipv6:       Boolean; to use IPv6 or not
1912
2127
    """
 
2128
    
1913
2129
    def __init__(self, server_address, RequestHandlerClass,
1914
 
                 interface=None, use_ipv6=True, socketfd=None):
 
2130
                 interface=None,
 
2131
                 use_ipv6=True,
 
2132
                 socketfd=None):
1915
2133
        """If socketfd is set, use that file descriptor instead of
1916
2134
        creating a new one with socket.socket().
1917
2135
        """
1958
2176
                             self.interface)
1959
2177
            else:
1960
2178
                try:
1961
 
                    self.socket.setsockopt(socket.SOL_SOCKET,
1962
 
                                           SO_BINDTODEVICE,
1963
 
                                           str(self.interface + '\0'))
 
2179
                    self.socket.setsockopt(
 
2180
                        socket.SOL_SOCKET, SO_BINDTODEVICE,
 
2181
                        (self.interface + "\0").encode("utf-8"))
1964
2182
                except socket.error as error:
1965
2183
                    if error.errno == errno.EPERM:
1966
2184
                        logger.error("No permission to bind to"
1984
2202
                self.server_address = (any_address,
1985
2203
                                       self.server_address[1])
1986
2204
            elif not self.server_address[1]:
1987
 
                self.server_address = (self.server_address[0],
1988
 
                                       0)
 
2205
                self.server_address = (self.server_address[0], 0)
1989
2206
#                 if self.interface:
1990
2207
#                     self.server_address = (self.server_address[0],
1991
2208
#                                            0, # port
2005
2222
    
2006
2223
    Assumes a gobject.MainLoop event loop.
2007
2224
    """
 
2225
    
2008
2226
    def __init__(self, server_address, RequestHandlerClass,
2009
 
                 interface=None, use_ipv6=True, clients=None,
2010
 
                 gnutls_priority=None, use_dbus=True, socketfd=None):
 
2227
                 interface=None,
 
2228
                 use_ipv6=True,
 
2229
                 clients=None,
 
2230
                 gnutls_priority=None,
 
2231
                 use_dbus=True,
 
2232
                 socketfd=None):
2011
2233
        self.enabled = False
2012
2234
        self.clients = clients
2013
2235
        if self.clients is None:
2019
2241
                                interface = interface,
2020
2242
                                use_ipv6 = use_ipv6,
2021
2243
                                socketfd = socketfd)
 
2244
    
2022
2245
    def server_activate(self):
2023
2246
        if self.enabled:
2024
2247
            return socketserver.TCPServer.server_activate(self)
2028
2251
    
2029
2252
    def add_pipe(self, parent_pipe, proc):
2030
2253
        # Call "handle_ipc" for both data and EOF events
2031
 
        gobject.io_add_watch(parent_pipe.fileno(),
2032
 
                             gobject.IO_IN | gobject.IO_HUP,
2033
 
                             functools.partial(self.handle_ipc,
2034
 
                                               parent_pipe =
2035
 
                                               parent_pipe,
2036
 
                                               proc = proc))
 
2254
        gobject.io_add_watch(
 
2255
            parent_pipe.fileno(),
 
2256
            gobject.IO_IN | gobject.IO_HUP,
 
2257
            functools.partial(self.handle_ipc,
 
2258
                              parent_pipe = parent_pipe,
 
2259
                              proc = proc))
2037
2260
    
2038
 
    def handle_ipc(self, source, condition, parent_pipe=None,
2039
 
                   proc = None, client_object=None):
 
2261
    def handle_ipc(self, source, condition,
 
2262
                   parent_pipe=None,
 
2263
                   proc = None,
 
2264
                   client_object=None):
2040
2265
        # error, or the other end of multiprocessing.Pipe has closed
2041
2266
        if condition & (gobject.IO_ERR | gobject.IO_HUP):
2042
2267
            # Wait for other process to exit
2065
2290
                parent_pipe.send(False)
2066
2291
                return False
2067
2292
            
2068
 
            gobject.io_add_watch(parent_pipe.fileno(),
2069
 
                                 gobject.IO_IN | gobject.IO_HUP,
2070
 
                                 functools.partial(self.handle_ipc,
2071
 
                                                   parent_pipe =
2072
 
                                                   parent_pipe,
2073
 
                                                   proc = proc,
2074
 
                                                   client_object =
2075
 
                                                   client))
 
2293
            gobject.io_add_watch(
 
2294
                parent_pipe.fileno(),
 
2295
                gobject.IO_IN | gobject.IO_HUP,
 
2296
                functools.partial(self.handle_ipc,
 
2297
                                  parent_pipe = parent_pipe,
 
2298
                                  proc = proc,
 
2299
                                  client_object = client))
2076
2300
            parent_pipe.send(True)
2077
2301
            # remove the old hook in favor of the new above hook on
2078
2302
            # same fileno
2084
2308
            
2085
2309
            parent_pipe.send(('data', getattr(client_object,
2086
2310
                                              funcname)(*args,
2087
 
                                                         **kwargs)))
 
2311
                                                        **kwargs)))
2088
2312
        
2089
2313
        if command == 'getattr':
2090
2314
            attrname = request[1]
2091
 
            if callable(client_object.__getattribute__(attrname)):
2092
 
                parent_pipe.send(('function',))
 
2315
            if isinstance(client_object.__getattribute__(attrname),
 
2316
                          collections.Callable):
 
2317
                parent_pipe.send(('function', ))
2093
2318
            else:
2094
 
                parent_pipe.send(('data', client_object
2095
 
                                  .__getattribute__(attrname)))
 
2319
                parent_pipe.send((
 
2320
                    'data', client_object.__getattribute__(attrname)))
2096
2321
        
2097
2322
        if command == 'setattr':
2098
2323
            attrname = request[1]
2129
2354
    # avoid excessive use of external libraries.
2130
2355
    
2131
2356
    # New type for defining tokens, syntax, and semantics all-in-one
2132
 
    Token = collections.namedtuple("Token",
2133
 
                                   ("regexp", # To match token; if
2134
 
                                              # "value" is not None,
2135
 
                                              # must have a "group"
2136
 
                                              # containing digits
2137
 
                                    "value",  # datetime.timedelta or
2138
 
                                              # None
2139
 
                                    "followers")) # Tokens valid after
2140
 
                                                  # this token
 
2357
    Token = collections.namedtuple("Token", (
 
2358
        "regexp",  # To match token; if "value" is not None, must have
 
2359
                   # a "group" containing digits
 
2360
        "value",   # datetime.timedelta or None
 
2361
        "followers"))           # Tokens valid after this token
2141
2362
    # RFC 3339 "duration" tokens, syntax, and semantics; taken from
2142
2363
    # the "duration" ABNF definition in RFC 3339, Appendix A.
2143
2364
    token_end = Token(re.compile(r"$"), None, frozenset())
2144
2365
    token_second = Token(re.compile(r"(\d+)S"),
2145
2366
                         datetime.timedelta(seconds=1),
2146
 
                         frozenset((token_end,)))
 
2367
                         frozenset((token_end, )))
2147
2368
    token_minute = Token(re.compile(r"(\d+)M"),
2148
2369
                         datetime.timedelta(minutes=1),
2149
2370
                         frozenset((token_second, token_end)))
2165
2386
                       frozenset((token_month, token_end)))
2166
2387
    token_week = Token(re.compile(r"(\d+)W"),
2167
2388
                       datetime.timedelta(weeks=1),
2168
 
                       frozenset((token_end,)))
 
2389
                       frozenset((token_end, )))
2169
2390
    token_duration = Token(re.compile(r"P"), None,
2170
2391
                           frozenset((token_year, token_month,
2171
2392
                                      token_day, token_time,
2173
2394
    # Define starting values
2174
2395
    value = datetime.timedelta() # Value so far
2175
2396
    found_token = None
2176
 
    followers = frozenset((token_duration,)) # Following valid tokens
 
2397
    followers = frozenset((token_duration, )) # Following valid tokens
2177
2398
    s = duration                # String left to parse
2178
2399
    # Loop until end token is found
2179
2400
    while found_token is not token_end:
2196
2417
                break
2197
2418
        else:
2198
2419
            # No currently valid tokens were found
2199
 
            raise ValueError("Invalid RFC 3339 duration")
 
2420
            raise ValueError("Invalid RFC 3339 duration: {!r}"
 
2421
                             .format(duration))
2200
2422
    # End token found
2201
2423
    return value
2202
2424
 
2226
2448
    timevalue = datetime.timedelta(0)
2227
2449
    for s in interval.split():
2228
2450
        try:
2229
 
            suffix = unicode(s[-1])
 
2451
            suffix = s[-1]
2230
2452
            value = int(s[:-1])
2231
2453
            if suffix == "d":
2232
2454
                delta = datetime.timedelta(value)
2239
2461
            elif suffix == "w":
2240
2462
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
2241
2463
            else:
2242
 
                raise ValueError("Unknown suffix {!r}"
2243
 
                                 .format(suffix))
 
2464
                raise ValueError("Unknown suffix {!r}".format(suffix))
2244
2465
        except IndexError as e:
2245
2466
            raise ValueError(*(e.args))
2246
2467
        timevalue += delta
2262
2483
        # Close all standard open file descriptors
2263
2484
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2264
2485
        if not stat.S_ISCHR(os.fstat(null).st_mode):
2265
 
            raise OSError(errno.ENODEV, "{} not a character device"
 
2486
            raise OSError(errno.ENODEV,
 
2487
                          "{} not a character device"
2266
2488
                          .format(os.devnull))
2267
2489
        os.dup2(null, sys.stdin.fileno())
2268
2490
        os.dup2(null, sys.stdout.fileno())
2334
2556
                        "port": "",
2335
2557
                        "debug": "False",
2336
2558
                        "priority":
2337
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
 
2559
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
 
2560
                        ":+SIGN-DSA-SHA256",
2338
2561
                        "servicename": "Mandos",
2339
2562
                        "use_dbus": "True",
2340
2563
                        "use_ipv6": "True",
2344
2567
                        "statedir": "/var/lib/mandos",
2345
2568
                        "foreground": "False",
2346
2569
                        "zeroconf": "True",
2347
 
                        }
 
2570
                    }
2348
2571
    
2349
2572
    # Parse config file for server-global settings
2350
2573
    server_config = configparser.SafeConfigParser(server_defaults)
2351
2574
    del server_defaults
2352
 
    server_config.read(os.path.join(options.configdir,
2353
 
                                    "mandos.conf"))
 
2575
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
2354
2576
    # Convert the SafeConfigParser object to a dict
2355
2577
    server_settings = server_config.defaults()
2356
2578
    # Use the appropriate methods on the non-string config options
2374
2596
    # Override the settings from the config file with command line
2375
2597
    # options, if set.
2376
2598
    for option in ("interface", "address", "port", "debug",
2377
 
                   "priority", "servicename", "configdir",
2378
 
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
2379
 
                   "statedir", "socket", "foreground", "zeroconf"):
 
2599
                   "priority", "servicename", "configdir", "use_dbus",
 
2600
                   "use_ipv6", "debuglevel", "restore", "statedir",
 
2601
                   "socket", "foreground", "zeroconf"):
2380
2602
        value = getattr(options, option)
2381
2603
        if value is not None:
2382
2604
            server_settings[option] = value
2383
2605
    del options
2384
2606
    # Force all strings to be unicode
2385
2607
    for option in server_settings.keys():
2386
 
        if type(server_settings[option]) is str:
2387
 
            server_settings[option] = unicode(server_settings[option])
 
2608
        if isinstance(server_settings[option], bytes):
 
2609
            server_settings[option] = (server_settings[option]
 
2610
                                       .decode("utf-8"))
2388
2611
    # Force all boolean options to be boolean
2389
2612
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
2390
2613
                   "foreground", "zeroconf"):
2396
2619
    
2397
2620
    ##################################################################
2398
2621
    
2399
 
    if (not server_settings["zeroconf"] and
2400
 
        not (server_settings["port"]
2401
 
             or server_settings["socket"] != "")):
2402
 
            parser.error("Needs port or socket to work without"
2403
 
                         " Zeroconf")
 
2622
    if (not server_settings["zeroconf"]
 
2623
        and not (server_settings["port"]
 
2624
                 or server_settings["socket"] != "")):
 
2625
        parser.error("Needs port or socket to work without Zeroconf")
2404
2626
    
2405
2627
    # For convenience
2406
2628
    debug = server_settings["debug"]
2422
2644
            initlogger(debug, level)
2423
2645
    
2424
2646
    if server_settings["servicename"] != "Mandos":
2425
 
        syslogger.setFormatter(logging.Formatter
2426
 
                               ('Mandos ({}) [%(process)d]:'
2427
 
                                ' %(levelname)s: %(message)s'
2428
 
                                .format(server_settings
2429
 
                                        ["servicename"])))
 
2647
        syslogger.setFormatter(
 
2648
            logging.Formatter('Mandos ({}) [%(process)d]:'
 
2649
                              ' %(levelname)s: %(message)s'.format(
 
2650
                                  server_settings["servicename"])))
2430
2651
    
2431
2652
    # Parse config file with clients
2432
2653
    client_config = configparser.SafeConfigParser(Client
2440
2661
    socketfd = None
2441
2662
    if server_settings["socket"] != "":
2442
2663
        socketfd = server_settings["socket"]
2443
 
    tcp_server = MandosServer((server_settings["address"],
2444
 
                               server_settings["port"]),
2445
 
                              ClientHandler,
2446
 
                              interface=(server_settings["interface"]
2447
 
                                         or None),
2448
 
                              use_ipv6=use_ipv6,
2449
 
                              gnutls_priority=
2450
 
                              server_settings["priority"],
2451
 
                              use_dbus=use_dbus,
2452
 
                              socketfd=socketfd)
 
2664
    tcp_server = MandosServer(
 
2665
        (server_settings["address"], server_settings["port"]),
 
2666
        ClientHandler,
 
2667
        interface=(server_settings["interface"] or None),
 
2668
        use_ipv6=use_ipv6,
 
2669
        gnutls_priority=server_settings["priority"],
 
2670
        use_dbus=use_dbus,
 
2671
        socketfd=socketfd)
2453
2672
    if not foreground:
2454
2673
        pidfilename = "/run/mandos.pid"
2455
2674
        if not os.path.isdir("/run/."):
2456
2675
            pidfilename = "/var/run/mandos.pid"
2457
2676
        pidfile = None
2458
2677
        try:
2459
 
            pidfile = open(pidfilename, "w")
 
2678
            pidfile = codecs.open(pidfilename, "w", encoding="utf-8")
2460
2679
        except IOError as e:
2461
2680
            logger.error("Could not open file %r", pidfilename,
2462
2681
                         exc_info=e)
2489
2708
        def debug_gnutls(level, string):
2490
2709
            logger.debug("GnuTLS: %s", string[:-1])
2491
2710
        
2492
 
        (gnutls.library.functions
2493
 
         .gnutls_global_set_log_function(debug_gnutls))
 
2711
        gnutls.library.functions.gnutls_global_set_log_function(
 
2712
            debug_gnutls)
2494
2713
        
2495
2714
        # Redirect stdin so all checkers get /dev/null
2496
2715
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2516
2735
    if use_dbus:
2517
2736
        try:
2518
2737
            bus_name = dbus.service.BusName("se.recompile.Mandos",
2519
 
                                            bus, do_not_queue=True)
2520
 
            old_bus_name = (dbus.service.BusName
2521
 
                            ("se.bsnet.fukt.Mandos", bus,
2522
 
                             do_not_queue=True))
2523
 
        except dbus.exceptions.NameExistsException as e:
 
2738
                                            bus,
 
2739
                                            do_not_queue=True)
 
2740
            old_bus_name = dbus.service.BusName(
 
2741
                "se.bsnet.fukt.Mandos", bus,
 
2742
                do_not_queue=True)
 
2743
        except dbus.exceptions.DBusException as e:
2524
2744
            logger.error("Disabling D-Bus:", exc_info=e)
2525
2745
            use_dbus = False
2526
2746
            server_settings["use_dbus"] = False
2527
2747
            tcp_server.use_dbus = False
2528
2748
    if zeroconf:
2529
2749
        protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2530
 
        service = AvahiServiceToSyslog(name =
2531
 
                                       server_settings["servicename"],
2532
 
                                       servicetype = "_mandos._tcp",
2533
 
                                       protocol = protocol, bus = bus)
 
2750
        service = AvahiServiceToSyslog(
 
2751
            name = server_settings["servicename"],
 
2752
            servicetype = "_mandos._tcp",
 
2753
            protocol = protocol,
 
2754
            bus = bus)
2534
2755
        if server_settings["interface"]:
2535
 
            service.interface = (if_nametoindex
2536
 
                                 (str(server_settings["interface"])))
 
2756
            service.interface = if_nametoindex(
 
2757
                server_settings["interface"].encode("utf-8"))
2537
2758
    
2538
2759
    global multiprocessing_manager
2539
2760
    multiprocessing_manager = multiprocessing.Manager()
2558
2779
    if server_settings["restore"]:
2559
2780
        try:
2560
2781
            with open(stored_state_path, "rb") as stored_state:
2561
 
                clients_data, old_client_settings = (pickle.load
2562
 
                                                     (stored_state))
 
2782
                clients_data, old_client_settings = pickle.load(
 
2783
                    stored_state)
2563
2784
            os.remove(stored_state_path)
2564
2785
        except IOError as e:
2565
2786
            if e.errno == errno.ENOENT:
2566
 
                logger.warning("Could not load persistent state: {}"
2567
 
                                .format(os.strerror(e.errno)))
 
2787
                logger.warning("Could not load persistent state:"
 
2788
                               " {}".format(os.strerror(e.errno)))
2568
2789
            else:
2569
2790
                logger.critical("Could not load persistent state:",
2570
2791
                                exc_info=e)
2571
2792
                raise
2572
2793
        except EOFError as e:
2573
2794
            logger.warning("Could not load persistent state: "
2574
 
                           "EOFError:", exc_info=e)
 
2795
                           "EOFError:",
 
2796
                           exc_info=e)
2575
2797
    
2576
2798
    with PGPEngine() as pgp:
2577
2799
        for client_name, client in clients_data.items():
2589
2811
                    # For each value in new config, check if it
2590
2812
                    # differs from the old config value (Except for
2591
2813
                    # the "secret" attribute)
2592
 
                    if (name != "secret" and
2593
 
                        value != old_client_settings[client_name]
2594
 
                        [name]):
 
2814
                    if (name != "secret"
 
2815
                        and (value !=
 
2816
                             old_client_settings[client_name][name])):
2595
2817
                        client[name] = value
2596
2818
                except KeyError:
2597
2819
                    pass
2598
2820
            
2599
2821
            # Clients who has passed its expire date can still be
2600
 
            # enabled if its last checker was successful.  Clients
 
2822
            # enabled if its last checker was successful.  A Client
2601
2823
            # whose checker succeeded before we stored its state is
2602
2824
            # assumed to have successfully run all checkers during
2603
2825
            # downtime.
2606
2828
                    if not client["last_checked_ok"]:
2607
2829
                        logger.warning(
2608
2830
                            "disabling client {} - Client never "
2609
 
                            "performed a successful checker"
2610
 
                            .format(client_name))
 
2831
                            "performed a successful checker".format(
 
2832
                                client_name))
2611
2833
                        client["enabled"] = False
2612
2834
                    elif client["last_checker_status"] != 0:
2613
2835
                        logger.warning(
2614
2836
                            "disabling client {} - Client last"
2615
 
                            " checker failed with error code {}"
2616
 
                            .format(client_name,
2617
 
                                    client["last_checker_status"]))
 
2837
                            " checker failed with error code"
 
2838
                            " {}".format(
 
2839
                                client_name,
 
2840
                                client["last_checker_status"]))
2618
2841
                        client["enabled"] = False
2619
2842
                    else:
2620
 
                        client["expires"] = (datetime.datetime
2621
 
                                             .utcnow()
2622
 
                                             + client["timeout"])
 
2843
                        client["expires"] = (
 
2844
                            datetime.datetime.utcnow()
 
2845
                            + client["timeout"])
2623
2846
                        logger.debug("Last checker succeeded,"
2624
 
                                     " keeping {} enabled"
2625
 
                                     .format(client_name))
 
2847
                                     " keeping {} enabled".format(
 
2848
                                         client_name))
2626
2849
            try:
2627
 
                client["secret"] = (
2628
 
                    pgp.decrypt(client["encrypted_secret"],
2629
 
                                client_settings[client_name]
2630
 
                                ["secret"]))
 
2850
                client["secret"] = pgp.decrypt(
 
2851
                    client["encrypted_secret"],
 
2852
                    client_settings[client_name]["secret"])
2631
2853
            except PGPError:
2632
2854
                # If decryption fails, we use secret from new settings
2633
 
                logger.debug("Failed to decrypt {} old secret"
2634
 
                             .format(client_name))
2635
 
                client["secret"] = (
2636
 
                    client_settings[client_name]["secret"])
 
2855
                logger.debug("Failed to decrypt {} old secret".format(
 
2856
                    client_name))
 
2857
                client["secret"] = (client_settings[client_name]
 
2858
                                    ["secret"])
2637
2859
    
2638
2860
    # Add/remove clients based on new changes made to config
2639
2861
    for client_name in (set(old_client_settings)
2646
2868
    # Create all client objects
2647
2869
    for client_name, client in clients_data.items():
2648
2870
        tcp_server.clients[client_name] = client_class(
2649
 
            name = client_name, settings = client,
 
2871
            name = client_name,
 
2872
            settings = client,
2650
2873
            server_settings = server_settings)
2651
2874
    
2652
2875
    if not tcp_server.clients:
2654
2877
    
2655
2878
    if not foreground:
2656
2879
        if pidfile is not None:
 
2880
            pid = os.getpid()
2657
2881
            try:
2658
2882
                with pidfile:
2659
 
                    pid = os.getpid()
2660
 
                    pidfile.write(str(pid) + "\n".encode("utf-8"))
 
2883
                    print(pid, file=pidfile)
2661
2884
            except IOError:
2662
2885
                logger.error("Could not write to file %r with PID %d",
2663
2886
                             pidfilename, pid)
2668
2891
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2669
2892
    
2670
2893
    if use_dbus:
2671
 
        @alternate_dbus_interfaces({"se.recompile.Mandos":
2672
 
                                        "se.bsnet.fukt.Mandos"})
2673
 
        class MandosDBusService(DBusObjectWithProperties):
 
2894
        
 
2895
        @alternate_dbus_interfaces(
 
2896
            { "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
 
2897
        class MandosDBusService(DBusObjectWithObjectManager):
2674
2898
            """A D-Bus proxy object"""
 
2899
            
2675
2900
            def __init__(self):
2676
2901
                dbus.service.Object.__init__(self, bus, "/")
 
2902
            
2677
2903
            _interface = "se.recompile.Mandos"
2678
2904
            
2679
 
            @dbus_interface_annotations(_interface)
2680
 
            def _foo(self):
2681
 
                return { "org.freedesktop.DBus.Property"
2682
 
                         ".EmitsChangedSignal":
2683
 
                             "false"}
2684
 
            
2685
2905
            @dbus.service.signal(_interface, signature="o")
2686
2906
            def ClientAdded(self, objpath):
2687
2907
                "D-Bus signal"
2692
2912
                "D-Bus signal"
2693
2913
                pass
2694
2914
            
 
2915
            @dbus_annotations({"org.freedesktop.DBus.Deprecated":
 
2916
                               "true"})
2695
2917
            @dbus.service.signal(_interface, signature="os")
2696
2918
            def ClientRemoved(self, objpath, name):
2697
2919
                "D-Bus signal"
2698
2920
                pass
2699
2921
            
 
2922
            @dbus_annotations({"org.freedesktop.DBus.Deprecated":
 
2923
                               "true"})
2700
2924
            @dbus.service.method(_interface, out_signature="ao")
2701
2925
            def GetAllClients(self):
2702
2926
                "D-Bus method"
2703
 
                return dbus.Array(c.dbus_object_path
2704
 
                                  for c in
 
2927
                return dbus.Array(c.dbus_object_path for c in
2705
2928
                                  tcp_server.clients.itervalues())
2706
2929
            
 
2930
            @dbus_annotations({"org.freedesktop.DBus.Deprecated":
 
2931
                               "true"})
2707
2932
            @dbus.service.method(_interface,
2708
2933
                                 out_signature="a{oa{sv}}")
2709
2934
            def GetAllClientsWithProperties(self):
2710
2935
                "D-Bus method"
2711
2936
                return dbus.Dictionary(
2712
 
                    ((c.dbus_object_path, c.GetAll(""))
2713
 
                     for c in tcp_server.clients.itervalues()),
 
2937
                    { c.dbus_object_path: c.GetAll(
 
2938
                        "se.recompile.Mandos.Client")
 
2939
                      for c in tcp_server.clients.itervalues() },
2714
2940
                    signature="oa{sv}")
2715
2941
            
2716
2942
            @dbus.service.method(_interface, in_signature="o")
2720
2946
                    if c.dbus_object_path == object_path:
2721
2947
                        del tcp_server.clients[c.name]
2722
2948
                        c.remove_from_connection()
2723
 
                        # Don't signal anything except ClientRemoved
 
2949
                        # Don't signal the disabling
2724
2950
                        c.disable(quiet=True)
2725
 
                        # Emit D-Bus signal
2726
 
                        self.ClientRemoved(object_path, c.name)
 
2951
                        # Emit D-Bus signal for removal
 
2952
                        self.client_removed_signal(c)
2727
2953
                        return
2728
2954
                raise KeyError(object_path)
2729
2955
            
2730
2956
            del _interface
 
2957
            
 
2958
            @dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
 
2959
                                 out_signature = "a{oa{sa{sv}}}")
 
2960
            def GetManagedObjects(self):
 
2961
                """D-Bus method"""
 
2962
                return dbus.Dictionary(
 
2963
                    { client.dbus_object_path:
 
2964
                      dbus.Dictionary(
 
2965
                          { interface: client.GetAll(interface)
 
2966
                            for interface in
 
2967
                                 client._get_all_interface_names()})
 
2968
                      for client in tcp_server.clients.values()})
 
2969
            
 
2970
            def client_added_signal(self, client):
 
2971
                """Send the new standard signal and the old signal"""
 
2972
                if use_dbus:
 
2973
                    # New standard signal
 
2974
                    self.InterfacesAdded(
 
2975
                        client.dbus_object_path,
 
2976
                        dbus.Dictionary(
 
2977
                            { interface: client.GetAll(interface)
 
2978
                              for interface in
 
2979
                              client._get_all_interface_names()}))
 
2980
                    # Old signal
 
2981
                    self.ClientAdded(client.dbus_object_path)
 
2982
            
 
2983
            def client_removed_signal(self, client):
 
2984
                """Send the new standard signal and the old signal"""
 
2985
                if use_dbus:
 
2986
                    # New standard signal
 
2987
                    self.InterfacesRemoved(
 
2988
                        client.dbus_object_path,
 
2989
                        client._get_all_interface_names())
 
2990
                    # Old signal
 
2991
                    self.ClientRemoved(client.dbus_object_path,
 
2992
                                       client.name)
2731
2993
        
2732
2994
        mandos_dbus_service = MandosDBusService()
2733
2995
    
2756
3018
                # + secret.
2757
3019
                exclude = { "bus", "changedstate", "secret",
2758
3020
                            "checker", "server_settings" }
2759
 
                for name, typ in (inspect.getmembers
2760
 
                                  (dbus.service.Object)):
 
3021
                for name, typ in inspect.getmembers(dbus.service
 
3022
                                                    .Object):
2761
3023
                    exclude.add(name)
2762
3024
                
2763
3025
                client_dict["encrypted_secret"] = (client
2770
3032
                del client_settings[client.name]["secret"]
2771
3033
        
2772
3034
        try:
2773
 
            with (tempfile.NamedTemporaryFile
2774
 
                  (mode='wb', suffix=".pickle", prefix='clients-',
2775
 
                   dir=os.path.dirname(stored_state_path),
2776
 
                   delete=False)) as stored_state:
 
3035
            with tempfile.NamedTemporaryFile(
 
3036
                    mode='wb',
 
3037
                    suffix=".pickle",
 
3038
                    prefix='clients-',
 
3039
                    dir=os.path.dirname(stored_state_path),
 
3040
                    delete=False) as stored_state:
2777
3041
                pickle.dump((clients, client_settings), stored_state)
2778
 
                tempname=stored_state.name
 
3042
                tempname = stored_state.name
2779
3043
            os.rename(tempname, stored_state_path)
2780
3044
        except (IOError, OSError) as e:
2781
3045
            if not debug:
2796
3060
            name, client = tcp_server.clients.popitem()
2797
3061
            if use_dbus:
2798
3062
                client.remove_from_connection()
2799
 
            # Don't signal anything except ClientRemoved
 
3063
            # Don't signal the disabling
2800
3064
            client.disable(quiet=True)
 
3065
            # Emit D-Bus signal for removal
2801
3066
            if use_dbus:
2802
 
                # Emit D-Bus signal
2803
 
                mandos_dbus_service.ClientRemoved(client
2804
 
                                                  .dbus_object_path,
2805
 
                                                  client.name)
 
3067
                mandos_dbus_service.client_removed_signal(client)
2806
3068
        client_settings.clear()
2807
3069
    
2808
3070
    atexit.register(cleanup)
2809
3071
    
2810
3072
    for client in tcp_server.clients.itervalues():
2811
3073
        if use_dbus:
2812
 
            # Emit D-Bus signal
2813
 
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
 
3074
            # Emit D-Bus signal for adding
 
3075
            mandos_dbus_service.client_added_signal(client)
2814
3076
        # Need to initiate checking of clients
2815
3077
        if client.enabled:
2816
3078
            client.init_checker()
2861
3123
    # Must run before the D-Bus bus name gets deregistered
2862
3124
    cleanup()
2863
3125
 
 
3126
 
2864
3127
if __name__ == '__main__':
2865
3128
    main()