/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: 2015-03-10 18:03:38 UTC
  • Revision ID: teddy@recompile.se-20150310180338-pcxw6r2qmw9k6br9
Add ":!RSA" to GnuTLS priority string, to disallow non-DHE kx.

If Mandos was somehow made to use a non-ephemeral Diffie-Hellman key
exchange algorithm in the TLS handshake, any saved network traffic
could then be decrypted later if the Mandos client key was obtained.
By default, Mandos uses ephemeral DH key exchanges which does not have
this problem, but a non-ephemeral key exchange algorithm was still
enabled by default.  The simplest solution is to simply turn that off,
which ensures that Mandos will always use ephemeral DH key exchanges.

There is a "PFS" priority string specifier, but we can't use it because:

1. Security-wise, it is a mix between "NORMAL" and "SECURE128" - it
   enables a lot more algorithms than "SECURE256".

2. It is only available since GnuTLS 3.2.4.

Thanks to Andreas Fischer <af@bantuX.org> for reporting this issue.

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
                return True
720
729
            self.checker_callback_tag = (gobject.child_watch_add
721
730
                                         (self.checker.pid,
722
731
                                          self.checker_callback,
723
732
                                          data=command))
724
733
            # The checker may have completed before the gobject
725
734
            # watch was added.  Check for this.
726
 
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
 
735
            try:
 
736
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
 
737
            except OSError as error:
 
738
                if error.errno == errno.ECHILD:
 
739
                    # This should never happen
 
740
                    logger.error("Child process vanished",
 
741
                                 exc_info=error)
 
742
                    return True
 
743
                raise
727
744
            if pid:
728
745
                gobject.source_remove(self.checker_callback_tag)
729
746
                self.checker_callback(pid, status, command)
765
782
    # "Set" method, so we fail early here:
766
783
    if byte_arrays and signature != "ay":
767
784
        raise ValueError("Byte arrays not supported for non-'ay'"
768
 
                         " signature {0!r}".format(signature))
 
785
                         " signature {!r}".format(signature))
769
786
    def decorator(func):
770
787
        func._dbus_is_property = True
771
788
        func._dbus_interface = dbus_interface
802
819
    """Decorator to annotate D-Bus methods, signals or properties
803
820
    Usage:
804
821
    
 
822
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
 
823
                       "org.freedesktop.DBus.Property."
 
824
                       "EmitsChangedSignal": "false"})
805
825
    @dbus_service_property("org.example.Interface", signature="b",
806
826
                           access="r")
807
 
    @dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
808
 
                        "org.freedesktop.DBus.Property."
809
 
                        "EmitsChangedSignal": "false"})
810
827
    def Property_dbus_property(self):
811
828
        return dbus.Boolean(False)
812
829
    """
819
836
class DBusPropertyException(dbus.exceptions.DBusException):
820
837
    """A base class for D-Bus property-related exceptions
821
838
    """
822
 
    def __unicode__(self):
823
 
        return unicode(str(self))
824
 
 
 
839
    pass
825
840
 
826
841
class DBusPropertyAccessException(DBusPropertyException):
827
842
    """A property's access permissions disallows an operation.
850
865
        If called like _is_dbus_thing("method") it returns a function
851
866
        suitable for use as predicate to inspect.getmembers().
852
867
        """
853
 
        return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
 
868
        return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
854
869
                                   False)
855
870
    
856
871
    def _get_all_dbus_things(self, thing):
905
920
            # The byte_arrays option is not supported yet on
906
921
            # signatures other than "ay".
907
922
            if prop._dbus_signature != "ay":
908
 
                raise ValueError
 
923
                raise ValueError("Byte arrays not supported for non-"
 
924
                                 "'ay' signature {!r}"
 
925
                                 .format(prop._dbus_signature))
909
926
            value = dbus.ByteArray(b''.join(chr(byte)
910
927
                                            for byte in value))
911
928
        prop(value)
935
952
                                           value.variant_level+1)
936
953
        return dbus.Dictionary(properties, signature="sv")
937
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
    
938
963
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
939
964
                         out_signature="s",
940
965
                         path_keyword='object_path',
975
1000
                                              (prop,
976
1001
                                               "_dbus_annotations",
977
1002
                                               {}))
978
 
                        for name, value in annots.iteritems():
 
1003
                        for name, value in annots.items():
979
1004
                            ann_tag = document.createElement(
980
1005
                                "annotation")
981
1006
                            ann_tag.setAttribute("name", name)
984
1009
                # Add interface annotation tags
