/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: 2014-12-21 12:56:08 UTC
  • Revision ID: teddy@recompile.se-20141221125608-bmkvujlj39srpwd3
mandos-keygen: Fix some stylistic quoting issues.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python
 
1
#!/usr/bin/python2.7
2
2
# -*- mode: python; coding: utf-8 -*-
3
3
4
4
# Mandos server - give out binary blobs to connecting clients.
11
11
# "AvahiService" class, and some lines in "main".
12
12
13
13
# Everything else is
14
 
# Copyright © 2008-2012 Teddy Hogeborn
15
 
# Copyright © 2008-2012 Björn Påhlsson
 
14
# Copyright © 2008-2014 Teddy Hogeborn
 
15
# Copyright © 2008-2014 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
79
79
import ctypes.util
80
80
import xml.dom.minidom
81
81
import inspect
82
 
import GnuPGInterface
83
82
 
84
83
try:
85
84
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
89
88
    except ImportError:
90
89
        SO_BINDTODEVICE = None
91
90
 
92
 
version = "1.6.0"
 
91
if sys.version_info.major == 2:
 
92
    str = unicode
 
93
 
 
94
version = "1.6.9"
93
95
stored_state_file = "clients.pickle"
94
96
 
95
97
logger = logging.getLogger()
96
 
syslogger = (logging.handlers.SysLogHandler
97
 
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
98
 
              address = str("/dev/log")))
 
98
syslogger = None
99
99
 
100
100
try:
101
101
    if_nametoindex = (ctypes.cdll.LoadLibrary
107
107
        SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
108
108
        with contextlib.closing(socket.socket()) as s:
109
109
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
110
 
                                struct.pack(str("16s16x"),
111
 
                                            interface))
112
 
        interface_index = struct.unpack(str("I"),
113
 
                                        ifreq[16:20])[0]
 
110
                                struct.pack(b"16s16x", interface))
 
111
        interface_index = struct.unpack("I", ifreq[16:20])[0]
114
112
        return interface_index
115
113
 
116
114
 
117
115
def initlogger(debug, level=logging.WARNING):
118
116
    """init logger and add loglevel"""
119
117
    
 
118
    global syslogger
 
119
    syslogger = (logging.handlers.SysLogHandler
 
120
                 (facility =
 
121
                  logging.handlers.SysLogHandler.LOG_DAEMON,
 
122
                  address = "/dev/log"))
120
123
    syslogger.setFormatter(logging.Formatter
121
124
                           ('Mandos [%(process)d]: %(levelname)s:'
122
125
                            ' %(message)s'))
140
143
class PGPEngine(object):
141
144
    """A simple class for OpenPGP symmetric encryption & decryption"""
142
145
    def __init__(self):
143
 
        self.gnupg = GnuPGInterface.GnuPG()
144
146
        self.tempdir = tempfile.mkdtemp(prefix="mandos-")
145
 
        self.gnupg = GnuPGInterface.GnuPG()
146
 
        self.gnupg.options.meta_interactive = False
147
 
        self.gnupg.options.homedir = self.tempdir
148
 
        self.gnupg.options.extra_args.extend(['--force-mdc',
149
 
                                              '--quiet',
150
 
                                              '--no-use-agent'])
 
147
        self.gnupgargs = ['--batch',
 
148
                          '--home', self.tempdir,
 
149
                          '--force-mdc',
 
150
                          '--quiet',
 
151
                          '--no-use-agent']
151
152
    
152
153
    def __enter__(self):
153
154
        return self
175
176
    def password_encode(self, password):
176
177
        # Passphrase can not be empty and can not contain newlines or
177
178
        # NUL bytes.  So we prefix it and hex encode it.
178
 
        return b"mandos" + binascii.hexlify(password)
 
179
        encoded = b"mandos" + binascii.hexlify(password)
 
180
        if len(encoded) > 2048:
 
181
            # GnuPG can't handle long passwords, so encode differently
 
182
            encoded = (b"mandos" + password.replace(b"\\", b"\\\\")
 
183
                       .replace(b"\n", b"\\n")
 
184
                       .replace(b"\0", b"\\x00"))
 
185
        return encoded
179
186
    
180
187
    def encrypt(self, data, password):
181
 
        self.gnupg.passphrase = self.password_encode(password)
182
 
        with open(os.devnull, "w") as devnull:
183
 
            try:
184
 
                proc = self.gnupg.run(['--symmetric'],
185
 
                                      create_fhs=['stdin', 'stdout'],
186
 
                                      attach_fhs={'stderr': devnull})
187
 
                with contextlib.closing(proc.handles['stdin']) as f:
188
 
                    f.write(data)
189
 
                with contextlib.closing(proc.handles['stdout']) as f:
190
 
                    ciphertext = f.read()
191
 
                proc.wait()
192
 
            except IOError as e:
193
 
                raise PGPError(e)
194
 
        self.gnupg.passphrase = None
 
188
        passphrase = self.password_encode(password)
 
189
        with tempfile.NamedTemporaryFile(dir=self.tempdir
 
190
                                         ) as passfile:
 
191
            passfile.write(passphrase)
 
192
            passfile.flush()
 
193
            proc = subprocess.Popen(['gpg', '--symmetric',
 
194
                                     '--passphrase-file',
 
195
                                     passfile.name]
 
196
                                    + self.gnupgargs,
 
197
                                    stdin = subprocess.PIPE,
 
198
                                    stdout = subprocess.PIPE,
 
199
                                    stderr = subprocess.PIPE)
 
200
            ciphertext, err = proc.communicate(input = data)
 
201
        if proc.returncode != 0:
 
202
            raise PGPError(err)
195
203
        return ciphertext
196
204
    
197
205
    def decrypt(self, data, password):
198
 
        self.gnupg.passphrase = self.password_encode(password)
199
 
        with open(os.devnull, "w") as devnull:
200
 
            try:
201
 
                proc = self.gnupg.run(['--decrypt'],
202
 
                                      create_fhs=['stdin', 'stdout'],
203
 
                                      attach_fhs={'stderr': devnull})
204
 
                with contextlib.closing(proc.handles['stdin']) as f:
205
 
                    f.write(data)
206
 
                with contextlib.closing(proc.handles['stdout']) as f:
207
 
                    decrypted_plaintext = f.read()
208
 
                proc.wait()
209
 
            except IOError as e:
210
 
                raise PGPError(e)
211
 
        self.gnupg.passphrase = None
 
206
        passphrase = self.password_encode(password)
 
207
        with tempfile.NamedTemporaryFile(dir = self.tempdir
 
208
                                         ) as passfile:
 
209
            passfile.write(passphrase)
 
210
            passfile.flush()
 
211
            proc = subprocess.Popen(['gpg', '--decrypt',
 
212
                                     '--passphrase-file',
 
213
                                     passfile.name]
 
214
                                    + self.gnupgargs,
 
215
                                    stdin = subprocess.PIPE,
 
216
                                    stdout = subprocess.PIPE,
 
217
                                    stderr = subprocess.PIPE)
 
218
            decrypted_plaintext, err = proc.communicate(input
 
219
                                                        = data)
 
220
        if proc.returncode != 0:
 
221
            raise PGPError(err)
212
222
        return decrypted_plaintext
213
223
 
214
224
 
215
225
class AvahiError(Exception):
216
226
    def __init__(self, value, *args, **kwargs):
217
227
        self.value = value
218
 
        super(AvahiError, self).__init__(value, *args, **kwargs)
219
 
    def __unicode__(self):
220
 
        return unicode(repr(self.value))
 
228
        return super(AvahiError, self).__init__(value, *args,
 
229
                                                **kwargs)
221
230
 
222
231
class AvahiServiceError(AvahiError):
223
232
    pass
234
243
               Used to optionally bind to the specified interface.
235
244
    name: string; Example: 'Mandos'
236
245
    type: string; Example: '_mandos._tcp'.
237
 
                  See <http://www.dns-sd.org/ServiceTypes.html>
 
246
     See <https://www.iana.org/assignments/service-names-port-numbers>
238
247
    port: integer; what port to announce
239
248
    TXT: list of strings; TXT record for the service
240
249
    domain: string; Domain to publish on, default to .local if empty.
266
275
        self.bus = bus
267
276
        self.entry_group_state_changed_match = None
268
277
    
269
 
    def rename(self):
 
278
    def rename(self, remove=True):
270
279
        """Derived from the Avahi example code"""
271
280
        if self.rename_count >= self.max_renames:
272
281
            logger.critical("No suitable Zeroconf service name found"
273
282
                            " after %i retries, exiting.",
274
283
                            self.rename_count)
275
284
            raise AvahiServiceError("Too many renames")
276
 
        self.name = unicode(self.server
277
 
                            .GetAlternativeServiceName(self.name))
 
285
        self.name = str(self.server
 
286
                        .GetAlternativeServiceName(self.name))
 
287
        self.rename_count += 1
278
288
        logger.info("Changing Zeroconf service name to %r ...",
279
289
                    self.name)
280
 
        self.remove()
 
290
        if remove:
 
291
            self.remove()
281
292
        try:
282
293
            self.add()
283
294
        except dbus.exceptions.DBusException as error:
284
 
            logger.critical("D-Bus Exception", exc_info=error)
285
 
            self.cleanup()
286
 
            os._exit(1)
287
 
        self.rename_count += 1
 
295
            if (error.get_dbus_name()
 
296
                == "org.freedesktop.Avahi.CollisionError"):
 
297
                logger.info("Local Zeroconf service name collision.")
 
298
                return self.rename(remove=False)
 
299
            else:
 
300
                logger.critical("D-Bus Exception", exc_info=error)
 
301
                self.cleanup()
 
302
                os._exit(1)
288
303
    
289
304
    def remove(self):
290
305
        """Derived from the Avahi example code"""
328
343
            self.rename()
329
344
        elif state == avahi.ENTRY_GROUP_FAILURE:
330
345
            logger.critical("Avahi: Error in group state changed %s",
331
 
                            unicode(error))
332
 
            raise AvahiGroupError("State changed: {0!s}"
 
346
                            str(error))
 
347
            raise AvahiGroupError("State changed: {!s}"
333
348
                                  .format(error))
334
349
    
335
350
    def cleanup(self):
382
397
 
383
398
 
384
399
class AvahiServiceToSyslog(AvahiService):
385
 
    def rename(self):
 
400
    def rename(self, *args, **kwargs):
386
401
        """Add the new name to the syslog messages"""
387
 
        ret = AvahiService.rename(self)
 
402
        ret = AvahiService.rename(self, *args, **kwargs)
388
403
        syslogger.setFormatter(logging.Formatter
389
 
                               ('Mandos ({0}) [%(process)d]:'
 
404
                               ('Mandos ({}) [%(process)d]:'
390
405
                                ' %(levelname)s: %(message)s'
391
406
                                .format(self.name)))
392
407
        return ret
393
408
 
394
409
 
395
 
def timedelta_to_milliseconds(td):
396
 
    "Convert a datetime.timedelta() to milliseconds"
397
 
    return ((td.days * 24 * 60 * 60 * 1000)
398
 
            + (td.seconds * 1000)
399
 
            + (td.microseconds // 1000))
400
 
 
401
 
 
402
410
class Client(object):
403
411
    """A representation of a client host served by this server.
404
412
    
440
448
    runtime_expansions: Allowed attributes for runtime expansion.
441
449
    expires:    datetime.datetime(); time (UTC) when a client will be
442
450
                disabled, or None
 
451
    server_settings: The server_settings dict from main()
443
452
    """
444
453
    
445
454
    runtime_expansions = ("approval_delay", "approval_duration",
458
467
                        "enabled": "True",
459
468
                        }
460
469
    
461
 
    def timeout_milliseconds(self):
462
 
        "Return the 'timeout' attribute in milliseconds"
463
 
        return timedelta_to_milliseconds(self.timeout)
464
 
    
465
 
    def extended_timeout_milliseconds(self):
466
 
        "Return the 'extended_timeout' attribute in milliseconds"
467
 
        return timedelta_to_milliseconds(self.extended_timeout)
468
 
    
469
 
    def interval_milliseconds(self):
470
 
        "Return the 'interval' attribute in milliseconds"
471
 
        return timedelta_to_milliseconds(self.interval)
472
 
    
473
 
    def approval_delay_milliseconds(self):
474
 
        return timedelta_to_milliseconds(self.approval_delay)
475
 
    
476
470
    @staticmethod
477
471
    def config_parser(config):
478
472
        """Construct a new dict of client settings of this form:
493
487
            client["enabled"] = config.getboolean(client_name,
494
488
                                                  "enabled")
495
489
            
 
490
            # Uppercase and remove spaces from fingerprint for later
 
491
            # comparison purposes with return value from the
 
492
            # fingerprint() function
496
493
            client["fingerprint"] = (section["fingerprint"].upper()
497
494
                                     .replace(" ", ""))
498
495
            if "secret" in section:
503
500
                          "rb") as secfile:
504
501
                    client["secret"] = secfile.read()
505
502
            else:
506
 
                raise TypeError("No secret or secfile for section {0}"
 
503
                raise TypeError("No secret or secfile for section {}"
507
504
                                .format(section))
508
505
            client["timeout"] = string_to_delta(section["timeout"])
509
506
            client["extended_timeout"] = string_to_delta(
520
517
        
521
518
        return settings
522
519
    
523
 
    def __init__(self, settings, name = None):
 
520
    def __init__(self, settings, name = None, server_settings=None):
524
521
        self.name = name
 
522
        if server_settings is None:
 
523
            server_settings = {}
 
524
        self.server_settings = server_settings
525
525
        # adding all client settings
526
 
        for setting, value in settings.iteritems():
 
526
        for setting, value in settings.items():
527
527
            setattr(self, setting, value)
528
528
        
529
529
        if self.enabled:
537
537
            self.expires = None
538
538
        
539
539
        logger.debug("Creating client %r", self.name)
540
 
        # Uppercase and remove spaces from fingerprint for later
541
 
        # comparison purposes with return value from the fingerprint()
542
 
        # function
543
540
        logger.debug("  Fingerprint: %s", self.fingerprint)
544
541
        self.created = settings.get("created",
545
542
                                    datetime.datetime.utcnow())
612
609
        if self.checker_initiator_tag is not None:
613
610
            gobject.source_remove(self.checker_initiator_tag)
614
611
        self.checker_initiator_tag = (gobject.timeout_add
615
 
                                      (self.interval_milliseconds(),
 
612
                                      (int(self.interval
 
613
                                           .total_seconds() * 1000),
616
614
                                       self.start_checker))
617
615
        # Schedule a disable() when 'timeout' has passed
618
616
        if self.disable_initiator_tag is not None:
619
617
            gobject.source_remove(self.disable_initiator_tag)
620
618
        self.disable_initiator_tag = (gobject.timeout_add
621
 
                                   (self.timeout_milliseconds(),
622
 
                                    self.disable))
 
619
                                      (int(self.timeout
 
620
                                           .total_seconds() * 1000),
 
621
                                       self.disable))
623
622
        # Also start a new checker *right now*.
624
623
        self.start_checker()
625
624
    
656
655
            self.disable_initiator_tag = None
657
656
        if getattr(self, "enabled", False):
658
657
            self.disable_initiator_tag = (gobject.timeout_add
659
 
                                          (timedelta_to_milliseconds
660
 
                                           (timeout), self.disable))
 
658
                                          (int(timeout.total_seconds()
 
659
                                               * 1000), self.disable))
661
660
            self.expires = datetime.datetime.utcnow() + timeout
662
661
    
663
662
    def need_approval(self):
680
679
        # If a checker exists, make sure it is not a zombie
681
680
        try:
682
681
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
683
 
        except (AttributeError, OSError) as error:
684
 
            if (isinstance(error, OSError)
685
 
                and error.errno != errno.ECHILD):
686
 
                raise error
 
682
        except AttributeError:
 
683
            pass
 
684
        except OSError as error:
 
685
            if error.errno != errno.ECHILD:
 
686
                raise
687
687
        else:
688
688
            if pid:
689
689
                logger.warning("Checker was a zombie")
693
693
        # Start a new checker if needed
694
694
        if self.checker is None:
695
695
            # Escape attributes for the shell
696
 
            escaped_attrs = dict(
697
 
                (attr, re.escape(unicode(getattr(self, attr))))
698
 
                for attr in
699
 
                self.runtime_expansions)
 
696
            escaped_attrs = { attr:
 
697
                                  re.escape(str(getattr(self, attr)))
 
698
                              for attr in self.runtime_expansions }
700
699
            try:
701
700
                command = self.checker_command % escaped_attrs
702
701
            except TypeError as error:
711
710
                # in normal mode, that is already done by daemon(),
712
711
                # and in debug mode we don't want to.  (Stdin is
713
712
                # always replaced by /dev/null.)
 
713
                # The exception is when not debugging but nevertheless
 
714
                # running in the foreground; use the previously
 
715
                # created wnull.
 
716
                popen_args = {}
 
717
                if (not self.server_settings["debug"]
 
718
                    and self.server_settings["foreground"]):
 
719
                    popen_args.update({"stdout": wnull,
 
720
                                       "stderr": wnull })
714
721
                self.checker = subprocess.Popen(command,
715
722
                                                close_fds=True,
716
 
                                                shell=True, cwd="/")
 
723
                                                shell=True, cwd="/",
 
724
                                                **popen_args)
717
725
            except OSError as error:
718
726
                logger.error("Failed to start subprocess",
719
727
                             exc_info=error)
728
736
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
729
737
            except OSError as error:
730
738
                if error.errno == errno.ECHILD:
731
 
                    logger.error("Child process vanished", exc_info=error)
 
739
                    # This should never happen
 
740
                    logger.error("Child process vanished",
 
741
                                 exc_info=error)
732
742
                    return True
733
743
                raise
734
744
            if pid:
772
782
    # "Set" method, so we fail early here:
773
783
    if byte_arrays and signature != "ay":
774
784
        raise ValueError("Byte arrays not supported for non-'ay'"
775
 
                         " signature {0!r}".format(signature))
 
785
                         " signature {!r}".format(signature))
776
786
    def decorator(func):
777
787
        func._dbus_is_property = True
778
788
        func._dbus_interface = dbus_interface
809
819
    """Decorator to annotate D-Bus methods, signals or properties
810
820
    Usage:
811
821
    
 
822
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
 
823
                       "org.freedesktop.DBus.Property."
 
824
                       "EmitsChangedSignal": "false"})
812
825
    @dbus_service_property("org.example.Interface", signature="b",
813
826
                           access="r")
814
 
    @dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
815
 
                        "org.freedesktop.DBus.Property."
816
 
                        "EmitsChangedSignal": "false"})
817
827
    def Property_dbus_property(self):
818
828
        return dbus.Boolean(False)
819
829
    """
826
836
class DBusPropertyException(dbus.exceptions.DBusException):
827
837
    """A base class for D-Bus property-related exceptions
828
838
    """
829
 
    def __unicode__(self):
830
 
        return unicode(str(self))
831
 
 
 
839
    pass
832
840
 
833
841
class DBusPropertyAccessException(DBusPropertyException):
834
842
    """A property's access permissions disallows an operation.
857
865
        If called like _is_dbus_thing("method") it returns a function
858
866
        suitable for use as predicate to inspect.getmembers().
859
867
        """
860
 
        return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
 
868
        return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
861
869
                                   False)
862
870
    
863
871
    def _get_all_dbus_things(self, thing):
912
920
            # The byte_arrays option is not supported yet on
913
921
            # signatures other than "ay".
914
922
            if prop._dbus_signature != "ay":
915
 
                raise ValueError
 
923
                raise ValueError("Byte arrays not supported for non-"
 
924
                                 "'ay' signature {!r}"
 
925
                                 .format(prop._dbus_signature))
916
926
            value = dbus.ByteArray(b''.join(chr(byte)
917
927
                                            for byte in value))
918
928
        prop(value)
942
952
                                           value.variant_level+1)
943
953
        return dbus.Dictionary(properties, signature="sv")
944
954
    
 
955
    @dbus.service.signal(dbus.PROPERTIES_IFACE, signature="sa{sv}as")
 
956
    def PropertiesChanged(self, interface_name, changed_properties,
 
957
                          invalidated_properties):
 
958
        """Standard D-Bus PropertiesChanged() signal, see D-Bus
 
959
        standard.
 
960
        """
 
961
        pass
 
962
    
945
963
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
946
964
                         out_signature="s",
947
965
                         path_keyword='object_path',
982
1000
                                              (prop,
983
1001
                                               "_dbus_annotations",
984
1002
                                               {}))
985
 
                        for name, value in annots.iteritems():
 
1003
                        for name, value in annots.items():
986
1004
                            ann_tag = document.createElement(
987
1005
                                "annotation")
988
1006
                            ann_tag.setAttribute("name", name)
991
1009
                # Add interface annotation tags
992
1010
                for annotation, value in dict(
993
1011
                    itertools.chain.from_iterable(
994
 
                        annotations().iteritems()
 
1012
                        annotations().items()
995
1013
                        for name, annotations in
996
1014
                        self._get_all_dbus_things("interface")
997
1015
                        if name == if_tag.getAttribute("name")
998
 
                        )).iteritems():
 
1016
                        )).items():
999
1017
                    ann_tag = document.createElement("annotation")
1000
1018
                    ann_tag.setAttribute("name", annotation)
1001
1019
                    ann_tag.setAttribute("value", value)
1057
1075
    """
1058
1076
    def wrapper(cls):
1059
1077
        for orig_interface_name, alt_interface_name in (
1060
 
            alt_interface_names.iteritems()):
 
1078
            alt_interface_names.items()):
1061
1079
            attr = {}
1062
1080
            interface_names = set()
1063
1081
            # Go though all attributes of the class
1076
1094
                interface_names.add(alt_interface)
1077
1095
                # Is this a D-Bus signal?
1078
1096
                if getattr(attribute, "_dbus_is_signal", False):
1079
 
                    # Extract the original non-method function by
1080
 
                    # black magic
 
1097
                    # Extract the original non-method undecorated
 
1098
                    # function by black magic
1081
1099
                    nonmethod_func = (dict(
1082
1100
                            zip(attribute.func_code.co_freevars,
1083
1101
                                attribute.__closure__))["func"]
1180
1198
                                        attribute.func_closure)))
1181
1199
            if deprecate:
1182
1200
                # Deprecate all alternate interfaces
1183
 
                iname="_AlternateDBusNames_interface_annotation{0}"
 
1201
                iname="_AlternateDBusNames_interface_annotation{}"
1184
1202
                for interface_name in interface_names:
1185
1203
                    @dbus_interface_annotations(interface_name)
1186
1204
                    def func(self):
1195
1213
            if interface_names:
1196
1214
                # Replace the class with a new subclass of it with
1197
1215
                # methods, signals, etc. as created above.
1198
 
                cls = type(b"{0}Alternate".format(cls.__name__),
 
1216
                cls = type(b"{}Alternate".format(cls.__name__),
1199
1217
                           (cls,), attr)
1200
1218
        return cls
1201
1219
    return wrapper
1214
1232
    runtime_expansions = (Client.runtime_expansions
1215
1233
                          + ("dbus_object_path",))
1216
1234
    
 
1235
    _interface = "se.recompile.Mandos.Client"
 
1236
    
1217
1237
    # dbus.service.Object doesn't use super(), so we can't either.
1218
1238
    
1219
1239
    def __init__(self, bus = None, *args, **kwargs):
1221
1241
        Client.__init__(self, *args, **kwargs)
1222
1242
        # Only now, when this client is initialized, can it show up on
1223
1243
        # the D-Bus
1224
 
        client_object_name = unicode(self.name).translate(
 
1244
        client_object_name = str(self.name).translate(
1225
1245
            {ord("."): ord("_"),
1226
1246
             ord("-"): ord("_")})
1227
1247
        self.dbus_object_path = (dbus.ObjectPath
1231
1251
    
1232
1252
    def notifychangeproperty(transform_func,
1233
1253
                             dbus_name, type_func=lambda x: x,
1234
 
                             variant_level=1):
 
1254
                             variant_level=1, invalidate_only=False,
 
1255
                             _interface=_interface):
1235
1256
        """ Modify a variable so that it's a property which announces
1236
1257
        its changes to DBus.
1237
1258
        
1242
1263
                   to the D-Bus.  Default: no transform
1243
1264
        variant_level: D-Bus variant level.  Default: 1
1244
1265
        """
1245
 
        attrname = "_{0}".format(dbus_name)
 
1266
        attrname = "_{}".format(dbus_name)
1246
1267
        def setter(self, value):
1247
1268
            if hasattr(self, "dbus_object_path"):
1248
1269
                if (not hasattr(self, attrname) or
1249
1270
                    type_func(getattr(self, attrname, None))
1250
1271
                    != type_func(value)):
1251
 
                    dbus_value = transform_func(type_func(value),
1252
 
                                                variant_level
1253
 
                                                =variant_level)
1254
 
                    self.PropertyChanged(dbus.String(dbus_name),
1255
 
                                         dbus_value)
 
1272
                    if invalidate_only:
 
1273
                        self.PropertiesChanged(_interface,
 
1274
                                               dbus.Dictionary(),
 
1275
                                               dbus.Array
 
1276
                                               ((dbus_name,)))
 
1277
                    else:
 
1278
                        dbus_value = transform_func(type_func(value),
 
1279
                                                    variant_level
 
1280
                                                    =variant_level)
 
1281
                        self.PropertyChanged(dbus.String(dbus_name),
 
1282
                                             dbus_value)
 
1283
                        self.PropertiesChanged(_interface,
 
1284
                                               dbus.Dictionary({
 
1285
                                    dbus.String(dbus_name):
 
1286
                                        dbus_value }), dbus.Array())
1256
1287
            setattr(self, attrname, value)
1257
1288
        
1258
1289
        return property(lambda self: getattr(self, attrname), setter)
1278
1309
    approval_delay = notifychangeproperty(dbus.UInt64,
1279
1310
                                          "ApprovalDelay",
1280
1311
                                          type_func =
1281
 
                                          timedelta_to_milliseconds)
 
1312
                                          lambda td: td.total_seconds()
 
1313
                                          * 1000)
1282
1314
    approval_duration = notifychangeproperty(
1283
1315
        dbus.UInt64, "ApprovalDuration",
1284
 
        type_func = timedelta_to_milliseconds)
 
1316
        type_func = lambda td: td.total_seconds() * 1000)
1285
1317
    host = notifychangeproperty(dbus.String, "Host")
1286
1318
    timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1287
 
                                   type_func =
1288
 
                                   timedelta_to_milliseconds)
 
1319
                                   type_func = lambda td:
 
1320
                                       td.total_seconds() * 1000)
1289
1321
    extended_timeout = notifychangeproperty(
1290
1322
        dbus.UInt64, "ExtendedTimeout",
1291
 
        type_func = timedelta_to_milliseconds)
 
1323
        type_func = lambda td: td.total_seconds() * 1000)
1292
1324
    interval = notifychangeproperty(dbus.UInt64,
1293
1325
                                    "Interval",
1294
1326
                                    type_func =
1295
 
                                    timedelta_to_milliseconds)
 
1327
                                    lambda td: td.total_seconds()
 
1328
                                    * 1000)
1296
1329
    checker_command = notifychangeproperty(dbus.String, "Checker")
 
1330
    secret = notifychangeproperty(dbus.ByteArray, "Secret",
 
1331
                                  invalidate_only=True)
1297
1332
    
1298
1333
    del notifychangeproperty
1299
1334
    
1326
1361
                                       *args, **kwargs)
1327
1362
    
1328
1363
    def start_checker(self, *args, **kwargs):
1329
 
        old_checker = self.checker
1330
 
        if self.checker is not None:
1331
 
            old_checker_pid = self.checker.pid
1332
 
        else:
1333
 
            old_checker_pid = None
 
1364
        old_checker_pid = getattr(self.checker, "pid", None)
1334
1365
        r = Client.start_checker(self, *args, **kwargs)
1335
1366
        # Only if new checker process was started
1336
1367
        if (self.checker is not None
1345
1376
    
1346
1377
    def approve(self, value=True):
1347
1378
        self.approved = value
1348
 
        gobject.timeout_add(timedelta_to_milliseconds
1349
 
                            (self.approval_duration),
1350
 
                            self._reset_approved)
 
1379
        gobject.timeout_add(int(self.approval_duration.total_seconds()
 
1380
                                * 1000), self._reset_approved)
1351
1381
        self.send_changedstate()
1352
1382
    
1353
1383
    ## D-Bus methods, signals & properties
1354
 
    _interface = "se.recompile.Mandos.Client"
1355
1384
    
1356
1385
    ## Interfaces
1357
1386
    
1358
 
    @dbus_interface_annotations(_interface)
1359
 
    def _foo(self):
1360
 
        return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
1361
 
                     "false"}
1362
 
    
1363
1387
    ## Signals
1364
1388
    
1365
1389
    # CheckerCompleted - signal
1375
1399
        pass
1376
1400
    
1377
1401
    # PropertyChanged - signal
 
1402
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1378
1403
    @dbus.service.signal(_interface, signature="sv")
1379
1404
    def PropertyChanged(self, property, value):
1380
1405
        "D-Bus signal"
1456
1481
                           access="readwrite")
1457
1482
    def ApprovalDelay_dbus_property(self, value=None):
1458
1483
        if value is None:       # get
1459
 
            return dbus.UInt64(self.approval_delay_milliseconds())
 
1484
            return dbus.UInt64(self.approval_delay.total_seconds()
 
1485
                               * 1000)
1460
1486
        self.approval_delay = datetime.timedelta(0, 0, 0, value)
1461
1487
    
1462
1488
    # ApprovalDuration - property
1464
1490
                           access="readwrite")
1465
1491
    def ApprovalDuration_dbus_property(self, value=None):
1466
1492
        if value is None:       # get
1467
 
            return dbus.UInt64(timedelta_to_milliseconds(
1468
 
                    self.approval_duration))
 
1493
            return dbus.UInt64(self.approval_duration.total_seconds()
 
1494
                               * 1000)
1469
1495
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
1470
1496
    
1471
1497
    # Name - property
1484
1510
    def Host_dbus_property(self, value=None):
1485
1511
        if value is None:       # get
1486
1512
            return dbus.String(self.host)
1487
 
        self.host = unicode(value)
 
1513
        self.host = str(value)
1488
1514
    
1489
1515
    # Created - property
1490
1516
    @dbus_service_property(_interface, signature="s", access="read")
1537
1563
                           access="readwrite")
1538
1564
    def Timeout_dbus_property(self, value=None):
1539
1565
        if value is None:       # get
1540
 
            return dbus.UInt64(self.timeout_milliseconds())
 
1566
            return dbus.UInt64(self.timeout.total_seconds() * 1000)
1541
1567
        old_timeout = self.timeout
1542
1568
        self.timeout = datetime.timedelta(0, 0, 0, value)
1543
1569
        # Reschedule disabling
1554
1580
                gobject.source_remove(self.disable_initiator_tag)
1555
1581
                self.disable_initiator_tag = (
1556
1582
                    gobject.timeout_add(
1557
 
                        timedelta_to_milliseconds(self.expires - now),
1558
 
                        self.disable))
 
1583
                        int((self.expires - now).total_seconds()
 
1584
                            * 1000), self.disable))
1559
1585
    
1560
1586
    # ExtendedTimeout - property
1561
1587
    @dbus_service_property(_interface, signature="t",
1562
1588
                           access="readwrite")
1563
1589
    def ExtendedTimeout_dbus_property(self, value=None):
1564
1590
        if value is None:       # get
1565
 
            return dbus.UInt64(self.extended_timeout_milliseconds())
 
1591
            return dbus.UInt64(self.extended_timeout.total_seconds()
 
1592
                               * 1000)
1566
1593
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1567
1594
    
1568
1595
    # Interval - property
1570
1597
                           access="readwrite")
1571
1598
    def Interval_dbus_property(self, value=None):
1572
1599
        if value is None:       # get
1573
 
            return dbus.UInt64(self.interval_milliseconds())
 
1600
            return dbus.UInt64(self.interval.total_seconds() * 1000)
1574
1601
        self.interval = datetime.timedelta(0, 0, 0, value)
1575
1602
        if getattr(self, "checker_initiator_tag", None) is None:
1576
1603
            return
1587
1614
    def Checker_dbus_property(self, value=None):
1588
1615
        if value is None:       # get
1589
1616
            return dbus.String(self.checker_command)
1590
 
        self.checker_command = unicode(value)
 
1617
        self.checker_command = str(value)
1591
1618
    
1592
1619
    # CheckerRunning - property
1593
1620
    @dbus_service_property(_interface, signature="b",
1609
1636
    @dbus_service_property(_interface, signature="ay",
1610
1637
                           access="write", byte_arrays=True)
1611
1638
    def Secret_dbus_property(self, value):
1612
 
        self.secret = str(value)
 
1639
        self.secret = bytes(value)
1613
1640
    
1614
1641
    del _interface
1615
1642
 
1649
1676
    def handle(self):
1650
1677
        with contextlib.closing(self.server.child_pipe) as child_pipe:
1651
1678
            logger.info("TCP connection from: %s",
1652
 
                        unicode(self.client_address))
 
1679
                        str(self.client_address))
1653
1680
            logger.debug("Pipe FD: %d",
1654
1681
                         self.server.child_pipe.fileno())
1655
1682
            
1681
1708
            logger.debug("Protocol version: %r", line)
1682
1709
            try:
1683
1710
                if int(line.strip().split()[0]) > 1:
1684
 
                    raise RuntimeError
 
1711
                    raise RuntimeError(line)
1685
1712
            except (ValueError, IndexError, RuntimeError) as error:
1686
1713
                logger.error("Unknown protocol version: %s", error)
1687
1714
                return
1736
1763
                        if self.server.use_dbus:
1737
1764
                            # Emit D-Bus signal
1738
1765
                            client.NeedApproval(
1739
 
                                client.approval_delay_milliseconds(),
1740
 
                                client.approved_by_default)
 
1766
                                client.approval_delay.total_seconds()
 
1767
                                * 1000, client.approved_by_default)
1741
1768
                    else:
1742
1769
                        logger.warning("Client %s was not approved",
1743
1770
                                       client.name)
1749
1776
                    #wait until timeout or approved
1750
1777
                    time = datetime.datetime.now()
1751
1778
                    client.changedstate.acquire()
1752
 
                    client.changedstate.wait(
1753
 
                        float(timedelta_to_milliseconds(delay)
1754
 
                              / 1000))
 
1779
                    client.changedstate.wait(delay.total_seconds())
1755
1780
                    client.changedstate.release()
1756
1781
                    time2 = datetime.datetime.now()
1757
1782
                    if (time2 - time) >= delay:
1894
1919
    
1895
1920
    def add_pipe(self, parent_pipe, proc):
1896
1921
        """Dummy function; override as necessary"""
1897
 
        raise NotImplementedError
 
1922
        raise NotImplementedError()
1898
1923
 
1899
1924
 
1900
1925
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1956
1981
                try:
1957
1982
                    self.socket.setsockopt(socket.SOL_SOCKET,
1958
1983
                                           SO_BINDTODEVICE,
1959
 
                                           str(self.interface + '\0'))
 
1984
                                           (self.interface + "\0")
 
1985
                                           .encode("utf-8"))
1960
1986
                except socket.error as error:
1961
1987
                    if error.errno == errno.EPERM:
1962
1988
                        logger.error("No permission to bind to"
1976
2002
                if self.address_family == socket.AF_INET6:
1977
2003
                    any_address = "::" # in6addr_any
1978
2004
                else:
1979
 
                    any_address = socket.INADDR_ANY
 
2005
                    any_address = "0.0.0.0" # INADDR_ANY
1980
2006
                self.server_address = (any_address,
1981
2007
                                       self.server_address[1])
1982
2008
            elif not self.server_address[1]:
2165
2191
    token_duration = Token(re.compile(r"P"), None,
2166
2192
                           frozenset((token_year, token_month,
2167
2193
                                      token_day, token_time,
2168
 
                                      token_week))),
 
2194
                                      token_week)))
2169
2195
    # Define starting values
2170
2196
    value = datetime.timedelta() # Value so far
2171
2197
    found_token = None
2172
 
    followers = frozenset(token_duration,) # Following valid tokens
 
2198
    followers = frozenset((token_duration,)) # Following valid tokens
2173
2199
    s = duration                # String left to parse
2174
2200
    # Loop until end token is found
2175
2201
    while found_token is not token_end:
2222
2248
    timevalue = datetime.timedelta(0)
2223
2249
    for s in interval.split():
2224
2250
        try:
2225
 
            suffix = unicode(s[-1])
 
2251
            suffix = s[-1]
2226
2252
            value = int(s[:-1])
2227
2253
            if suffix == "d":
2228
2254
                delta = datetime.timedelta(value)
2235
2261
            elif suffix == "w":
2236
2262
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
2237
2263
            else:
2238
 
                raise ValueError("Unknown suffix {0!r}"
 
2264
                raise ValueError("Unknown suffix {!r}"
2239
2265
                                 .format(suffix))
2240
 
        except (ValueError, IndexError) as e:
 
2266
        except IndexError as e:
2241
2267
            raise ValueError(*(e.args))
2242
2268
        timevalue += delta
2243
2269
    return timevalue
2258
2284
        # Close all standard open file descriptors
2259
2285
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2260
2286
        if not stat.S_ISCHR(os.fstat(null).st_mode):
2261
 
            raise OSError(errno.ENODEV,
2262
 
                          "{0} not a character device"
 
2287
            raise OSError(errno.ENODEV, "{} not a character device"
2263
2288
                          .format(os.devnull))
2264
2289
        os.dup2(null, sys.stdin.fileno())
2265
2290
        os.dup2(null, sys.stdout.fileno())
2275
2300
    
2276
2301
    parser = argparse.ArgumentParser()
2277
2302
    parser.add_argument("-v", "--version", action="version",
2278
 
                        version = "%(prog)s {0}".format(version),
 
2303
                        version = "%(prog)s {}".format(version),
2279
2304
                        help="show version number and exit")
2280
2305
    parser.add_argument("-i", "--interface", metavar="IF",
2281
2306
                        help="Bind to interface IF")
2287
2312
                        help="Run self-test")
2288
2313
    parser.add_argument("--debug", action="store_true",
2289
2314
                        help="Debug mode; run in foreground and log"
2290
 
                        " to terminal")
 
2315
                        " to terminal", default=None)
2291
2316
    parser.add_argument("--debuglevel", metavar="LEVEL",
2292
2317
                        help="Debug level for stdout output")
2293
2318
    parser.add_argument("--priority", help="GnuTLS"
2300
2325
                        " files")
2301
2326
    parser.add_argument("--no-dbus", action="store_false",
2302
2327
                        dest="use_dbus", help="Do not provide D-Bus"
2303
 
                        " system bus interface")
 
2328
                        " system bus interface", default=None)
2304
2329
    parser.add_argument("--no-ipv6", action="store_false",
2305
 
                        dest="use_ipv6", help="Do not use IPv6")
 
2330
                        dest="use_ipv6", help="Do not use IPv6",
 
2331
                        default=None)
2306
2332
    parser.add_argument("--no-restore", action="store_false",
2307
2333
                        dest="restore", help="Do not restore stored"
2308
 
                        " state")
 
2334
                        " state", default=None)
2309
2335
    parser.add_argument("--socket", type=int,
2310
2336
                        help="Specify a file descriptor to a network"
2311
2337
                        " socket to use instead of creating one")
2312
2338
    parser.add_argument("--statedir", metavar="DIR",
2313
2339
                        help="Directory to save/restore state in")
2314
2340
    parser.add_argument("--foreground", action="store_true",
2315
 
                        help="Run in foreground")
 
2341
                        help="Run in foreground", default=None)
 
2342
    parser.add_argument("--no-zeroconf", action="store_false",
 
2343
                        dest="zeroconf", help="Do not use Zeroconf",
 
2344
                        default=None)
2316
2345
    
2317
2346
    options = parser.parse_args()
2318
2347
    
2319
2348
    if options.check:
2320
2349
        import doctest
2321
 
        doctest.testmod()
2322
 
        sys.exit()
 
2350
        fail_count, test_count = doctest.testmod()
 
2351
        sys.exit(os.EX_OK if fail_count == 0 else 1)
2323
2352
    
2324
2353
    # Default values for config file for server-global settings
2325
2354
    server_defaults = { "interface": "",
2327
2356
                        "port": "",
2328
2357
                        "debug": "False",
2329
2358
                        "priority":
2330
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
 
2359
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
2331
2360
                        "servicename": "Mandos",
2332
2361
                        "use_dbus": "True",
2333
2362
                        "use_ipv6": "True",
2336
2365
                        "socket": "",
2337
2366
                        "statedir": "/var/lib/mandos",
2338
2367
                        "foreground": "False",
 
2368
                        "zeroconf": "True",
2339
2369
                        }
2340
2370
    
2341
2371
    # Parse config file for server-global settings
2368
2398
    for option in ("interface", "address", "port", "debug",
2369
2399
                   "priority", "servicename", "configdir",
2370
2400
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
2371
 
                   "statedir", "socket", "foreground"):
 
2401
                   "statedir", "socket", "foreground", "zeroconf"):
2372
2402
        value = getattr(options, option)
2373
2403
        if value is not None:
2374
2404
            server_settings[option] = value
2375
2405
    del options
2376
2406
    # Force all strings to be unicode
2377
2407
    for option in server_settings.keys():
2378
 
        if type(server_settings[option]) is str:
2379
 
            server_settings[option] = unicode(server_settings[option])
 
2408
        if isinstance(server_settings[option], bytes):
 
2409
            server_settings[option] = (server_settings[option]
 
2410
                                       .decode("utf-8"))
 
2411
    # Force all boolean options to be boolean
 
2412
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
 
2413
                   "foreground", "zeroconf"):
 
2414
        server_settings[option] = bool(server_settings[option])
2380
2415
    # Debug implies foreground
2381
2416
    if server_settings["debug"]:
2382
2417
        server_settings["foreground"] = True
2384
2419
    
2385
2420
    ##################################################################
2386
2421
    
 
2422
    if (not server_settings["zeroconf"] and
 
2423
        not (server_settings["port"]
 
2424
             or server_settings["socket"] != "")):
 
2425
            parser.error("Needs port or socket to work without"
 
2426
                         " Zeroconf")
 
2427
    
2387
2428
    # For convenience
2388
2429
    debug = server_settings["debug"]
2389
2430
    debuglevel = server_settings["debuglevel"]
2392
2433
    stored_state_path = os.path.join(server_settings["statedir"],
2393
2434
                                     stored_state_file)
2394
2435
    foreground = server_settings["foreground"]
 
2436
    zeroconf = server_settings["zeroconf"]
2395
2437
    
2396
2438
    if debug:
2397
2439
        initlogger(debug, logging.DEBUG)
2404
2446
    
2405
2447
    if server_settings["servicename"] != "Mandos":
2406
2448
        syslogger.setFormatter(logging.Formatter
2407
 
                               ('Mandos ({0}) [%(process)d]:'
 
2449
                               ('Mandos ({}) [%(process)d]:'
2408
2450
                                ' %(levelname)s: %(message)s'
2409
2451
                                .format(server_settings
2410
2452
                                        ["servicename"])))
2418
2460
    global mandos_dbus_service
2419
2461
    mandos_dbus_service = None
2420
2462
    
 
2463
    socketfd = None
 
2464
    if server_settings["socket"] != "":
 
2465
        socketfd = server_settings["socket"]
2421
2466
    tcp_server = MandosServer((server_settings["address"],
2422
2467
                               server_settings["port"]),
2423
2468
                              ClientHandler,
2427
2472
                              gnutls_priority=
2428
2473
                              server_settings["priority"],
2429
2474
                              use_dbus=use_dbus,
2430
 
                              socketfd=(server_settings["socket"]
2431
 
                                        or None))
 
2475
                              socketfd=socketfd)
2432
2476
    if not foreground:
2433
 
        pidfilename = "/var/run/mandos.pid"
 
2477
        pidfilename = "/run/mandos.pid"
 
2478
        if not os.path.isdir("/run/."):
 
2479
            pidfilename = "/var/run/mandos.pid"
2434
2480
        pidfile = None
2435
2481
        try:
2436
2482
            pidfile = open(pidfilename, "w")
2453
2499
        os.setuid(uid)
2454
2500
    except OSError as error:
2455
2501
        if error.errno != errno.EPERM:
2456
 
            raise error
 
2502
            raise
2457
2503
    
2458
2504
    if debug:
2459
2505
        # Enable all possible GnuTLS debugging
2502
2548
            use_dbus = False
2503
2549
            server_settings["use_dbus"] = False
2504
2550
            tcp_server.use_dbus = False
2505
 
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2506
 
    service = AvahiServiceToSyslog(name =
2507
 
                                   server_settings["servicename"],
2508
 
                                   servicetype = "_mandos._tcp",
2509
 
                                   protocol = protocol, bus = bus)
2510
 
    if server_settings["interface"]:
2511
 
        service.interface = (if_nametoindex
2512
 
                             (str(server_settings["interface"])))
 
2551
    if zeroconf:
 
2552
        protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
 
2553
        service = AvahiServiceToSyslog(name =
 
2554
                                       server_settings["servicename"],
 
2555
                                       servicetype = "_mandos._tcp",
 
2556
                                       protocol = protocol, bus = bus)
 
2557
        if server_settings["interface"]:
 
2558
            service.interface = (if_nametoindex
 
2559
                                 (server_settings["interface"]
 
2560
                                  .encode("utf-8")))
2513
2561
    
2514
2562
    global multiprocessing_manager
2515
2563
    multiprocessing_manager = multiprocessing.Manager()
2522
2570
    old_client_settings = {}
2523
2571
    clients_data = {}
2524
2572
    
 
2573
    # This is used to redirect stdout and stderr for checker processes
 
2574
    global wnull
 
2575
    wnull = open(os.devnull, "w") # A writable /dev/null
 
2576
    # Only used if server is running in foreground but not in debug
 
2577
    # mode
 
2578
    if debug or not foreground:
 
2579
        wnull.close()
 
2580
    
2525
2581
    # Get client data and settings from last running state.
2526
2582
    if server_settings["restore"]:
2527
2583
        try:
2531
2587
            os.remove(stored_state_path)
2532
2588
        except IOError as e:
2533
2589
            if e.errno == errno.ENOENT:
2534
 
                logger.warning("Could not load persistent state: {0}"
 
2590
                logger.warning("Could not load persistent state: {}"
2535
2591
                                .format(os.strerror(e.errno)))
2536
2592
            else:
2537
2593
                logger.critical("Could not load persistent state:",
2542
2598
                           "EOFError:", exc_info=e)
2543
2599
    
2544
2600
    with PGPEngine() as pgp:
2545
 
        for client_name, client in clients_data.iteritems():
 
2601
        for client_name, client in clients_data.items():
 
2602
            # Skip removed clients
 
2603
            if client_name not in client_settings:
 
2604
                continue
 
2605
            
2546
2606
            # Decide which value to use after restoring saved state.
2547
2607
            # We have three different values: Old config file,
2548
2608
            # new config file, and saved state.
2569
2629
                if datetime.datetime.utcnow() >= client["expires"]:
2570
2630
                    if not client["last_checked_ok"]:
2571
2631
                        logger.warning(
2572
 
                            "disabling client {0} - Client never "
 
2632
                            "disabling client {} - Client never "
2573
2633
                            "performed a successful checker"
2574
2634
                            .format(client_name))
2575
2635
                        client["enabled"] = False
2576
2636
                    elif client["last_checker_status"] != 0:
2577
2637
                        logger.warning(
2578
 
                            "disabling client {0} - Client "
2579
 
                            "last checker failed with error code {1}"
 
2638
                            "disabling client {} - Client last"
 
2639
                            " checker failed with error code {}"
2580
2640
                            .format(client_name,
2581
2641
                                    client["last_checker_status"]))
2582
2642
                        client["enabled"] = False
2585
2645
                                             .utcnow()
2586
2646
                                             + client["timeout"])
2587
2647
                        logger.debug("Last checker succeeded,"
2588
 
                                     " keeping {0} enabled"
 
2648
                                     " keeping {} enabled"
2589
2649
                                     .format(client_name))
2590
2650
            try:
2591
2651
                client["secret"] = (
2594
2654
                                ["secret"]))
2595
2655
            except PGPError:
2596
2656
                # If decryption fails, we use secret from new settings
2597
 
                logger.debug("Failed to decrypt {0} old secret"
 
2657
                logger.debug("Failed to decrypt {} old secret"
2598
2658
                             .format(client_name))
2599
2659
                client["secret"] = (
2600
2660
                    client_settings[client_name]["secret"])
2608
2668
        clients_data[client_name] = client_settings[client_name]
2609
2669
    
2610
2670
    # Create all client objects
2611
 
    for client_name, client in clients_data.iteritems():
 
2671
    for client_name, client in clients_data.items():
2612
2672
        tcp_server.clients[client_name] = client_class(
2613
 
            name = client_name, settings = client)
 
2673
            name = client_name, settings = client,
 
2674
            server_settings = server_settings)
2614
2675
    
2615
2676
    if not tcp_server.clients:
2616
2677
        logger.warning("No clients defined")
2620
2681
            try:
2621
2682
                with pidfile:
2622
2683
                    pid = os.getpid()
2623
 
                    pidfile.write(str(pid) + "\n".encode("utf-8"))
 
2684
                    pidfile.write("{}\n".format(pid).encode("utf-8"))
2624
2685
            except IOError:
2625
2686
                logger.error("Could not write to file %r with PID %d",
2626
2687
                             pidfilename, pid)
2672
2733
            def GetAllClientsWithProperties(self):
2673
2734
                "D-Bus method"
2674
2735
                return dbus.Dictionary(
2675
 
                    ((c.dbus_object_path, c.GetAll(""))
2676
 
                     for c in tcp_server.clients.itervalues()),
 
2736
                    { c.dbus_object_path: c.GetAll("")
 
2737
                      for c in tcp_server.clients.itervalues() },
2677
2738
                    signature="oa{sv}")
2678
2739
            
2679
2740
            @dbus.service.method(_interface, in_signature="o")
2696
2757
    
2697
2758
    def cleanup():
2698
2759
        "Cleanup function; run on exit"
2699
 
        service.cleanup()
 
2760
        if zeroconf:
 
2761
            service.cleanup()
2700
2762
        
2701
2763
        multiprocessing.active_children()
 
2764
        wnull.close()
2702
2765
        if not (tcp_server.clients or client_settings):
2703
2766
            return
2704
2767
        
2715
2778
                
2716
2779
                # A list of attributes that can not be pickled
2717
2780
                # + secret.
2718
 
                exclude = set(("bus", "changedstate", "secret",
2719
 
                               "checker"))
 
2781
                exclude = { "bus", "changedstate", "secret",
 
2782
                            "checker", "server_settings" }
2720
2783
                for name, typ in (inspect.getmembers
2721
2784
                                  (dbus.service.Object)):
2722
2785
                    exclude.add(name)
2745
2808
                except NameError:
2746
2809
                    pass
2747
2810
            if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
2748
 
                logger.warning("Could not save persistent state: {0}"
 
2811
                logger.warning("Could not save persistent state: {}"
2749
2812
                               .format(os.strerror(e.errno)))
2750
2813
            else:
2751
2814
                logger.warning("Could not save persistent state:",
2752
2815
                               exc_info=e)
2753
 
                raise e
 
2816
                raise
2754
2817
        
2755
2818
        # Delete all clients, and settings from config
2756
2819
        while tcp_server.clients:
2780
2843
    tcp_server.server_activate()
2781
2844
    
2782
2845
    # Find out what port we got
2783
 
    service.port = tcp_server.socket.getsockname()[1]
 
2846
    if zeroconf:
 
2847
        service.port = tcp_server.socket.getsockname()[1]
2784
2848
    if use_ipv6:
2785
2849
        logger.info("Now listening on address %r, port %d,"
2786
2850
                    " flowinfo %d, scope_id %d",
2792
2856
    #service.interface = tcp_server.socket.getsockname()[3]
2793
2857
    
2794
2858
    try:
2795
 
        # From the Avahi example code
2796
 
        try:
2797
 
            service.activate()
2798
 
        except dbus.exceptions.DBusException as error:
2799
 
            logger.critical("D-Bus Exception", exc_info=error)
2800
 
            cleanup()
2801
 
            sys.exit(1)
2802
 
        # End of Avahi example code
 
2859
        if zeroconf:
 
2860
            # From the Avahi example code
 
2861
            try:
 
2862
                service.activate()
 
2863
            except dbus.exceptions.DBusException as error:
 
2864
                logger.critical("D-Bus Exception", exc_info=error)
 
2865
                cleanup()
 
2866
                sys.exit(1)
 
2867
            # End of Avahi example code
2803
2868
        
2804
2869
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2805
2870
                             lambda *args, **kwargs: