111
110
        return interface_index
 
114
 
def initlogger(debug, level=logging.WARNING):
 
 
113
def initlogger(level=logging.WARNING):
 
115
114
    """init logger and add loglevel"""
 
117
 
    syslogger = (logging.handlers.SysLogHandler
 
119
 
                  logging.handlers.SysLogHandler.LOG_DAEMON,
 
120
 
                  address = str("/dev/log")))
 
121
116
    syslogger.setFormatter(logging.Formatter
 
122
117
                           ('Mandos [%(process)d]: %(levelname)s:'
 
124
119
    logger.addHandler(syslogger)
 
127
 
        console = logging.StreamHandler()
 
128
 
        console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
 
132
 
        logger.addHandler(console)
 
 
121
    console = logging.StreamHandler()
 
 
122
    console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
 
 
126
    logger.addHandler(console)
 
133
127
    logger.setLevel(level)
 
 
174
169
    def password_encode(self, password):
 
175
170
        # Passphrase can not be empty and can not contain newlines or
 
176
171
        # NUL bytes.  So we prefix it and hex encode it.
 
177
 
        encoded = b"mandos" + binascii.hexlify(password)
 
178
 
        if len(encoded) > 2048:
 
179
 
            # GnuPG can't handle long passwords, so encode differently
 
180
 
            encoded = (b"mandos" + password.replace(b"\\", b"\\\\")
 
181
 
                       .replace(b"\n", b"\\n")
 
182
 
                       .replace(b"\0", b"\\x00"))
 
 
172
        return b"mandos" + binascii.hexlify(password)
 
185
174
    def encrypt(self, data, password):
 
186
 
        passphrase = self.password_encode(password)
 
187
 
        with tempfile.NamedTemporaryFile(dir=self.tempdir
 
189
 
            passfile.write(passphrase)
 
191
 
            proc = subprocess.Popen(['gpg', '--symmetric',
 
195
 
                                    stdin = subprocess.PIPE,
 
196
 
                                    stdout = subprocess.PIPE,
 
197
 
                                    stderr = subprocess.PIPE)
 
198
 
            ciphertext, err = proc.communicate(input = data)
 
199
 
        if proc.returncode != 0:
 
 
175
        self.gnupg.passphrase = self.password_encode(password)
 
 
176
        with open(os.devnull) as devnull:
 
 
178
                proc = self.gnupg.run(['--symmetric'],
 
 
179
                                      create_fhs=['stdin', 'stdout'],
 
 
180
                                      attach_fhs={'stderr': devnull})
 
 
181
                with contextlib.closing(proc.handles['stdin']) as f:
 
 
183
                with contextlib.closing(proc.handles['stdout']) as f:
 
 
184
                    ciphertext = f.read()
 
 
188
        self.gnupg.passphrase = None
 
201
189
        return ciphertext
 
203
191
    def decrypt(self, data, password):
 
204
 
        passphrase = self.password_encode(password)
 
205
 
        with tempfile.NamedTemporaryFile(dir = self.tempdir
 
207
 
            passfile.write(passphrase)
 
209
 
            proc = subprocess.Popen(['gpg', '--decrypt',
 
213
 
                                    stdin = subprocess.PIPE,
 
214
 
                                    stdout = subprocess.PIPE,
 
215
 
                                    stderr = subprocess.PIPE)
 
216
 
            decrypted_plaintext, err = proc.communicate(input
 
218
 
        if proc.returncode != 0:
 
 
192
        self.gnupg.passphrase = self.password_encode(password)
 
 
193
        with open(os.devnull) as devnull:
 
 
195
                proc = self.gnupg.run(['--decrypt'],
 
 
196
                                      create_fhs=['stdin', 'stdout'],
 
 
197
                                      attach_fhs={'stderr': devnull})
 
 
198
                with contextlib.closing(proc.handles['stdin'] ) as f:
 
 
200
                with contextlib.closing(proc.handles['stdout']) as f:
 
 
201
                    decrypted_plaintext = f.read()
 
 
205
        self.gnupg.passphrase = None
 
220
206
        return decrypted_plaintext
 
223
210
class AvahiError(Exception):
 
224
211
    def __init__(self, value, *args, **kwargs):
 
225
212
        self.value = value
 
 
388
367
                                 self.server_state_changed)
 
389
368
        self.server_state_changed(self.server.GetState())
 
392
370
class AvahiServiceToSyslog(AvahiService):
 
393
371
    def rename(self):
 
394
372
        """Add the new name to the syslog messages"""
 
395
373
        ret = AvahiService.rename(self)
 
396
374
        syslogger.setFormatter(logging.Formatter
 
397
 
                               ('Mandos ({0}) [%(process)d]:'
 
398
 
                                ' %(levelname)s: %(message)s'
 
 
375
                               ('Mandos (%s) [%%(process)d]:'
 
 
376
                                ' %%(levelname)s: %%(message)s'
 
403
380
def timedelta_to_milliseconds(td):
 
404
381
    "Convert a datetime.timedelta() to milliseconds"
 
405
382
    return ((td.days * 24 * 60 * 60 * 1000)
 
406
383
            + (td.seconds * 1000)
 
407
384
            + (td.microseconds // 1000))
 
410
386
class Client(object):
 
411
387
    """A representation of a client host served by this server.
 
 
437
413
    last_checked_ok: datetime.datetime(); (UTC) or None
 
438
414
    last_checker_status: integer between 0 and 255 reflecting exit
 
439
415
                         status of last checker. -1 reflects crashed
 
440
 
                         checker, -2 means no checker completed yet.
 
441
417
    last_enabled: datetime.datetime(); (UTC) or None
 
442
418
    name:       string; from the config file, used in log messages and
 
443
419
                        D-Bus identifiers
 
444
420
    secret:     bytestring; sent verbatim (over TLS) to client
 
445
421
    timeout:    datetime.timedelta(); How long from last_checked_ok
 
446
422
                                      until this client is disabled
 
447
 
    extended_timeout:   extra long timeout when secret has been sent
 
 
423
    extended_timeout:   extra long timeout when password has been sent
 
448
424
    runtime_expansions: Allowed attributes for runtime expansion.
 
449
425
    expires:    datetime.datetime(); time (UTC) when a client will be
 
450
426
                disabled, or None
 
451
 
    server_settings: The server_settings dict from main()
 
454
429
    runtime_expansions = ("approval_delay", "approval_duration",
 
455
 
                          "created", "enabled", "expires",
 
456
 
                          "fingerprint", "host", "interval",
 
457
 
                          "last_approval_request", "last_checked_ok",
 
 
430
                          "created", "enabled", "fingerprint",
 
 
431
                          "host", "interval", "last_checked_ok",
 
458
432
                          "last_enabled", "name", "timeout")
 
459
 
    client_defaults = { "timeout": "PT5M",
 
460
 
                        "extended_timeout": "PT15M",
 
 
433
    client_defaults = { "timeout": "5m",
 
 
434
                        "extended_timeout": "15m",
 
462
436
                        "checker": "fping -q -- %%(host)s",
 
464
 
                        "approval_delay": "PT0S",
 
465
 
                        "approval_duration": "PT1S",
 
 
438
                        "approval_delay": "0s",
 
 
439
                        "approval_duration": "1s",
 
466
440
                        "approved_by_default": "True",
 
467
441
                        "enabled": "True",
 
 
525
495
            client["checker_command"] = section["checker"]
 
526
496
            client["last_approval_request"] = None
 
527
497
            client["last_checked_ok"] = None
 
528
 
            client["last_checker_status"] = -2
 
 
498
            client["last_checker_status"] = None
 
 
499
            if client["enabled"]:
 
 
500
                client["last_enabled"] = datetime.datetime.utcnow()
 
 
501
                client["expires"] = (datetime.datetime.utcnow()
 
 
504
                client["last_enabled"] = None
 
 
505
                client["expires"] = None
 
532
 
    def __init__(self, settings, name = None, server_settings=None):
 
 
510
    def __init__(self, settings, name = None):
 
 
511
        """Note: the 'checker' key in 'config' sets the
 
 
512
        'checker_command' attribute and *not* the 'checker'
 
534
 
        if server_settings is None:
 
536
 
        self.server_settings = server_settings
 
537
515
        # adding all client settings
 
538
516
        for setting, value in settings.iteritems():
 
539
517
            setattr(self, setting, value)
 
542
 
            if not hasattr(self, "last_enabled"):
 
543
 
                self.last_enabled = datetime.datetime.utcnow()
 
544
 
            if not hasattr(self, "expires"):
 
545
 
                self.expires = (datetime.datetime.utcnow()
 
548
 
            self.last_enabled = None
 
551
519
        logger.debug("Creating client %r", self.name)
 
552
520
        # Uppercase and remove spaces from fingerprint for later
 
553
521
        # comparison purposes with return value from the fingerprint()
 
555
523
        logger.debug("  Fingerprint: %s", self.fingerprint)
 
556
 
        self.created = settings.get("created",
 
557
 
                                    datetime.datetime.utcnow())
 
 
524
        self.created = settings.get("created", datetime.datetime.utcnow())
 
559
526
        # attributes specific for this server instance
 
560
527
        self.checker = None
 
561
528
        self.checker_initiator_tag = None
 
 
589
556
        if getattr(self, "enabled", False):
 
590
557
            # Already enabled
 
 
559
        self.send_changedstate()
 
592
560
        self.expires = datetime.datetime.utcnow() + self.timeout
 
593
561
        self.enabled = True
 
594
562
        self.last_enabled = datetime.datetime.utcnow()
 
595
563
        self.init_checker()
 
596
 
        self.send_changedstate()
 
598
565
    def disable(self, quiet=True):
 
599
566
        """Disable this client."""
 
600
567
        if not getattr(self, "enabled", False):
 
 
570
            self.send_changedstate()
 
603
572
            logger.info("Disabling client %s", self.name)
 
604
 
        if getattr(self, "disable_initiator_tag", None) is not None:
 
 
573
        if getattr(self, "disable_initiator_tag", False):
 
605
574
            gobject.source_remove(self.disable_initiator_tag)
 
606
575
            self.disable_initiator_tag = None
 
607
576
        self.expires = None
 
608
 
        if getattr(self, "checker_initiator_tag", None) is not None:
 
 
577
        if getattr(self, "checker_initiator_tag", False):
 
609
578
            gobject.source_remove(self.checker_initiator_tag)
 
610
579
            self.checker_initiator_tag = None
 
611
580
        self.stop_checker()
 
612
581
        self.enabled = False
 
614
 
            self.send_changedstate()
 
615
582
        # Do not run this again if called by a gobject.timeout_add
 
 
653
616
            logger.warning("Checker for %(name)s crashed?",
 
656
 
    def checked_ok(self):
 
657
 
        """Assert that the client has been seen, alive and well."""
 
658
 
        self.last_checked_ok = datetime.datetime.utcnow()
 
659
 
        self.last_checker_status = 0
 
662
 
    def bump_timeout(self, timeout=None):
 
663
 
        """Bump up the timeout for this client."""
 
 
619
    def checked_ok(self, timeout=None):
 
 
620
        """Bump up the timeout for this client.
 
 
622
        This should only be called when the client has been seen,
 
664
625
        if timeout is None:
 
665
626
            timeout = self.timeout
 
 
627
        self.last_checked_ok = datetime.datetime.utcnow()
 
666
628
        if self.disable_initiator_tag is not None:
 
667
629
            gobject.source_remove(self.disable_initiator_tag)
 
668
 
            self.disable_initiator_tag = None
 
669
630
        if getattr(self, "enabled", False):
 
670
631
            self.disable_initiator_tag = (gobject.timeout_add
 
671
632
                                          (timedelta_to_milliseconds
 
 
681
642
        If a checker already exists, leave it running and do
 
683
644
        # The reason for not killing a running checker is that if we
 
684
 
        # did that, and if a checker (for some reason) started running
 
685
 
        # slowly and taking more than 'interval' time, then the client
 
686
 
        # would inevitably timeout, since no checker would get a
 
687
 
        # chance to run to completion.  If we instead leave running
 
 
645
        # did that, then if a checker (for some reason) started
 
 
646
        # running slowly and taking more than 'interval' time, the
 
 
647
        # client would inevitably timeout, since no checker would get
 
 
648
        # a chance to run to completion.  If we instead leave running
 
688
649
        # checkers alone, the checker would have to take more time
 
689
650
        # than 'timeout' for the client to be disabled, which is as it
 
 
705
665
                                      self.current_checker_command)
 
706
666
        # Start a new checker if needed
 
707
667
        if self.checker is None:
 
708
 
            # Escape attributes for the shell
 
709
 
            escaped_attrs = dict(
 
710
 
                (attr, re.escape(unicode(getattr(self, attr))))
 
712
 
                self.runtime_expansions)
 
714
 
                command = self.checker_command % escaped_attrs
 
715
 
            except TypeError as error:
 
716
 
                logger.error('Could not format string "%s"',
 
717
 
                             self.checker_command, exc_info=error)
 
718
 
                return True # Try again later
 
 
669
                # In case checker_command has exactly one % operator
 
 
670
                command = self.checker_command % self.host
 
 
672
                # Escape attributes for the shell
 
 
673
                escaped_attrs = dict(
 
 
675
                     re.escape(unicode(str(getattr(self, attr, "")),
 
 
679
                    self.runtime_expansions)
 
 
682
                    command = self.checker_command % escaped_attrs
 
 
683
                except TypeError as error:
 
 
684
                    logger.error('Could not format string "%s":'
 
 
685
                                 ' %s', self.checker_command, error)
 
 
686
                    return True # Try again later
 
719
687
            self.current_checker_command = command
 
721
689
                logger.info("Starting checker %r for %s",
 
 
724
692
                # in normal mode, that is already done by daemon(),
 
725
693
                # and in debug mode we don't want to.  (Stdin is
 
726
694
                # always replaced by /dev/null.)
 
727
 
                # The exception is when not debugging but nevertheless
 
728
 
                # running in the foreground; use the previously
 
731
 
                if (not self.server_settings["debug"]
 
732
 
                    and self.server_settings["foreground"]):
 
733
 
                    popen_args.update({"stdout": wnull,
 
735
695
                self.checker = subprocess.Popen(command,
 
739
 
            except OSError as error:
 
740
 
                logger.error("Failed to start subprocess",
 
743
 
            self.checker_callback_tag = (gobject.child_watch_add
 
745
 
                                          self.checker_callback,
 
747
 
            # The checker may have completed before the gobject
 
748
 
            # watch was added.  Check for this.
 
 
698
                self.checker_callback_tag = (gobject.child_watch_add
 
 
700
                                              self.checker_callback,
 
 
702
                # The checker may have completed before the gobject
 
 
703
                # watch was added.  Check for this.
 
750
704
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
 
 
706
                    gobject.source_remove(self.checker_callback_tag)
 
 
707
                    self.checker_callback(pid, status, command)
 
751
708
            except OSError as error:
 
752
 
                if error.errno == errno.ECHILD:
 
753
 
                    # This should never happen
 
754
 
                    logger.error("Child process vanished",
 
759
 
                gobject.source_remove(self.checker_callback_tag)
 
760
 
                self.checker_callback(pid, status, command)
 
 
709
                logger.error("Failed to start subprocess: %s",
 
761
711
        # Re-run this periodically if run by gobject.timeout_add
 
 
813
 
def dbus_interface_annotations(dbus_interface):
 
814
 
    """Decorator for marking functions returning interface annotations
 
818
 
    @dbus_interface_annotations("org.example.Interface")
 
819
 
    def _foo(self):  # Function name does not matter
 
820
 
        return {"org.freedesktop.DBus.Deprecated": "true",
 
821
 
                "org.freedesktop.DBus.Property.EmitsChangedSignal":
 
825
 
        func._dbus_is_interface = True
 
826
 
        func._dbus_interface = dbus_interface
 
827
 
        func._dbus_name = dbus_interface
 
832
 
def dbus_annotations(annotations):
 
833
 
    """Decorator to annotate D-Bus methods, signals or properties
 
836
 
    @dbus_service_property("org.example.Interface", signature="b",
 
838
 
    @dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
 
839
 
                        "org.freedesktop.DBus.Property."
 
840
 
                        "EmitsChangedSignal": "false"})
 
841
 
    def Property_dbus_property(self):
 
842
 
        return dbus.Boolean(False)
 
845
 
        func._dbus_annotations = annotations
 
850
763
class DBusPropertyException(dbus.exceptions.DBusException):
 
851
764
    """A base class for D-Bus property-related exceptions
 
 
878
 
    def _is_dbus_thing(thing):
 
879
 
        """Returns a function testing if an attribute is a D-Bus thing
 
881
 
        If called like _is_dbus_thing("method") it returns a function
 
882
 
        suitable for use as predicate to inspect.getmembers().
 
884
 
        return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
 
 
791
    def _is_dbus_property(obj):
 
 
792
        return getattr(obj, "_dbus_is_property", False)
 
887
 
    def _get_all_dbus_things(self, thing):
 
 
794
    def _get_all_dbus_properties(self):
 
888
795
        """Returns a generator of (name, attribute) pairs
 
890
 
        return ((getattr(athing.__get__(self), "_dbus_name",
 
892
 
                 athing.__get__(self))
 
 
797
        return ((prop.__get__(self)._dbus_name, prop.__get__(self))
 
893
798
                for cls in self.__class__.__mro__
 
895
 
                inspect.getmembers(cls,
 
896
 
                                   self._is_dbus_thing(thing)))
 
 
800
                inspect.getmembers(cls, self._is_dbus_property))
 
898
802
    def _get_dbus_property(self, interface_name, property_name):
 
899
803
        """Returns a bound method if one exists which is a D-Bus
 
 
988
887
                e.setAttribute("access", prop._dbus_access)
 
990
889
            for if_tag in document.getElementsByTagName("interface"):
 
992
890
                for tag in (make_tag(document, name, prop)
 
994
 
                            in self._get_all_dbus_things("property")
 
 
892
                            in self._get_all_dbus_properties()
 
995
893
                            if prop._dbus_interface
 
996
894
                            == if_tag.getAttribute("name")):
 
997
895
                    if_tag.appendChild(tag)
 
998
 
                # Add annotation tags
 
999
 
                for typ in ("method", "signal", "property"):
 
1000
 
                    for tag in if_tag.getElementsByTagName(typ):
 
1002
 
                        for name, prop in (self.
 
1003
 
                                           _get_all_dbus_things(typ)):
 
1004
 
                            if (name == tag.getAttribute("name")
 
1005
 
                                and prop._dbus_interface
 
1006
 
                                == if_tag.getAttribute("name")):
 
1007
 
                                annots.update(getattr
 
1009
 
                                               "_dbus_annotations",
 
1011
 
                        for name, value in annots.iteritems():
 
1012
 
                            ann_tag = document.createElement(
 
1014
 
                            ann_tag.setAttribute("name", name)
 
1015
 
                            ann_tag.setAttribute("value", value)
 
1016
 
                            tag.appendChild(ann_tag)
 
1017
 
                # Add interface annotation tags
 
1018
 
                for annotation, value in dict(
 
1019
 
                    itertools.chain.from_iterable(
 
1020
 
                        annotations().iteritems()
 
1021
 
                        for name, annotations in
 
1022
 
                        self._get_all_dbus_things("interface")
 
1023
 
                        if name == if_tag.getAttribute("name")
 
1025
 
                    ann_tag = document.createElement("annotation")
 
1026
 
                    ann_tag.setAttribute("name", annotation)
 
1027
 
                    ann_tag.setAttribute("value", value)
 
1028
 
                    if_tag.appendChild(ann_tag)
 
1029
896
                # Add the names to the return values for the
 
1030
897
                # "org.freedesktop.DBus.Properties" methods
 
1031
898
                if (if_tag.getAttribute("name")
 
 
1058
925
                       variant_level=variant_level)
 
1061
 
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
 
1062
 
    """A class decorator; applied to a subclass of
 
1063
 
    dbus.service.Object, it will add alternate D-Bus attributes with
 
1064
 
    interface names according to the "alt_interface_names" mapping.
 
1067
 
    @alternate_dbus_interfaces({"org.example.Interface":
 
1068
 
                                    "net.example.AlternateInterface"})
 
1069
 
    class SampleDBusObject(dbus.service.Object):
 
1070
 
        @dbus.service.method("org.example.Interface")
 
1071
 
        def SampleDBusMethod():
 
1074
 
    The above "SampleDBusMethod" on "SampleDBusObject" will be
 
1075
 
    reachable via two interfaces: "org.example.Interface" and
 
1076
 
    "net.example.AlternateInterface", the latter of which will have
 
1077
 
    its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
 
1078
 
    "true", unless "deprecate" is passed with a False value.
 
1080
 
    This works for methods and signals, and also for D-Bus properties
 
1081
 
    (from DBusObjectWithProperties) and interfaces (from the
 
1082
 
    dbus_interface_annotations decorator).
 
 
928
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
 
 
930
    """Applied to an empty subclass of a D-Bus object, this metaclass
 
 
931
    will add additional D-Bus attributes matching a certain pattern.
 
1085
 
        for orig_interface_name, alt_interface_name in (
 
1086
 
            alt_interface_names.iteritems()):
 
1088
 
            interface_names = set()
 
1089
 
            # Go though all attributes of the class
 
1090
 
            for attrname, attribute in inspect.getmembers(cls):
 
 
933
    def __new__(mcs, name, bases, attr):
 
 
934
        # Go through all the base classes which could have D-Bus
 
 
935
        # methods, signals, or properties in them
 
 
936
        for base in (b for b in bases
 
 
937
                     if issubclass(b, dbus.service.Object)):
 
 
938
            # Go though all attributes of the base class
 
 
939
            for attrname, attribute in inspect.getmembers(base):
 
1091
940
                # Ignore non-D-Bus attributes, and D-Bus attributes
 
1092
941
                # with the wrong interface name
 
1093
942
                if (not hasattr(attribute, "_dbus_interface")
 
1094
943
                    or not attribute._dbus_interface
 
1095
 
                    .startswith(orig_interface_name)):
 
 
944
                    .startswith("se.recompile.Mandos")):
 
1097
946
                # Create an alternate D-Bus interface name based on
 
1098
947
                # the current name
 
1099
948
                alt_interface = (attribute._dbus_interface
 
1100
 
                                 .replace(orig_interface_name,
 
1101
 
                                          alt_interface_name))
 
1102
 
                interface_names.add(alt_interface)
 
 
949
                                 .replace("se.recompile.Mandos",
 
 
950
                                          "se.bsnet.fukt.Mandos"))
 
1103
951
                # Is this a D-Bus signal?
 
1104
952
                if getattr(attribute, "_dbus_is_signal", False):
 
1105
 
                    # Extract the original non-method undecorated
 
1106
 
                    # function by black magic
 
 
953
                    # Extract the original non-method function by
 
1107
955
                    nonmethod_func = (dict(
 
1108
956
                            zip(attribute.func_code.co_freevars,
 
1109
957
                                attribute.__closure__))["func"]
 
 
1120
968
                                nonmethod_func.func_name,
 
1121
969
                                nonmethod_func.func_defaults,
 
1122
970
                                nonmethod_func.func_closure)))
 
1123
 
                    # Copy annotations, if any
 
1125
 
                        new_function._dbus_annotations = (
 
1126
 
                            dict(attribute._dbus_annotations))
 
1127
 
                    except AttributeError:
 
1129
971
                    # Define a creator of a function to call both the
 
1130
 
                    # original and alternate functions, so both the
 
1131
 
                    # original and alternate signals gets sent when
 
1132
 
                    # the function is called
 
 
972
                    # old and new functions, so both the old and new
 
 
973
                    # signals gets sent when the function is called
 
1133
974
                    def fixscope(func1, func2):
 
1134
975
                        """This function is a scope container to pass
 
1135
976
                        func1 and func2 to the "call_both" function
 
 
1184
1020
                                        attribute.func_name,
 
1185
1021
                                        attribute.func_defaults,
 
1186
1022
                                        attribute.func_closure)))
 
1187
 
                    # Copy annotations, if any
 
1189
 
                        attr[attrname]._dbus_annotations = (
 
1190
 
                            dict(attribute._dbus_annotations))
 
1191
 
                    except AttributeError:
 
1193
 
                # Is this a D-Bus interface?
 
1194
 
                elif getattr(attribute, "_dbus_is_interface", False):
 
1195
 
                    # Create a new, but exactly alike, function
 
1196
 
                    # object.  Decorate it to be a new D-Bus interface
 
1197
 
                    # with the alternate D-Bus interface name.  Add it
 
1199
 
                    attr[attrname] = (dbus_interface_annotations
 
1202
 
                                       (attribute.func_code,
 
1203
 
                                        attribute.func_globals,
 
1204
 
                                        attribute.func_name,
 
1205
 
                                        attribute.func_defaults,
 
1206
 
                                        attribute.func_closure)))
 
1208
 
                # Deprecate all alternate interfaces
 
1209
 
                iname="_AlternateDBusNames_interface_annotation{0}"
 
1210
 
                for interface_name in interface_names:
 
1211
 
                    @dbus_interface_annotations(interface_name)
 
1213
 
                        return { "org.freedesktop.DBus.Deprecated":
 
1215
 
                    # Find an unused name
 
1216
 
                    for aname in (iname.format(i)
 
1217
 
                                  for i in itertools.count()):
 
1218
 
                        if aname not in attr:
 
1222
 
                # Replace the class with a new subclass of it with
 
1223
 
                # methods, signals, etc. as created above.
 
1224
 
                cls = type(b"{0}Alternate".format(cls.__name__),
 
1230
 
@alternate_dbus_interfaces({"se.recompile.Mandos":
 
1231
 
                                "se.bsnet.fukt.Mandos"})
 
 
1023
        return type.__new__(mcs, name, bases, attr)
 
1232
1026
class ClientDBus(Client, DBusObjectWithProperties):
 
1233
1027
    """A Client class using D-Bus
 
 
1560
1356
    def Timeout_dbus_property(self, value=None):
 
1561
1357
        if value is None:       # get
 
1562
1358
            return dbus.UInt64(self.timeout_milliseconds())
 
1563
 
        old_timeout = self.timeout
 
1564
1359
        self.timeout = datetime.timedelta(0, 0, 0, value)
 
1565
 
        # Reschedule disabling
 
1567
 
            now = datetime.datetime.utcnow()
 
1568
 
            self.expires += self.timeout - old_timeout
 
1569
 
            if self.expires <= now:
 
1570
 
                # The timeout has passed
 
1573
 
                if (getattr(self, "disable_initiator_tag", None)
 
1576
 
                gobject.source_remove(self.disable_initiator_tag)
 
1577
 
                self.disable_initiator_tag = (
 
1578
 
                    gobject.timeout_add(
 
1579
 
                        timedelta_to_milliseconds(self.expires - now),
 
 
1360
        if getattr(self, "disable_initiator_tag", None) is None:
 
 
1362
        # Reschedule timeout
 
 
1363
        gobject.source_remove(self.disable_initiator_tag)
 
 
1364
        self.disable_initiator_tag = None
 
 
1366
        time_to_die = timedelta_to_milliseconds((self
 
 
1371
        if time_to_die <= 0:
 
 
1372
            # The timeout has passed
 
 
1375
            self.expires = (datetime.datetime.utcnow()
 
 
1376
                            + datetime.timedelta(milliseconds =
 
 
1378
            self.disable_initiator_tag = (gobject.timeout_add
 
 
1379
                                          (time_to_die, self.disable))
 
1582
1381
    # ExtendedTimeout - property
 
1583
1382
    @dbus_service_property(_interface, signature="t",
 
 
1929
1735
        use_ipv6:       Boolean; to use IPv6 or not
 
1931
1737
    def __init__(self, server_address, RequestHandlerClass,
 
1932
 
                 interface=None, use_ipv6=True, socketfd=None):
 
1933
 
        """If socketfd is set, use that file descriptor instead of
 
1934
 
        creating a new one with socket.socket().
 
 
1738
                 interface=None, use_ipv6=True):
 
1936
1739
        self.interface = interface
 
1938
1741
            self.address_family = socket.AF_INET6
 
1939
 
        if socketfd is not None:
 
1940
 
            # Save the file descriptor
 
1941
 
            self.socketfd = socketfd
 
1942
 
            # Save the original socket.socket() function
 
1943
 
            self.socket_socket = socket.socket
 
1944
 
            # To implement --socket, we monkey patch socket.socket.
 
1946
 
            # (When socketserver.TCPServer is a new-style class, we
 
1947
 
            # could make self.socket into a property instead of monkey
 
1948
 
            # patching socket.socket.)
 
1950
 
            # Create a one-time-only replacement for socket.socket()
 
1951
 
            @functools.wraps(socket.socket)
 
1952
 
            def socket_wrapper(*args, **kwargs):
 
1953
 
                # Restore original function so subsequent calls are
 
1955
 
                socket.socket = self.socket_socket
 
1956
 
                del self.socket_socket
 
1957
 
                # This time only, return a new socket object from the
 
1958
 
                # saved file descriptor.
 
1959
 
                return socket.fromfd(self.socketfd, *args, **kwargs)
 
1960
 
            # Replace socket.socket() function with wrapper
 
1961
 
            socket.socket = socket_wrapper
 
1962
 
        # The socketserver.TCPServer.__init__ will call
 
1963
 
        # socket.socket(), which might be our replacement,
 
1964
 
        # socket_wrapper(), if socketfd was set.
 
1965
1742
        socketserver.TCPServer.__init__(self, server_address,
 
1966
1743
                                        RequestHandlerClass)
 
1968
1744
    def server_bind(self):
 
1969
1745
        """This overrides the normal server_bind() function
 
1970
1746
        to bind to an interface if one was specified, and also NOT to
 
 
1979
1755
                    self.socket.setsockopt(socket.SOL_SOCKET,
 
1980
1756
                                           SO_BINDTODEVICE,
 
1981
 
                                           str(self.interface + '\0'))
 
1982
1759
                except socket.error as error:
 
1983
 
                    if error.errno == errno.EPERM:
 
1984
 
                        logger.error("No permission to bind to"
 
1985
 
                                     " interface %s", self.interface)
 
1986
 
                    elif error.errno == errno.ENOPROTOOPT:
 
 
1760
                    if error[0] == errno.EPERM:
 
 
1761
                        logger.error("No permission to"
 
 
1762
                                     " bind to interface %s",
 
 
1764
                    elif error[0] == errno.ENOPROTOOPT:
 
1987
1765
                        logger.error("SO_BINDTODEVICE not available;"
 
1988
1766
                                     " cannot bind to interface %s",
 
1989
1767
                                     self.interface)
 
1990
 
                    elif error.errno == errno.ENODEV:
 
1991
 
                        logger.error("Interface %s does not exist,"
 
1992
 
                                     " cannot bind", self.interface)
 
1995
1770
        # Only bind(2) the socket if we really need to.
 
 
2056
1830
    def handle_ipc(self, source, condition, parent_pipe=None,
 
2057
1831
                   proc = None, client_object=None):
 
 
1833
            gobject.IO_IN: "IN",   # There is data to read.
 
 
1834
            gobject.IO_OUT: "OUT", # Data can be written (without
 
 
1836
            gobject.IO_PRI: "PRI", # There is urgent data to read.
 
 
1837
            gobject.IO_ERR: "ERR", # Error condition.
 
 
1838
            gobject.IO_HUP: "HUP"  # Hung up (the connection has been
 
 
1839
                                    # broken, usually for pipes and
 
 
1842
        conditions_string = ' | '.join(name
 
 
1844
                                       condition_names.iteritems()
 
 
1845
                                       if cond & condition)
 
2058
1846
        # error, or the other end of multiprocessing.Pipe has closed
 
2059
 
        if condition & (gobject.IO_ERR | gobject.IO_HUP):
 
 
1847
        if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
 
2060
1848
            # Wait for other process to exit
 
 
2123
 
def rfc3339_duration_to_delta(duration):
 
2124
 
    """Parse an RFC 3339 "duration" and return a datetime.timedelta
 
2126
 
    >>> rfc3339_duration_to_delta("P7D")
 
2127
 
    datetime.timedelta(7)
 
2128
 
    >>> rfc3339_duration_to_delta("PT60S")
 
2129
 
    datetime.timedelta(0, 60)
 
2130
 
    >>> rfc3339_duration_to_delta("PT60M")
 
2131
 
    datetime.timedelta(0, 3600)
 
2132
 
    >>> rfc3339_duration_to_delta("PT24H")
 
2133
 
    datetime.timedelta(1)
 
2134
 
    >>> rfc3339_duration_to_delta("P1W")
 
2135
 
    datetime.timedelta(7)
 
2136
 
    >>> rfc3339_duration_to_delta("PT5M30S")
 
2137
 
    datetime.timedelta(0, 330)
 
2138
 
    >>> rfc3339_duration_to_delta("P1DT3M20S")
 
2139
 
    datetime.timedelta(1, 200)
 
2142
 
    # Parsing an RFC 3339 duration with regular expressions is not
 
2143
 
    # possible - there would have to be multiple places for the same
 
2144
 
    # values, like seconds.  The current code, while more esoteric, is
 
2145
 
    # cleaner without depending on a parsing library.  If Python had a
 
2146
 
    # built-in library for parsing we would use it, but we'd like to
 
2147
 
    # avoid excessive use of external libraries.
 
2149
 
    # New type for defining tokens, syntax, and semantics all-in-one
 
2150
 
    Token = collections.namedtuple("Token",
 
2151
 
                                   ("regexp", # To match token; if
 
2152
 
                                              # "value" is not None,
 
2153
 
                                              # must have a "group"
 
2155
 
                                    "value",  # datetime.timedelta or
 
2157
 
                                    "followers")) # Tokens valid after
 
2159
 
    # RFC 3339 "duration" tokens, syntax, and semantics; taken from
 
2160
 
    # the "duration" ABNF definition in RFC 3339, Appendix A.
 
2161
 
    token_end = Token(re.compile(r"$"), None, frozenset())
 
2162
 
    token_second = Token(re.compile(r"(\d+)S"),
 
2163
 
                         datetime.timedelta(seconds=1),
 
2164
 
                         frozenset((token_end,)))
 
2165
 
    token_minute = Token(re.compile(r"(\d+)M"),
 
2166
 
                         datetime.timedelta(minutes=1),
 
2167
 
                         frozenset((token_second, token_end)))
 
2168
 
    token_hour = Token(re.compile(r"(\d+)H"),
 
2169
 
                       datetime.timedelta(hours=1),
 
2170
 
                       frozenset((token_minute, token_end)))
 
2171
 
    token_time = Token(re.compile(r"T"),
 
2173
 
                       frozenset((token_hour, token_minute,
 
2175
 
    token_day = Token(re.compile(r"(\d+)D"),
 
2176
 
                      datetime.timedelta(days=1),
 
2177
 
                      frozenset((token_time, token_end)))
 
2178
 
    token_month = Token(re.compile(r"(\d+)M"),
 
2179
 
                        datetime.timedelta(weeks=4),
 
2180
 
                        frozenset((token_day, token_end)))
 
2181
 
    token_year = Token(re.compile(r"(\d+)Y"),
 
2182
 
                       datetime.timedelta(weeks=52),
 
2183
 
                       frozenset((token_month, token_end)))
 
2184
 
    token_week = Token(re.compile(r"(\d+)W"),
 
2185
 
                       datetime.timedelta(weeks=1),
 
2186
 
                       frozenset((token_end,)))
 
2187
 
    token_duration = Token(re.compile(r"P"), None,
 
2188
 
                           frozenset((token_year, token_month,
 
2189
 
                                      token_day, token_time,
 
2191
 
    # Define starting values
 
2192
 
    value = datetime.timedelta() # Value so far
 
2194
 
    followers = frozenset(token_duration,) # Following valid tokens
 
2195
 
    s = duration                # String left to parse
 
2196
 
    # Loop until end token is found
 
2197
 
    while found_token is not token_end:
 
2198
 
        # Search for any currently valid tokens
 
2199
 
        for token in followers:
 
2200
 
            match = token.regexp.match(s)
 
2201
 
            if match is not None:
 
2203
 
                if token.value is not None:
 
2204
 
                    # Value found, parse digits
 
2205
 
                    factor = int(match.group(1), 10)
 
2206
 
                    # Add to value so far
 
2207
 
                    value += factor * token.value
 
2208
 
                # Strip token from string
 
2209
 
                s = token.regexp.sub("", s, 1)
 
2212
 
                # Set valid next tokens
 
2213
 
                followers = found_token.followers
 
2216
 
            # No currently valid tokens were found
 
2217
 
            raise ValueError("Invalid RFC 3339 duration")
 
2222
1911
def string_to_delta(interval):
 
2223
1912
    """Parse a string and return a datetime.timedelta
 
 
2323
2005
    parser.add_argument("--no-dbus", action="store_false",
 
2324
2006
                        dest="use_dbus", help="Do not provide D-Bus"
 
2325
 
                        " system bus interface", default=None)
 
 
2007
                        " system bus interface")
 
2326
2008
    parser.add_argument("--no-ipv6", action="store_false",
 
2327
 
                        dest="use_ipv6", help="Do not use IPv6",
 
 
2009
                        dest="use_ipv6", help="Do not use IPv6")
 
2329
2010
    parser.add_argument("--no-restore", action="store_false",
 
2330
2011
                        dest="restore", help="Do not restore stored"
 
2331
 
                        " state", default=None)
 
2332
 
    parser.add_argument("--socket", type=int,
 
2333
 
                        help="Specify a file descriptor to a network"
 
2334
 
                        " socket to use instead of creating one")
 
2335
2013
    parser.add_argument("--statedir", metavar="DIR",
 
2336
2014
                        help="Directory to save/restore state in")
 
2337
 
    parser.add_argument("--foreground", action="store_true",
 
2338
 
                        help="Run in foreground", default=None)
 
2340
2016
    options = parser.parse_args()
 
2342
2018
    if options.check:
 
2344
 
        fail_count, test_count = doctest.testmod()
 
2345
 
        sys.exit(os.EX_OK if fail_count == 0 else 1)
 
2347
2023
    # Default values for config file for server-global settings
 
2348
2024
    server_defaults = { "interface": "",
 
 
2369
2043
    # Convert the SafeConfigParser object to a dict
 
2370
2044
    server_settings = server_config.defaults()
 
2371
2045
    # Use the appropriate methods on the non-string config options
 
2372
 
    for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
 
 
2046
    for option in ("debug", "use_dbus", "use_ipv6"):
 
2373
2047
        server_settings[option] = server_config.getboolean("DEFAULT",
 
2375
2049
    if server_settings["port"]:
 
2376
2050
        server_settings["port"] = server_config.getint("DEFAULT",
 
2378
 
    if server_settings["socket"]:
 
2379
 
        server_settings["socket"] = server_config.getint("DEFAULT",
 
2381
 
        # Later, stdin will, and stdout and stderr might, be dup'ed
 
2382
 
        # over with an opened os.devnull.  But we don't want this to
 
2383
 
        # happen with a supplied network socket.
 
2384
 
        if 0 <= server_settings["socket"] <= 2:
 
2385
 
            server_settings["socket"] = os.dup(server_settings
 
2387
2052
    del server_config
 
2389
2054
    # Override the settings from the config file with command line
 
 
2418
2076
    use_ipv6 = server_settings["use_ipv6"]
 
2419
2077
    stored_state_path = os.path.join(server_settings["statedir"],
 
2420
2078
                                     stored_state_file)
 
2421
 
    foreground = server_settings["foreground"]
 
2424
 
        initlogger(debug, logging.DEBUG)
 
 
2081
        initlogger(logging.DEBUG)
 
2426
2083
        if not debuglevel:
 
2429
2086
            level = getattr(logging, debuglevel.upper())
 
2430
 
            initlogger(debug, level)
 
2432
2089
    if server_settings["servicename"] != "Mandos":
 
2433
2090
        syslogger.setFormatter(logging.Formatter
 
2434
 
                               ('Mandos ({0}) [%(process)d]:'
 
2435
 
                                ' %(levelname)s: %(message)s'
 
2436
 
                                .format(server_settings
 
 
2091
                               ('Mandos (%s) [%%(process)d]:'
 
 
2092
                                ' %%(levelname)s: %%(message)s'
 
 
2093
                                % server_settings["servicename"]))
 
2439
2095
    # Parse config file with clients
 
2440
 
    client_config = configparser.SafeConfigParser(Client
 
 
2096
    client_config = configparser.SafeConfigParser(Client.client_defaults)
 
2442
2097
    client_config.read(os.path.join(server_settings["configdir"],
 
2443
2098
                                    "clients.conf"))
 
 
2453
2108
                              use_ipv6=use_ipv6,
 
2454
2109
                              gnutls_priority=
 
2455
2110
                              server_settings["priority"],
 
2457
 
                              socketfd=(server_settings["socket"]
 
2460
 
        pidfilename = "/run/mandos.pid"
 
2461
 
        if not os.path.isdir("/run/."):
 
2462
 
            pidfilename = "/var/run/mandos.pid"
 
 
2113
        pidfilename = "/var/run/mandos.pid"
 
2465
2115
            pidfile = open(pidfilename, "w")
 
2466
 
        except IOError as e:
 
2467
 
            logger.error("Could not open file %r", pidfilename,
 
 
2117
            logger.error("Could not open file %r", pidfilename)
 
2470
 
    for name in ("_mandos", "mandos", "nobody"):
 
 
2120
        uid = pwd.getpwnam("_mandos").pw_uid
 
 
2121
        gid = pwd.getpwnam("_mandos").pw_gid
 
2472
 
            uid = pwd.getpwnam(name).pw_uid
 
2473
 
            gid = pwd.getpwnam(name).pw_gid
 
 
2124
            uid = pwd.getpwnam("mandos").pw_uid
 
 
2125
            gid = pwd.getpwnam("mandos").pw_gid
 
2475
2126
        except KeyError:
 
 
2128
                uid = pwd.getpwnam("nobody").pw_uid
 
 
2129
                gid = pwd.getpwnam("nobody").pw_gid
 
2483
2136
    except OSError as error:
 
2484
 
        if error.errno != errno.EPERM:
 
 
2137
        if error[0] != errno.EPERM:
 
2488
2141
        # Enable all possible GnuTLS debugging
 
 
2499
2152
         .gnutls_global_set_log_function(debug_gnutls))
 
2501
2154
        # Redirect stdin so all checkers get /dev/null
 
2502
 
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
 
 
2155
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
2503
2156
        os.dup2(null, sys.stdin.fileno())
 
 
2160
        # No console logging
 
 
2161
        logger.removeHandler(console)
 
2507
2163
    # Need to fork before connecting to D-Bus
 
2509
2165
        # Close all input and output, do double fork, etc.
 
2512
 
    # multiprocessing will use threads, so before we use gobject we
 
2513
 
    # need to inform gobject that threads will be used.
 
2514
 
    gobject.threads_init()
 
2516
2168
    global main_loop
 
2517
2169
    # From the Avahi example code
 
2518
 
    DBusGMainLoop(set_as_default=True)
 
 
2170
    DBusGMainLoop(set_as_default=True )
 
2519
2171
    main_loop = gobject.MainLoop()
 
2520
2172
    bus = dbus.SystemBus()
 
2521
2173
    # End of Avahi example code
 
 
2567
2212
                                                     (stored_state))
 
2568
2213
            os.remove(stored_state_path)
 
2569
2214
        except IOError as e:
 
2570
 
            if e.errno == errno.ENOENT:
 
2571
 
                logger.warning("Could not load persistent state: {0}"
 
2572
 
                                .format(os.strerror(e.errno)))
 
2574
 
                logger.critical("Could not load persistent state:",
 
 
2215
            logger.warning("Could not load persistent state: {0}"
 
 
2217
            if e.errno != errno.ENOENT:
 
2577
 
        except EOFError as e:
 
2578
 
            logger.warning("Could not load persistent state: "
 
2579
 
                           "EOFError:", exc_info=e)
 
2581
2220
    with PGPEngine() as pgp:
 
2582
2221
        for client_name, client in clients_data.iteritems():
 
2583
 
            # Skip removed clients
 
2584
 
            if client_name not in client_settings:
 
2587
2222
            # Decide which value to use after restoring saved state.
 
2588
2223
            # We have three different values: Old config file,
 
2589
2224
            # new config file, and saved state.
 
 
2604
2239
            # Clients who has passed its expire date can still be
 
2605
2240
            # enabled if its last checker was successful.  Clients
 
2606
 
            # whose checker succeeded before we stored its state is
 
2607
 
            # assumed to have successfully run all checkers during
 
 
2241
            # whose checker failed before we stored its state is
 
 
2242
            # assumed to have failed all checkers during downtime.
 
2609
2243
            if client["enabled"]:
 
2610
2244
                if datetime.datetime.utcnow() >= client["expires"]:
 
2611
2245
                    if not client["last_checked_ok"]:
 
2612
2246
                        logger.warning(
 
2613
2247
                            "disabling client {0} - Client never "
 
2614
 
                            "performed a successful checker"
 
2615
 
                            .format(client_name))
 
 
2248
                            "performed a successfull checker"
 
 
2249
                            .format(client["name"]))
 
2616
2250
                        client["enabled"] = False
 
2617
2251
                    elif client["last_checker_status"] != 0:
 
2618
2252
                        logger.warning(
 
2619
2253
                            "disabling client {0} - Client "
 
2620
2254
                            "last checker failed with error code {1}"
 
2621
 
                            .format(client_name,
 
 
2255
                            .format(client["name"],
 
2622
2256
                                    client["last_checker_status"]))
 
2623
2257
                        client["enabled"] = False
 
2625
2259
                        client["expires"] = (datetime.datetime
 
2627
2261
                                             + client["timeout"])
 
2628
 
                        logger.debug("Last checker succeeded,"
 
2629
 
                                     " keeping {0} enabled"
 
2630
 
                                     .format(client_name))
 
2632
2264
                client["secret"] = (
 
2633
2265
                    pgp.decrypt(client["encrypted_secret"],
 
 
2639
2271
                             .format(client_name))
 
2640
2272
                client["secret"] = (
 
2641
2273
                    client_settings[client_name]["secret"])
 
2643
2276
    # Add/remove clients based on new changes made to config
 
2644
 
    for client_name in (set(old_client_settings)
 
2645
 
                        - set(client_settings)):
 
 
2277
    for client_name in set(old_client_settings) - set(client_settings):
 
2646
2278
        del clients_data[client_name]
 
2647
 
    for client_name in (set(client_settings)
 
2648
 
                        - set(old_client_settings)):
 
 
2279
    for client_name in set(client_settings) - set(old_client_settings):
 
2649
2280
        clients_data[client_name] = client_settings[client_name]
 
2651
 
    # Create all client objects
 
 
2282
    # Create clients all clients
 
2652
2283
    for client_name, client in clients_data.iteritems():
 
2653
2284
        tcp_server.clients[client_name] = client_class(
 
2654
 
            name = client_name, settings = client,
 
2655
 
            server_settings = server_settings)
 
 
2285
            name = client_name, settings = client)
 
2657
2287
    if not tcp_server.clients:
 
2658
2288
        logger.warning("No clients defined")
 
2661
 
        if pidfile is not None:
 
2665
 
                    pidfile.write(str(pid) + "\n".encode("utf-8"))
 
2667
 
                logger.error("Could not write to file %r with PID %d",
 
 
2294
                pidfile.write(str(pid) + "\n".encode("utf-8"))
 
 
2297
            logger.error("Could not write to file %r with PID %d",
 
 
2300
            # "pidfile" was never created
 
2670
2302
        del pidfilename
 
 
2303
        signal.signal(signal.SIGINT, signal.SIG_IGN)
 
2672
2305
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
 
2673
2306
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
 
2676
 
        @alternate_dbus_interfaces({"se.recompile.Mandos":
 
2677
 
                                        "se.bsnet.fukt.Mandos"})
 
2678
 
        class MandosDBusService(DBusObjectWithProperties):
 
 
2309
        class MandosDBusService(dbus.service.Object):
 
2679
2310
            """A D-Bus proxy object"""
 
2680
2311
            def __init__(self):
 
2681
2312
                dbus.service.Object.__init__(self, bus, "/")
 
2682
2313
            _interface = "se.recompile.Mandos"
 
2684
 
            @dbus_interface_annotations(_interface)
 
2686
 
                return { "org.freedesktop.DBus.Property"
 
2687
 
                         ".EmitsChangedSignal":
 
2690
2315
            @dbus.service.signal(_interface, signature="o")
 
2691
2316
            def ClientAdded(self, objpath):
 
 
2774
2400
                del client_settings[client.name]["secret"]
 
2777
 
            with (tempfile.NamedTemporaryFile
 
2778
 
                  (mode='wb', suffix=".pickle", prefix='clients-',
 
2779
 
                   dir=os.path.dirname(stored_state_path),
 
2780
 
                   delete=False)) as stored_state:
 
 
2403
            with os.fdopen(os.open(stored_state_path,
 
 
2404
                                   os.O_CREAT|os.O_WRONLY|os.O_TRUNC,
 
 
2405
                                   0600), "wb") as stored_state:
 
2781
2406
                pickle.dump((clients, client_settings), stored_state)
 
2782
 
                tempname=stored_state.name
 
2783
 
            os.rename(tempname, stored_state_path)
 
2784
2407
        except (IOError, OSError) as e:
 
2790
 
            if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
 
2791
 
                logger.warning("Could not save persistent state: {0}"
 
2792
 
                               .format(os.strerror(e.errno)))
 
2794
 
                logger.warning("Could not save persistent state:",
 
 
2408
            logger.warning("Could not save persistent state: {0}"
 
 
2410
            if e.errno not in (errno.ENOENT, errno.EACCES):
 
2798
2413
        # Delete all clients, and settings from config
 
 
2826
2441
    service.port = tcp_server.socket.getsockname()[1]
 
2828
2443
        logger.info("Now listening on address %r, port %d,"
 
2829
 
                    " flowinfo %d, scope_id %d",
 
2830
 
                    *tcp_server.socket.getsockname())
 
 
2444
                    " flowinfo %d, scope_id %d"
 
 
2445
                    % tcp_server.socket.getsockname())
 
2832
 
        logger.info("Now listening on address %r, port %d",
 
2833
 
                    *tcp_server.socket.getsockname())
 
 
2447
        logger.info("Now listening on address %r, port %d"
 
 
2448
                    % tcp_server.socket.getsockname())
 
2835
2450
    #service.interface = tcp_server.socket.getsockname()[3]