985
1010
                for annotation, value in dict(
986
1011
                    itertools.chain.from_iterable(
987
 
                        annotations().iteritems()
 
1012
                        annotations().items()
988
1013
                        for name, annotations in
989
1014
                        self._get_all_dbus_things("interface")
990
1015
                        if name == if_tag.getAttribute("name")
991
 
                        )).iteritems():
 
1016
                        )).items():
992
1017
                    ann_tag = document.createElement("annotation")
993
1018
                    ann_tag.setAttribute("name", annotation)
994
1019
                    ann_tag.setAttribute("value", value)
1050
1075
    """
1051
1076
    def wrapper(cls):
1052
1077
        for orig_interface_name, alt_interface_name in (
1053
 
            alt_interface_names.iteritems()):
 
1078
            alt_interface_names.items()):
1054
1079
            attr = {}
1055
1080
            interface_names = set()
1056
1081
            # Go though all attributes of the class
1069
1094
                interface_names.add(alt_interface)
1070
1095
                # Is this a D-Bus signal?
1071
1096
                if getattr(attribute, "_dbus_is_signal", False):
1072
 
                    # Extract the original non-method function by
1073
 
                    # black magic
 
1097
                    # Extract the original non-method undecorated
 
1098
                    # function by black magic
1074
1099
                    nonmethod_func = (dict(
1075
1100
                            zip(attribute.func_code.co_freevars,
1076
1101
                                attribute.__closure__))["func"]
1173
1198
                                        attribute.func_closure)))
1174
1199
            if deprecate:
1175
1200
                # Deprecate all alternate interfaces
1176
 
                iname="_AlternateDBusNames_interface_annotation{0}"
 
1201
                iname="_AlternateDBusNames_interface_annotation{}"
1177
1202
                for interface_name in interface_names:
1178
1203
                    @dbus_interface_annotations(interface_name)
1179
1204
                    def func(self):
1188
1213
            if interface_names:
1189
1214
                # Replace the class with a new subclass of it with
1190
1215
                # methods, signals, etc. as created above.
1191
 
                cls = type(b"{0}Alternate".format(cls.__name__),
 
1216
                cls = type(b"{}Alternate".format(cls.__name__),
1192
1217
                           (cls,), attr)
1193
1218
        return cls
1194
1219
    return wrapper
1207
1232
    runtime_expansions = (Client.runtime_expansions
1208
1233
                          + ("dbus_object_path",))
1209
1234
    
 
1235
    _interface = "se.recompile.Mandos.Client"
 
1236
    
1210
1237
    # dbus.service.Object doesn't use super(), so we can't either.
1211
1238
    
1212
1239
    def __init__(self, bus = None, *args, **kwargs):
1214
1241
        Client.__init__(self, *args, **kwargs)
1215
1242
        # Only now, when this client is initialized, can it show up on
1216
1243
        # the D-Bus
1217
 
        client_object_name = unicode(self.name).translate(
 
1244
        client_object_name = str(self.name).translate(
1218
1245
            {ord("."): ord("_"),
1219
1246
             ord("-"): ord("_")})
1220
1247
        self.dbus_object_path = (dbus.ObjectPath
1224
1251
    
1225
1252
    def notifychangeproperty(transform_func,
1226
1253
                             dbus_name, type_func=lambda x: x,
1227
 
                             variant_level=1):
 
1254
                             variant_level=1, invalidate_only=False,
 
1255
                             _interface=_interface):
1228
1256
        """ Modify a variable so that it's a property which announces
1229
1257
        its changes to DBus.
1230
1258
        
1235
1263
                   to the D-Bus.  Default: no transform
1236
1264
        variant_level: D-Bus variant level.  Default: 1
1237
1265
        """
1238
 
        attrname = "_{0}".format(dbus_name)
 
1266
        attrname = "_{}".format(dbus_name)
1239
1267
        def setter(self, value):
1240
1268
            if hasattr(self, "dbus_object_path"):
1241
1269
                if (not hasattr(self, attrname) or
1242
1270
                    type_func(getattr(self, attrname, None))
1243
1271
                    != type_func(value)):
1244
 
                    dbus_value = transform_func(type_func(value),
1245
 
                                                variant_level
1246
 
                                                =variant_level)
1247
 
                    self.PropertyChanged(dbus.String(dbus_name),
1248
 
                                         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())
1249
1287
            setattr(self, attrname, value)
1250
1288
        
1251
1289
        return property(lambda self: getattr(self, attrname), setter)
1271
1309
    approval_delay = notifychangeproperty(dbus.UInt64,
1272
1310
                                          "ApprovalDelay",
1273
1311
                                          type_func =
1274
 
                                          timedelta_to_milliseconds)
 
1312
                                          lambda td: td.total_seconds()
 
1313
                                          * 1000)
1275
1314
    approval_duration = notifychangeproperty(
1276
1315
        dbus.UInt64, "ApprovalDuration",
1277
 
        type_func = timedelta_to_milliseconds)
 
1316
        type_func = lambda td: td.total_seconds() * 1000)
1278
1317
    host = notifychangeproperty(dbus.String, "Host")
1279
1318
    timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1280
 
                                   type_func =
1281
 
                                   timedelta_to_milliseconds)
 
1319
                                   type_func = lambda td:
 
1320
                                       td.total_seconds() * 1000)
1282
1321
    extended_timeout = notifychangeproperty(
1283
1322
        dbus.UInt64, "ExtendedTimeout",
1284
 
        type_func = timedelta_to_milliseconds)
 
1323
        type_func = lambda td: td.total_seconds() * 1000)
1285
1324
    interval = notifychangeproperty(dbus.UInt64,
1286
1325
                                    "Interval",
1287
1326
                                    type_func =
1288
 
                                    timedelta_to_milliseconds)
 
1327
                                    lambda td: td.total_seconds()
 
1328
                                    * 1000)
1289
1329
    checker_command = notifychangeproperty(dbus.String, "Checker")
 
1330
    secret = notifychangeproperty(dbus.ByteArray, "Secret",
 
1331
                                  invalidate_only=True)
1290
1332
    
1291
1333
    del notifychangeproperty
1292
1334
    
1319
1361
                                       *args, **kwargs)
1320
1362
    
1321
1363
    def start_checker(self, *args, **kwargs):
1322
 
        old_checker = self.checker
1323
 
        if self.checker is not None:
1324
 
            old_checker_pid = self.checker.pid
1325
 
        else:
1326
 
            old_checker_pid = None
 
1364
        old_checker_pid = getattr(self.checker, "pid", None)
1327
1365
        r = Client.start_checker(self, *args, **kwargs)
1328
1366
        # Only if new checker process was started
1329
1367
        if (self.checker is not None
1338
1376
    
1339
1377
    def approve(self, value=True):
1340
1378
        self.approved = value
1341
 
        gobject.timeout_add(timedelta_to_milliseconds
1342
 
                            (self.approval_duration),
1343
 
                            self._reset_approved)
 
1379
        gobject.timeout_add(int(self.approval_duration.total_seconds()
 
1380
                                * 1000), self._reset_approved)
1344
1381
        self.send_changedstate()
1345
1382
    
1346
1383
    ## D-Bus methods, signals & properties
1347
 
    _interface = "se.recompile.Mandos.Client"
1348
1384
    
1349
1385
    ## Interfaces
1350
1386
    
1351
 
    @dbus_interface_annotations(_interface)
1352
 
    def _foo(self):
1353
 
        return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
1354
 
                     "false"}
1355
 
    
1356
1387
    ## Signals
1357
1388
    
1358
1389
    # CheckerCompleted - signal
1368
1399
        pass
1369
1400
    
1370
1401
    # PropertyChanged - signal
 
1402
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1371
1403
    @dbus.service.signal(_interface, signature="sv")
1372
1404
    def PropertyChanged(self, property, value):
1373
1405
        "D-Bus signal"
1449
1481
                           access="readwrite")
1450
1482
    def ApprovalDelay_dbus_property(self, value=None):
1451
1483
        if value is None:       # get
1452
 
            return dbus.UInt64(self.approval_delay_milliseconds())
 
1484
            return dbus.UInt64(self.approval_delay.total_seconds()
 
1485
                               * 1000)
1453
1486
        self.approval_delay = datetime.timedelta(0, 0, 0, value)
1454
1487
    
1455
1488
    # ApprovalDuration - property
1457
1490
                           access="readwrite")
1458
1491
    def ApprovalDuration_dbus_property(self, value=None):
1459
1492
        if value is None:       # get
1460
 
            return dbus.UInt64(timedelta_to_milliseconds(
1461
 
                    self.approval_duration))
 
1493
            return dbus.UInt64(self.approval_duration.total_seconds()
 
1494
                               * 1000)
1462
1495
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
1463
1496
    
1464
1497
    # Name - property
1477
1510
    def Host_dbus_property(self, value=None):
1478
1511
        if value is None:       # get
1479
1512
            return dbus.String(self.host)
1480
 
        self.host = unicode(value)
 
1513
        self.host = str(value)
1481
1514
    
1482
1515
    # Created - property
1483
1516
    @dbus_service_property(_interface, signature="s", access="read")
1530
1563
                           access="readwrite")
1531
1564
    def Timeout_dbus_property(self, value=None):
1532
1565
        if value is None:       # get
1533
 
            return dbus.UInt64(self.timeout_milliseconds())
 
1566
            return dbus.UInt64(self.timeout.total_seconds() * 1000)
1534
1567
        old_timeout = self.timeout
1535
1568
        self.timeout = datetime.timedelta(0, 0, 0, value)
1536
1569
        # Reschedule disabling
1547
1580
                gobject.source_remove(self.disable_initiator_tag)
1548
1581
                self.disable_initiator_tag = (
1549
1582
                    gobject.timeout_add(
1550
 
                        timedelta_to_milliseconds(self.expires - now),
1551
 
                        self.disable))
 
1583
                        int((self.expires - now).total_seconds()
 
1584
                            * 1000), self.disable))
1552
1585
    
1553
1586
    # ExtendedTimeout - property
1554
1587
    @dbus_service_property(_interface, signature="t",
1555
1588
                           access="readwrite")
1556
1589
    def ExtendedTimeout_dbus_property(self, value=None):
1557
1590
        if value is None:       # get
1558
 
            return dbus.UInt64(self.extended_timeout_milliseconds())
 
1591
            return dbus.UInt64(self.extended_timeout.total_seconds()
 
1592
                               * 1000)
1559
1593
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1560
1594
    
1561
1595
    # Interval - property
1563
1597
                           access="readwrite")
1564
1598
    def Interval_dbus_property(self, value=None):
1565
1599
        if value is None:       # get
1566
 
            return dbus.UInt64(self.interval_milliseconds())
 
1600
            return dbus.UInt64(self.interval.total_seconds() * 1000)
1567
1601
        self.interval = datetime.timedelta(0, 0, 0, value)
1568
1602
        if getattr(self, "checker_initiator_tag", None) is None:
1569
1603
            return
1580
1614
    def Checker_dbus_property(self, value=None):
1581
1615
        if value is None:       # get
1582
1616
            return dbus.String(self.checker_command)
1583
 
        self.checker_command = unicode(value)
 
1617
        self.checker_command = str(value)
1584
1618
    
1585
1619
    # CheckerRunning - property
1586
1620
    @dbus_service_property(_interface, signature="b",
1602
1636
    @dbus_service_property(_interface, signature="ay",
1603
1637
                           access="write", byte_arrays=True)
1604
1638
    def Secret_dbus_property(self, value):
1605
 
        self.secret = str(value)
 
1639
        self.secret = bytes(value)
1606
1640
    
1607
1641
    del _interface
1608
1642
 
1642
1676
    def handle(self):
1643
1677
        with contextlib.closing(self.server.child_pipe) as child_pipe:
1644
1678
            logger.info("TCP connection from: %s",
1645
 
                        unicode(self.client_address))
 
1679
                        str(self.client_address))
1646
1680
            logger.debug("Pipe FD: %d",
1647
1681
                         self.server.child_pipe.fileno())
1648
1682
            
1674
1708
            logger.debug("Protocol version: %r", line)
1675
1709
            try:
1676
1710
                if int(line.strip().split()[0]) > 1:
1677
 
                    raise RuntimeError
 
1711
                    raise RuntimeError(line)
1678
1712
            except (ValueError, IndexError, RuntimeError) as error:
1679
1713
                logger.error("Unknown protocol version: %s", error)
1680
1714
                return
1729
1763
                        if self.server.use_dbus:
1730
1764
                            # Emit D-Bus signal
1731
1765
                            client.NeedApproval(
1732
 
                                client.approval_delay_milliseconds(),
1733
 
                                client.approved_by_default)
 
1766
                                client.approval_delay.total_seconds()
 
1767
                                * 1000, client.approved_by_default)
1734
1768
                    else:
1735
1769
                        logger.warning("Client %s was not approved",
1736
1770
                                       client.name)
1742
1776
                    #wait until timeout or approved
1743
1777
                    time = datetime.datetime.now()
1744
1778
                    client.changedstate.acquire()
1745
 
                    client.changedstate.wait(
1746
 
                        float(timedelta_to_milliseconds(delay)
1747
 
                              / 1000))
 
1779
                    client.changedstate.wait(delay.total_seconds())
1748
1780
                    client.changedstate.release()
1749
1781
                    time2 = datetime.datetime.now()
1750
1782
                    if (time2 - time) >= delay:
1887
1919
    
1888
1920
    def add_pipe(self, parent_pipe, proc):
1889
1921
        """Dummy function; override as necessary"""
1890
 
        raise NotImplementedError
 
1922
        raise NotImplementedError()
1891
1923
 
1892
1924
 
1893
1925
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1949
1981
                try:
1950
1982
                    self.socket.setsockopt(socket.SOL_SOCKET,
1951
1983
                                           SO_BINDTODEVICE,
1952
 
                                           str(self.interface + '\0'))
 
1984
                                           (self.interface + "\0")
 
1985
                                           .encode("utf-8"))
1953
1986
                except socket.error as error:
1954
1987
                    if error.errno == errno.EPERM:
1955
1988
                        logger.error("No permission to bind to"
1969
2002
                if self.address_family == socket.AF_INET6:
1970
2003
                    any_address = "::" # in6addr_any
1971
2004
                else:
1972
 
                    any_address = socket.INADDR_ANY
 
2005
                    any_address = "0.0.0.0" # INADDR_ANY
1973
2006
                self.server_address = (any_address,
1974
2007
                                       self.server_address[1])
1975
2008
            elif not self.server_address[1]:
2158
2191
    token_duration = Token(re.compile(r"P"), None,
2159
2192
                           frozenset((token_year, token_month,
2160
2193
                                      token_day, token_time,
2161
 
                                      token_week))),
 
2194
                                      token_week)))
2162
2195
    # Define starting values
2163
2196
    value = datetime.timedelta() # Value so far
2164
2197
    found_token = None
2165
 
    followers = frozenset(token_duration,) # Following valid tokens
 
2198
    followers = frozenset((token_duration,)) # Following valid tokens
2166
2199
    s = duration                # String left to parse
2167
2200
    # Loop until end token is found
2168
2201
    while found_token is not token_end:
2215
2248
    timevalue = datetime.timedelta(0)
2216
2249
    for s in interval.split():
2217
2250
        try:
2218
 
            suffix = unicode(s[-1])
 
2251
            suffix = s[-1]
2219
2252
            value = int(s[:-1])
2220
2253
            if suffix == "d":
2221
2254
                delta = datetime.timedelta(value)
2228
2261
            elif suffix == "w":
2229
2262
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
2230
2263
            else:
2231
 
                raise ValueError("Unknown suffix {0!r}"
 
2264
                raise ValueError("Unknown suffix {!r}"
2232
2265
                                 .format(suffix))
2233
 
        except (ValueError, IndexError) as e:
 
2266
        except IndexError as e:
2234
2267
            raise ValueError(*(e.args))
2235
2268
        timevalue += delta
2236
2269
    return timevalue
2251
2284
        # Close all standard open file descriptors
2252
2285
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2253
2286
        if not stat.S_ISCHR(os.fstat(null).st_mode):
2254
 
            raise OSError(errno.ENODEV,
2255
 
                          "{0} not a character device"
 
2287
            raise OSError(errno.ENODEV, "{} not a character device"
2256
2288
                          .format(os.devnull))
2257
2289
        os.dup2(null, sys.stdin.fileno())
2258
2290
        os.dup2(null, sys.stdout.fileno())
2268
2300
    
2269
2301
    parser = argparse.ArgumentParser()
2270
2302
    parser.add_argument("-v", "--version", action="version",
2271
 
                        version = "%(prog)s {0}".format(version),
 
2303
                        version = "%(prog)s {}".format(version),
2272
2304
                        help="show version number and exit")
2273
2305
    parser.add_argument("-i", "--interface", metavar="IF",
2274
2306
                        help="Bind to interface IF")
2280
2312
                        help="Run self-test")
2281
2313
    parser.add_argument("--debug", action="store_true",
2282
2314
                        help="Debug mode; run in foreground and log"
2283
 
                        " to terminal")
 
2315
                        " to terminal", default=None)
2284
2316
    parser.add_argument("--debuglevel", metavar="LEVEL",
2285
2317
                        help="Debug level for stdout output")
2286
2318
    parser.add_argument("--priority", help="GnuTLS"
2293
2325
                        " files")
2294
2326
    parser.add_argument("--no-dbus", action="store_false",
2295
2327
                        dest="use_dbus", help="Do not provide D-Bus"
2296
 
                        " system bus interface")
 
2328
                        " system bus interface", default=None)
2297
2329
    parser.add_argument("--no-ipv6", action="store_false",
2298
 
                        dest="use_ipv6", help="Do not use IPv6")
 
2330
                        dest="use_ipv6", help="Do not use IPv6",
 
2331
                        default=None)
2299
2332
    parser.add_argument("--no-restore", action="store_false",
2300
2333
                        dest="restore", help="Do not restore stored"
2301
 
                        " state")
 
2334
                        " state", default=None)
2302
2335
    parser.add_argument("--socket", type=int,
2303
2336
                        help="Specify a file descriptor to a network"
2304
2337
                        " socket to use instead of creating one")
2305
2338
    parser.add_argument("--statedir", metavar="DIR",
2306
2339
                        help="Directory to save/restore state in")
2307
2340
    parser.add_argument("--foreground", action="store_true",
2308
 
                        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)
2309
2345
    
2310
2346
    options = parser.parse_args()
2311
2347
    
2312
2348
    if options.check:
2313
2349
        import doctest
2314
 
        doctest.testmod()
2315
 
        sys.exit()
 
2350
        fail_count, test_count = doctest.testmod()
 
2351
        sys.exit(os.EX_OK if fail_count == 0 else 1)
2316
2352
    
2317
2353
    # Default values for config file for server-global settings
2318
2354
    server_defaults = { "interface": "",
2320
2356
                        "port": "",
2321
2357
                        "debug": "False",
2322
2358
                        "priority":
2323
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
 
2359
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
 
2360
                        ":+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
2324
2361
                        "servicename": "Mandos",
2325
2362
                        "use_dbus": "True",
2326
2363
                        "use_ipv6": "True",
2329
2366
                        "socket": "",
2330
2367
                        "statedir": "/var/lib/mandos",
2331
2368
                        "foreground": "False",
 
2369
                        "zeroconf": "True",
2332
2370
                        }
2333
2371
    
2334
2372
    # Parse config file for server-global settings
2361
2399
    for option in ("interface", "address", "port", "debug",
2362
2400
                   "priority", "servicename", "configdir",
2363
2401
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
2364
 
                   "statedir", "socket", "foreground"):
 
2402
                   "statedir", "socket", "foreground", "zeroconf"):
2365
2403
        value = getattr(options, option)
2366
2404
        if value is not None:
2367
2405
            server_settings[option] = value
2368
2406
    del options
2369
2407
    # Force all strings to be unicode
2370
2408
    for option in server_settings.keys():
2371
 
        if type(server_settings[option]) is str:
2372
 
            server_settings[option] = unicode(server_settings[option])
 
2409
        if isinstance(server_settings[option], bytes):
 
2410
            server_settings[option] = (server_settings[option]
 
2411
                                       .decode("utf-8"))
 
2412
    # Force all boolean options to be boolean
 
2413
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
 
2414
                   "foreground", "zeroconf"):
 
2415
        server_settings[option] = bool(server_settings[option])
2373
2416
    # Debug implies foreground
2374
2417
    if server_settings["debug"]:
2375
2418
        server_settings["foreground"] = True
2377
2420
    
2378
2421
    ##################################################################
2379
2422
    
 
2423
    if (not server_settings["zeroconf"] and
 
2424
        not (server_settings["port"]
 
2425
             or server_settings["socket"] != "")):
 
2426
            parser.error("Needs port or socket to work without"
 
2427
                         " Zeroconf")
 
2428
    
2380
2429
    # For convenience
2381
2430
    debug = server_settings["debug"]
2382
2431
    debuglevel = server_settings["debuglevel"]
2385
2434
    stored_state_path = os.path.join(server_settings["statedir"],
2386
2435
                                     stored_state_file)
2387
2436
    foreground = server_settings["foreground"]
 
2437
    zeroconf = server_settings["zeroconf"]
2388
2438
    
2389
2439
    if debug:
2390
2440
        initlogger(debug, logging.DEBUG)
2397
2447
    
2398
2448
    if server_settings["servicename"] != "Mandos":
2399
2449
        syslogger.setFormatter(logging.Formatter
2400
 
                               ('Mandos ({0}) [%(process)d]:'
 
2450
                               ('Mandos ({}) [%(process)d]:'
2401
2451
                                ' %(levelname)s: %(message)s'
2402
2452
                                .format(server_settings
2403
2453
                                        ["servicename"])))
2411
2461
    global mandos_dbus_service
2412
2462
    mandos_dbus_service = None
2413
2463
    
 
2464
    socketfd = None
 
2465
    if server_settings["socket"] != "":
 
2466
        socketfd = server_settings["socket"]
2414
2467
    tcp_server = MandosServer((server_settings["address"],
2415
2468
                               server_settings["port"]),
2416
2469
                              ClientHandler,
2420
2473
                              gnutls_priority=
2421
2474
                              server_settings["priority"],
2422
2475
                              use_dbus=use_dbus,
2423
 
                              socketfd=(server_settings["socket"]
2424
 
                                        or None))
 
2476
                              socketfd=socketfd)
2425
2477
    if not foreground:
2426
 
        pidfilename = "/var/run/mandos.pid"
 
2478
        pidfilename = "/run/mandos.pid"
 
2479
        if not os.path.isdir("/run/."):
 
2480
            pidfilename = "/var/run/mandos.pid"
2427
2481
        pidfile = None
2428
2482
        try:
2429
2483
            pidfile = open(pidfilename, "w")
2446
2500
        os.setuid(uid)
2447
2501
    except OSError as error:
2448
2502
        if error.errno != errno.EPERM:
2449
 
            raise error
 
2503
            raise
2450
2504
    
2451
2505
    if debug:
2452
2506
        # Enable all possible GnuTLS debugging
2495
2549
            use_dbus = False
2496
2550
            server_settings["use_dbus"] = False
2497
2551
            tcp_server.use_dbus = False
2498
 
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2499
 
    service = AvahiServiceToSyslog(name =
2500
 
                                   server_settings["servicename"],
2501
 
                                   servicetype = "_mandos._tcp",
2502
 
                                   protocol = protocol, bus = bus)
2503
 
    if server_settings["interface"]:
2504
 
        service.interface = (if_nametoindex
2505
 
                             (str(server_settings["interface"])))
 
2552
    if zeroconf:
 
2553
        protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
 
2554
        service = AvahiServiceToSyslog(name =
 
2555
                                       server_settings["servicename"],
 
2556
                                       servicetype = "_mandos._tcp",
 
2557
                                       protocol = protocol, bus = bus)
 
2558
        if server_settings["interface"]:
 
2559
            service.interface = (if_nametoindex
 
2560
                                 (server_settings["interface"]
 
2561
                                  .encode("utf-8")))
2506
2562
    
2507
2563
    global multiprocessing_manager
2508
2564
    multiprocessing_manager = multiprocessing.Manager()
2515
2571
    old_client_settings = {}
2516
2572
    clients_data = {}
2517
2573
    
 
2574
    # This is used to redirect stdout and stderr for checker processes
 
2575
    global wnull
 
2576
    wnull = open(os.devnull, "w") # A writable /dev/null
 
2577
    # Only used if server is running in foreground but not in debug
 
2578
    # mode
 
2579
    if debug or not foreground:
 
2580
        wnull.close()
 
2581
    
2518
2582
    # Get client data and settings from last running state.
2519
2583
    if server_settings["restore"]:
2520
2584
        try:
2524
2588
            os.remove(stored_state_path)
2525
2589
        except IOError as e:
2526
2590
            if e.errno == errno.ENOENT:
2527
 
                logger.warning("Could not load persistent state: {0}"
 
2591
                logger.warning("Could not load persistent state: {}"
2528
2592
                                .format(os.strerror(e.errno)))
2529
2593
            else:
2530
2594
                logger.critical("Could not load persistent state:",
2535
2599
                           "EOFError:", exc_info=e)
2536
2600
    
2537
2601
    with PGPEngine() as pgp:
2538
 
        for client_name, client in clients_data.iteritems():
 
2602
        for client_name, client in clients_data.items():
 
2603
            # Skip removed clients
 
2604
            if client_name not in client_settings:
 
2605
                continue
 
2606
            
2539
2607
            # Decide which value to use after restoring saved state.
2540
2608
            # We have three different values: Old config file,
2541
2609
            # new config file, and saved state.
2562
2630
                if datetime.datetime.utcnow() >= client["expires"]:
2563
2631
                    if not client["last_checked_ok"]:
2564
2632
                        logger.warning(
2565
 
                            "disabling client {0} - Client never "
 
2633
                            "disabling client {} - Client never "
2566
2634
                            "performed a successful checker"
2567
2635
                            .format(client_name))
2568
2636
                        client["enabled"] = False
2569
2637
                    elif client["last_checker_status"] != 0:
2570
2638
                        logger.warning(
2571
 
                            "disabling client {0} - Client "
2572
 
                            "last checker failed with error code {1}"
 
2639
                            "disabling client {} - Client last"
 
2640
                            " checker failed with error code {}"
2573
2641
                            .format(client_name,
2574
2642
                                    client["last_checker_status"]))
2575
2643
                        client["enabled"] = False
2578
2646
                                             .utcnow()
2579
2647
                                             + client["timeout"])
2580
2648
                        logger.debug("Last checker succeeded,"
2581
 
                                     " keeping {0} enabled"
 
2649
                                     " keeping {} enabled"
2582
2650
                                     .format(client_name))
2583
2651
            try:
2584
2652
                client["secret"] = (
2587
2655
                                ["secret"]))
2588
2656
            except PGPError:
2589
2657
                # If decryption fails, we use secret from new settings
2590
 
                logger.debug("Failed to decrypt {0} old secret"
 
2658
                logger.debug("Failed to decrypt {} old secret"
2591
2659
                             .format(client_name))
2592
2660
                client["secret"] = (
2593
2661
                    client_settings[client_name]["secret"])
2601
2669
        clients_data[client_name] = client_settings[client_name]
2602
2670
    
2603
2671
    # Create all client objects
2604
 
    for client_name, client in clients_data.iteritems():
 
2672
    for client_name, client in clients_data.items():
2605
2673
        tcp_server.clients[client_name] = client_class(
2606
 
            name = client_name, settings = client)
 
2674
            name = client_name, settings = client,
 
2675
            server_settings = server_settings)
2607
2676
    
2608
2677
    if not tcp_server.clients:
2609
2678
        logger.warning("No clients defined")
2613
2682
            try:
2614
2683
                with pidfile:
2615
2684
                    pid = os.getpid()
2616
 
                    pidfile.write(str(pid) + "\n".encode("utf-8"))
 
2685
                    pidfile.write("{}\n".format(pid).encode("utf-8"))
2617
2686
            except IOError:
2618
2687
                logger.error("Could not write to file %r with PID %d",
2619
2688
                             pidfilename, pid)
2665
2734
            def GetAllClientsWithProperties(self):
2666
2735
                "D-Bus method"
2667
2736
                return dbus.Dictionary(
2668
 
                    ((c.dbus_object_path, c.GetAll(""))
2669
 
                     for c in tcp_server.clients.itervalues()),
 
2737
                    { c.dbus_object_path: c.GetAll("")
 
2738
                      for c in tcp_server.clients.itervalues() },
2670
2739
                    signature="oa{sv}")
2671
2740
            
2672
2741
            @dbus.service.method(_interface, in_signature="o")
2689
2758
    
2690
2759
    def cleanup():
2691
2760
        "Cleanup function; run on exit"
2692
 
        service.cleanup()
 
2761
        if zeroconf:
 
2762
            service.cleanup()
2693
2763
        
2694
2764
        multiprocessing.active_children()
 
2765
        wnull.close()
2695
2766
        if not (tcp_server.clients or client_settings):
2696
2767
            return
2697
2768
        
2708
2779
                
2709
2780
                # A list of attributes that can not be pickled
2710
2781
                # + secret.
2711
 
                exclude = set(("bus", "changedstate", "secret",
2712
 
                               "checker"))
 
2782
                exclude = { "bus", "changedstate", "secret",
 
2783
                            "checker", "server_settings" }
2713
2784
                for name, typ in (inspect.getmembers
2714
2785
                                  (dbus.service.Object)):
2715
2786
                    exclude.add(name)
2738
2809
                except NameError:
2739
2810
                    pass
2740
2811
            if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
2741
 
                logger.warning("Could not save persistent state: {0}"
 
2812
                logger.warning("Could not save persistent state: {}"
2742
2813
                               .format(os.strerror(e.errno)))
2743
2814
            else:
2744
2815
                logger.warning("Could not save persistent state:",
2745
2816
                               exc_info=e)
2746
 
                raise e
 
2817
                raise
2747
2818
        
2748
2819
        # Delete all clients, and settings from config
2749
2820
        while tcp_server.clients:
2773
2844
    tcp_server.server_activate()
2774
2845
    
2775
2846
    # Find out what port we got
2776
 
    service.port = tcp_server.socket.getsockname()[1]
 
2847
    if zeroconf:
 
2848
        service.port = tcp_server.socket.getsockname()[1]
2777
2849
    if use_ipv6:
2778
2850
        logger.info("Now listening on address %r, port %d,"
2779
2851
                    " flowinfo %d, scope_id %d",
2785
2857
    #service.interface = tcp_server.socket.getsockname()[3]
2786
2858
    
2787
2859
    try:
2788
 
        # From the Avahi example code
2789
 
        try:
2790
 
            service.activate()
2791
 
        except dbus.exceptions.DBusException as error:
2792
 
            logger.critical("D-Bus Exception", exc_info=error)
2793
 
            cleanup()
2794
 
            sys.exit(1)
2795
 
        # End of Avahi example code
 
2860
        if zeroconf:
 
2861
            # From the Avahi example code
 
2862
            try:
 
2863
                service.activate()
 
2864
            except dbus.exceptions.DBusException as error:
 
2865
                logger.critical("D-Bus Exception", exc_info=error)
 
2866
                cleanup()
 
2867
                sys.exit(1)
 
2868
            # End of Avahi example code
2796
2869
        
2797
2870
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2798
2871
                             lambda *args, **kwargs: