172
171
    def password_encode(self, password):
 
173
172
        # Passphrase can not be empty and can not contain newlines or
 
174
173
        # NUL bytes.  So we prefix it and hex encode it.
 
175
 
        encoded = b"mandos" + binascii.hexlify(password)
 
176
 
        if len(encoded) > 2048:
 
177
 
            # GnuPG can't handle long passwords, so encode differently
 
178
 
            encoded = (b"mandos" + password.replace(b"\\", b"\\\\")
 
179
 
                       .replace(b"\n", b"\\n")
 
180
 
                       .replace(b"\0", b"\\x00"))
 
 
174
        return b"mandos" + binascii.hexlify(password)
 
183
176
    def encrypt(self, data, password):
 
184
 
        passphrase = self.password_encode(password)
 
185
 
        with tempfile.NamedTemporaryFile(dir=self.tempdir
 
187
 
            passfile.write(passphrase)
 
189
 
            proc = subprocess.Popen(['gpg', '--symmetric',
 
193
 
                                    stdin = subprocess.PIPE,
 
194
 
                                    stdout = subprocess.PIPE,
 
195
 
                                    stderr = subprocess.PIPE)
 
196
 
            ciphertext, err = proc.communicate(input = data)
 
197
 
        if proc.returncode != 0:
 
 
177
        self.gnupg.passphrase = self.password_encode(password)
 
 
178
        with open(os.devnull) as devnull:
 
 
180
                proc = self.gnupg.run(['--symmetric'],
 
 
181
                                      create_fhs=['stdin', 'stdout'],
 
 
182
                                      attach_fhs={'stderr': devnull})
 
 
183
                with contextlib.closing(proc.handles['stdin']) as f:
 
 
185
                with contextlib.closing(proc.handles['stdout']) as f:
 
 
186
                    ciphertext = f.read()
 
 
190
        self.gnupg.passphrase = None
 
199
191
        return ciphertext
 
201
193
    def decrypt(self, data, password):
 
202
 
        passphrase = self.password_encode(password)
 
203
 
        with tempfile.NamedTemporaryFile(dir = self.tempdir
 
205
 
            passfile.write(passphrase)
 
207
 
            proc = subprocess.Popen(['gpg', '--decrypt',
 
211
 
                                    stdin = subprocess.PIPE,
 
212
 
                                    stdout = subprocess.PIPE,
 
213
 
                                    stderr = subprocess.PIPE)
 
214
 
            decrypted_plaintext, err = proc.communicate(input
 
216
 
        if proc.returncode != 0:
 
 
194
        self.gnupg.passphrase = self.password_encode(password)
 
 
195
        with open(os.devnull) as devnull:
 
 
197
                proc = self.gnupg.run(['--decrypt'],
 
 
198
                                      create_fhs=['stdin', 'stdout'],
 
 
199
                                      attach_fhs={'stderr': devnull})
 
 
200
                with contextlib.closing(proc.handles['stdin'] ) as f:
 
 
202
                with contextlib.closing(proc.handles['stdout']) as f:
 
 
203
                    decrypted_plaintext = f.read()
 
 
207
        self.gnupg.passphrase = None
 
218
208
        return decrypted_plaintext
 
221
212
class AvahiError(Exception):
 
222
213
    def __init__(self, value, *args, **kwargs):
 
223
214
        self.value = value
 
 
386
369
                                 self.server_state_changed)
 
387
370
        self.server_state_changed(self.server.GetState())
 
390
372
class AvahiServiceToSyslog(AvahiService):
 
391
373
    def rename(self):
 
392
374
        """Add the new name to the syslog messages"""
 
393
375
        ret = AvahiService.rename(self)
 
394
376
        syslogger.setFormatter(logging.Formatter
 
395
 
                               ('Mandos ({0}) [%(process)d]:'
 
396
 
                                ' %(levelname)s: %(message)s'
 
 
377
                               ('Mandos (%s) [%%(process)d]:'
 
 
378
                                ' %%(levelname)s: %%(message)s'
 
401
382
def timedelta_to_milliseconds(td):
 
402
383
    "Convert a datetime.timedelta() to milliseconds"
 
403
384
    return ((td.days * 24 * 60 * 60 * 1000)
 
404
385
            + (td.seconds * 1000)
 
405
386
            + (td.microseconds // 1000))
 
408
388
class Client(object):
 
409
389
    """A representation of a client host served by this server.
 
 
435
415
    last_checked_ok: datetime.datetime(); (UTC) or None
 
436
416
    last_checker_status: integer between 0 and 255 reflecting exit
 
437
417
                         status of last checker. -1 reflects crashed
 
438
 
                         checker, -2 means no checker completed yet.
 
439
419
    last_enabled: datetime.datetime(); (UTC) or None
 
440
420
    name:       string; from the config file, used in log messages and
 
441
421
                        D-Bus identifiers
 
442
422
    secret:     bytestring; sent verbatim (over TLS) to client
 
443
423
    timeout:    datetime.timedelta(); How long from last_checked_ok
 
444
424
                                      until this client is disabled
 
445
 
    extended_timeout:   extra long timeout when secret has been sent
 
 
425
    extended_timeout:   extra long timeout when password has been sent
 
446
426
    runtime_expansions: Allowed attributes for runtime expansion.
 
447
427
    expires:    datetime.datetime(); time (UTC) when a client will be
 
448
428
                disabled, or None
 
449
 
    server_settings: The server_settings dict from main()
 
452
431
    runtime_expansions = ("approval_delay", "approval_duration",
 
453
 
                          "created", "enabled", "expires",
 
454
 
                          "fingerprint", "host", "interval",
 
455
 
                          "last_approval_request", "last_checked_ok",
 
 
432
                          "created", "enabled", "fingerprint",
 
 
433
                          "host", "interval", "last_checked_ok",
 
456
434
                          "last_enabled", "name", "timeout")
 
457
 
    client_defaults = { "timeout": "PT5M",
 
458
 
                        "extended_timeout": "PT15M",
 
 
435
    client_defaults = { "timeout": "5m",
 
 
436
                        "extended_timeout": "15m",
 
460
438
                        "checker": "fping -q -- %%(host)s",
 
462
 
                        "approval_delay": "PT0S",
 
463
 
                        "approval_duration": "PT1S",
 
 
440
                        "approval_delay": "0s",
 
 
441
                        "approval_duration": "1s",
 
464
442
                        "approved_by_default": "True",
 
465
443
                        "enabled": "True",
 
 
523
501
            client["checker_command"] = section["checker"]
 
524
502
            client["last_approval_request"] = None
 
525
503
            client["last_checked_ok"] = None
 
526
 
            client["last_checker_status"] = -2
 
 
504
            client["last_checker_status"] = None
 
530
 
    def __init__(self, settings, name = None, server_settings=None):
 
 
509
    def __init__(self, settings, name = None):
 
 
510
        """Note: the 'checker' key in 'config' sets the
 
 
511
        'checker_command' attribute and *not* the 'checker'
 
532
 
        if server_settings is None:
 
534
 
        self.server_settings = server_settings
 
535
514
        # adding all client settings
 
536
515
        for setting, value in settings.iteritems():
 
537
516
            setattr(self, setting, value)
 
 
587
566
        if getattr(self, "enabled", False):
 
588
567
            # Already enabled
 
 
569
        self.send_changedstate()
 
590
570
        self.expires = datetime.datetime.utcnow() + self.timeout
 
591
571
        self.enabled = True
 
592
572
        self.last_enabled = datetime.datetime.utcnow()
 
593
573
        self.init_checker()
 
594
 
        self.send_changedstate()
 
596
575
    def disable(self, quiet=True):
 
597
576
        """Disable this client."""
 
598
577
        if not getattr(self, "enabled", False):
 
 
580
            self.send_changedstate()
 
601
582
            logger.info("Disabling client %s", self.name)
 
602
 
        if getattr(self, "disable_initiator_tag", None) is not None:
 
 
583
        if getattr(self, "disable_initiator_tag", False):
 
603
584
            gobject.source_remove(self.disable_initiator_tag)
 
604
585
            self.disable_initiator_tag = None
 
605
586
        self.expires = None
 
606
 
        if getattr(self, "checker_initiator_tag", None) is not None:
 
 
587
        if getattr(self, "checker_initiator_tag", False):
 
607
588
            gobject.source_remove(self.checker_initiator_tag)
 
608
589
            self.checker_initiator_tag = None
 
609
590
        self.stop_checker()
 
610
591
        self.enabled = False
 
612
 
            self.send_changedstate()
 
613
592
        # Do not run this again if called by a gobject.timeout_add
 
 
651
626
            logger.warning("Checker for %(name)s crashed?",
 
654
 
    def checked_ok(self):
 
655
 
        """Assert that the client has been seen, alive and well."""
 
656
 
        self.last_checked_ok = datetime.datetime.utcnow()
 
657
 
        self.last_checker_status = 0
 
660
 
    def bump_timeout(self, timeout=None):
 
661
 
        """Bump up the timeout for this client."""
 
 
629
    def checked_ok(self, timeout=None):
 
 
630
        """Bump up the timeout for this client.
 
 
632
        This should only be called when the client has been seen,
 
662
635
        if timeout is None:
 
663
636
            timeout = self.timeout
 
 
637
        self.last_checked_ok = datetime.datetime.utcnow()
 
664
638
        if self.disable_initiator_tag is not None:
 
665
639
            gobject.source_remove(self.disable_initiator_tag)
 
666
 
            self.disable_initiator_tag = None
 
667
640
        if getattr(self, "enabled", False):
 
668
641
            self.disable_initiator_tag = (gobject.timeout_add
 
669
642
                                          (timedelta_to_milliseconds
 
 
679
652
        If a checker already exists, leave it running and do
 
681
654
        # The reason for not killing a running checker is that if we
 
682
 
        # did that, and if a checker (for some reason) started running
 
683
 
        # slowly and taking more than 'interval' time, then the client
 
684
 
        # would inevitably timeout, since no checker would get a
 
685
 
        # chance to run to completion.  If we instead leave running
 
 
655
        # did that, then if a checker (for some reason) started
 
 
656
        # running slowly and taking more than 'interval' time, the
 
 
657
        # client would inevitably timeout, since no checker would get
 
 
658
        # a chance to run to completion.  If we instead leave running
 
686
659
        # checkers alone, the checker would have to take more time
 
687
660
        # than 'timeout' for the client to be disabled, which is as it
 
 
703
675
                                      self.current_checker_command)
 
704
676
        # Start a new checker if needed
 
705
677
        if self.checker is None:
 
706
 
            # Escape attributes for the shell
 
707
 
            escaped_attrs = dict(
 
708
 
                (attr, re.escape(unicode(getattr(self, attr))))
 
710
 
                self.runtime_expansions)
 
712
 
                command = self.checker_command % escaped_attrs
 
713
 
            except TypeError as error:
 
714
 
                logger.error('Could not format string "%s"',
 
715
 
                             self.checker_command, exc_info=error)
 
716
 
                return True # Try again later
 
 
679
                # In case checker_command has exactly one % operator
 
 
680
                command = self.checker_command % self.host
 
 
682
                # Escape attributes for the shell
 
 
683
                escaped_attrs = dict(
 
 
685
                     re.escape(unicode(str(getattr(self, attr, "")),
 
 
689
                    self.runtime_expansions)
 
 
692
                    command = self.checker_command % escaped_attrs
 
 
693
                except TypeError as error:
 
 
694
                    logger.error('Could not format string "%s":'
 
 
695
                                 ' %s', self.checker_command, error)
 
 
696
                    return True # Try again later
 
717
697
            self.current_checker_command = command
 
719
699
                logger.info("Starting checker %r for %s",
 
 
722
702
                # in normal mode, that is already done by daemon(),
 
723
703
                # and in debug mode we don't want to.  (Stdin is
 
724
704
                # always replaced by /dev/null.)
 
725
 
                # The exception is when not debugging but nevertheless
 
726
 
                # running in the foreground; use the previously
 
729
 
                if (not self.server_settings["debug"]
 
730
 
                    and self.server_settings["foreground"]):
 
731
 
                    popen_args.update({"stdout": wnull,
 
733
705
                self.checker = subprocess.Popen(command,
 
737
 
            except OSError as error:
 
738
 
                logger.error("Failed to start subprocess",
 
741
 
            self.checker_callback_tag = (gobject.child_watch_add
 
743
 
                                          self.checker_callback,
 
745
 
            # The checker may have completed before the gobject
 
746
 
            # watch was added.  Check for this.
 
 
708
                self.checker_callback_tag = (gobject.child_watch_add
 
 
710
                                              self.checker_callback,
 
 
712
                # The checker may have completed before the gobject
 
 
713
                # watch was added.  Check for this.
 
748
714
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
 
 
716
                    gobject.source_remove(self.checker_callback_tag)
 
 
717
                    self.checker_callback(pid, status, command)
 
749
718
            except OSError as error:
 
750
 
                if error.errno == errno.ECHILD:
 
751
 
                    # This should never happen
 
752
 
                    logger.error("Child process vanished",
 
757
 
                gobject.source_remove(self.checker_callback_tag)
 
758
 
                self.checker_callback(pid, status, command)
 
 
719
                logger.error("Failed to start subprocess: %s",
 
759
721
        # Re-run this periodically if run by gobject.timeout_add
 
 
811
 
def dbus_interface_annotations(dbus_interface):
 
812
 
    """Decorator for marking functions returning interface annotations
 
816
 
    @dbus_interface_annotations("org.example.Interface")
 
817
 
    def _foo(self):  # Function name does not matter
 
818
 
        return {"org.freedesktop.DBus.Deprecated": "true",
 
819
 
                "org.freedesktop.DBus.Property.EmitsChangedSignal":
 
823
 
        func._dbus_is_interface = True
 
824
 
        func._dbus_interface = dbus_interface
 
825
 
        func._dbus_name = dbus_interface
 
830
 
def dbus_annotations(annotations):
 
831
 
    """Decorator to annotate D-Bus methods, signals or properties
 
834
 
    @dbus_service_property("org.example.Interface", signature="b",
 
836
 
    @dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
 
837
 
                        "org.freedesktop.DBus.Property."
 
838
 
                        "EmitsChangedSignal": "false"})
 
839
 
    def Property_dbus_property(self):
 
840
 
        return dbus.Boolean(False)
 
843
 
        func._dbus_annotations = annotations
 
848
773
class DBusPropertyException(dbus.exceptions.DBusException):
 
849
774
    """A base class for D-Bus property-related exceptions
 
 
876
 
    def _is_dbus_thing(thing):
 
877
 
        """Returns a function testing if an attribute is a D-Bus thing
 
879
 
        If called like _is_dbus_thing("method") it returns a function
 
880
 
        suitable for use as predicate to inspect.getmembers().
 
882
 
        return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
 
 
801
    def _is_dbus_property(obj):
 
 
802
        return getattr(obj, "_dbus_is_property", False)
 
885
 
    def _get_all_dbus_things(self, thing):
 
 
804
    def _get_all_dbus_properties(self):
 
886
805
        """Returns a generator of (name, attribute) pairs
 
888
 
        return ((getattr(athing.__get__(self), "_dbus_name",
 
890
 
                 athing.__get__(self))
 
 
807
        return ((prop.__get__(self)._dbus_name, prop.__get__(self))
 
891
808
                for cls in self.__class__.__mro__
 
893
 
                inspect.getmembers(cls,
 
894
 
                                   self._is_dbus_thing(thing)))
 
 
810
                inspect.getmembers(cls, self._is_dbus_property))
 
896
812
    def _get_dbus_property(self, interface_name, property_name):
 
897
813
        """Returns a bound method if one exists which is a D-Bus
 
 
984
897
                e.setAttribute("access", prop._dbus_access)
 
986
899
            for if_tag in document.getElementsByTagName("interface"):
 
988
900
                for tag in (make_tag(document, name, prop)
 
990
 
                            in self._get_all_dbus_things("property")
 
 
902
                            in self._get_all_dbus_properties()
 
991
903
                            if prop._dbus_interface
 
992
904
                            == if_tag.getAttribute("name")):
 
993
905
                    if_tag.appendChild(tag)
 
994
 
                # Add annotation tags
 
995
 
                for typ in ("method", "signal", "property"):
 
996
 
                    for tag in if_tag.getElementsByTagName(typ):
 
998
 
                        for name, prop in (self.
 
999
 
                                           _get_all_dbus_things(typ)):
 
1000
 
                            if (name == tag.getAttribute("name")
 
1001
 
                                and prop._dbus_interface
 
1002
 
                                == if_tag.getAttribute("name")):
 
1003
 
                                annots.update(getattr
 
1005
 
                                               "_dbus_annotations",
 
1007
 
                        for name, value in annots.iteritems():
 
1008
 
                            ann_tag = document.createElement(
 
1010
 
                            ann_tag.setAttribute("name", name)
 
1011
 
                            ann_tag.setAttribute("value", value)
 
1012
 
                            tag.appendChild(ann_tag)
 
1013
 
                # Add interface annotation tags
 
1014
 
                for annotation, value in dict(
 
1015
 
                    itertools.chain.from_iterable(
 
1016
 
                        annotations().iteritems()
 
1017
 
                        for name, annotations in
 
1018
 
                        self._get_all_dbus_things("interface")
 
1019
 
                        if name == if_tag.getAttribute("name")
 
1021
 
                    ann_tag = document.createElement("annotation")
 
1022
 
                    ann_tag.setAttribute("name", annotation)
 
1023
 
                    ann_tag.setAttribute("value", value)
 
1024
 
                    if_tag.appendChild(ann_tag)
 
1025
906
                # Add the names to the return values for the
 
1026
907
                # "org.freedesktop.DBus.Properties" methods
 
1027
908
                if (if_tag.getAttribute("name")
 
 
1054
935
                       variant_level=variant_level)
 
1057
 
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
 
1058
 
    """A class decorator; applied to a subclass of
 
1059
 
    dbus.service.Object, it will add alternate D-Bus attributes with
 
1060
 
    interface names according to the "alt_interface_names" mapping.
 
1063
 
    @alternate_dbus_interfaces({"org.example.Interface":
 
1064
 
                                    "net.example.AlternateInterface"})
 
1065
 
    class SampleDBusObject(dbus.service.Object):
 
1066
 
        @dbus.service.method("org.example.Interface")
 
1067
 
        def SampleDBusMethod():
 
1070
 
    The above "SampleDBusMethod" on "SampleDBusObject" will be
 
1071
 
    reachable via two interfaces: "org.example.Interface" and
 
1072
 
    "net.example.AlternateInterface", the latter of which will have
 
1073
 
    its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
 
1074
 
    "true", unless "deprecate" is passed with a False value.
 
1076
 
    This works for methods and signals, and also for D-Bus properties
 
1077
 
    (from DBusObjectWithProperties) and interfaces (from the
 
1078
 
    dbus_interface_annotations decorator).
 
 
938
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
 
 
940
    """Applied to an empty subclass of a D-Bus object, this metaclass
 
 
941
    will add additional D-Bus attributes matching a certain pattern.
 
1081
 
        for orig_interface_name, alt_interface_name in (
 
1082
 
            alt_interface_names.iteritems()):
 
1084
 
            interface_names = set()
 
1085
 
            # Go though all attributes of the class
 
1086
 
            for attrname, attribute in inspect.getmembers(cls):
 
 
943
    def __new__(mcs, name, bases, attr):
 
 
944
        # Go through all the base classes which could have D-Bus
 
 
945
        # methods, signals, or properties in them
 
 
946
        for base in (b for b in bases
 
 
947
                     if issubclass(b, dbus.service.Object)):
 
 
948
            # Go though all attributes of the base class
 
 
949
            for attrname, attribute in inspect.getmembers(base):
 
1087
950
                # Ignore non-D-Bus attributes, and D-Bus attributes
 
1088
951
                # with the wrong interface name
 
1089
952
                if (not hasattr(attribute, "_dbus_interface")
 
1090
953
                    or not attribute._dbus_interface
 
1091
 
                    .startswith(orig_interface_name)):
 
 
954
                    .startswith("se.recompile.Mandos")):
 
1093
956
                # Create an alternate D-Bus interface name based on
 
1094
957
                # the current name
 
1095
958
                alt_interface = (attribute._dbus_interface
 
1096
 
                                 .replace(orig_interface_name,
 
1097
 
                                          alt_interface_name))
 
1098
 
                interface_names.add(alt_interface)
 
 
959
                                 .replace("se.recompile.Mandos",
 
 
960
                                          "se.bsnet.fukt.Mandos"))
 
1099
961
                # Is this a D-Bus signal?
 
1100
962
                if getattr(attribute, "_dbus_is_signal", False):
 
1101
 
                    # Extract the original non-method undecorated
 
1102
 
                    # function by black magic
 
 
963
                    # Extract the original non-method function by
 
1103
965
                    nonmethod_func = (dict(
 
1104
966
                            zip(attribute.func_code.co_freevars,
 
1105
967
                                attribute.__closure__))["func"]
 
 
1116
978
                                nonmethod_func.func_name,
 
1117
979
                                nonmethod_func.func_defaults,
 
1118
980
                                nonmethod_func.func_closure)))
 
1119
 
                    # Copy annotations, if any
 
1121
 
                        new_function._dbus_annotations = (
 
1122
 
                            dict(attribute._dbus_annotations))
 
1123
 
                    except AttributeError:
 
1125
981
                    # Define a creator of a function to call both the
 
1126
 
                    # original and alternate functions, so both the
 
1127
 
                    # original and alternate signals gets sent when
 
1128
 
                    # the function is called
 
 
982
                    # old and new functions, so both the old and new
 
 
983
                    # signals gets sent when the function is called
 
1129
984
                    def fixscope(func1, func2):
 
1130
985
                        """This function is a scope container to pass
 
1131
986
                        func1 and func2 to the "call_both" function
 
 
1180
1030
                                        attribute.func_name,
 
1181
1031
                                        attribute.func_defaults,
 
1182
1032
                                        attribute.func_closure)))
 
1183
 
                    # Copy annotations, if any
 
1185
 
                        attr[attrname]._dbus_annotations = (
 
1186
 
                            dict(attribute._dbus_annotations))
 
1187
 
                    except AttributeError:
 
1189
 
                # Is this a D-Bus interface?
 
1190
 
                elif getattr(attribute, "_dbus_is_interface", False):
 
1191
 
                    # Create a new, but exactly alike, function
 
1192
 
                    # object.  Decorate it to be a new D-Bus interface
 
1193
 
                    # with the alternate D-Bus interface name.  Add it
 
1195
 
                    attr[attrname] = (dbus_interface_annotations
 
1198
 
                                       (attribute.func_code,
 
1199
 
                                        attribute.func_globals,
 
1200
 
                                        attribute.func_name,
 
1201
 
                                        attribute.func_defaults,
 
1202
 
                                        attribute.func_closure)))
 
1204
 
                # Deprecate all alternate interfaces
 
1205
 
                iname="_AlternateDBusNames_interface_annotation{0}"
 
1206
 
                for interface_name in interface_names:
 
1207
 
                    @dbus_interface_annotations(interface_name)
 
1209
 
                        return { "org.freedesktop.DBus.Deprecated":
 
1211
 
                    # Find an unused name
 
1212
 
                    for aname in (iname.format(i)
 
1213
 
                                  for i in itertools.count()):
 
1214
 
                        if aname not in attr:
 
1218
 
                # Replace the class with a new subclass of it with
 
1219
 
                # methods, signals, etc. as created above.
 
1220
 
                cls = type(b"{0}Alternate".format(cls.__name__),
 
1226
 
@alternate_dbus_interfaces({"se.recompile.Mandos":
 
1227
 
                                "se.bsnet.fukt.Mandos"})
 
 
1033
        return type.__new__(mcs, name, bases, attr)
 
1228
1036
class ClientDBus(Client, DBusObjectWithProperties):
 
1229
1037
    """A Client class using D-Bus
 
 
1560
1366
    def Timeout_dbus_property(self, value=None):
 
1561
1367
        if value is None:       # get
 
1562
1368
            return dbus.UInt64(self.timeout_milliseconds())
 
1563
 
        old_timeout = self.timeout
 
1564
1369
        self.timeout = datetime.timedelta(0, 0, 0, value)
 
1565
 
        # Reschedule disabling
 
 
1370
        # Reschedule timeout
 
1566
1371
        if self.enabled:
 
1567
1372
            now = datetime.datetime.utcnow()
 
1568
 
            self.expires += self.timeout - old_timeout
 
1569
 
            if self.expires <= now:
 
 
1373
            time_to_die = timedelta_to_milliseconds(
 
 
1374
                (self.last_checked_ok + self.timeout) - now)
 
 
1375
            if time_to_die <= 0:
 
1570
1376
                # The timeout has passed
 
 
1379
                self.expires = (now +
 
 
1380
                                datetime.timedelta(milliseconds =
 
1573
1382
                if (getattr(self, "disable_initiator_tag", None)
 
1576
1385
                gobject.source_remove(self.disable_initiator_tag)
 
1577
 
                self.disable_initiator_tag = (
 
1578
 
                    gobject.timeout_add(
 
1579
 
                        timedelta_to_milliseconds(self.expires - now),
 
 
1386
                self.disable_initiator_tag = (gobject.timeout_add
 
1582
1390
    # ExtendedTimeout - property
 
1583
1391
    @dbus_service_property(_interface, signature="t",
 
 
1929
1744
        use_ipv6:       Boolean; to use IPv6 or not
 
1931
1746
    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().
 
 
1747
                 interface=None, use_ipv6=True):
 
1936
1748
        self.interface = interface
 
1938
1750
            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
1751
        socketserver.TCPServer.__init__(self, server_address,
 
1966
1752
                                        RequestHandlerClass)
 
1968
1753
    def server_bind(self):
 
1969
1754
        """This overrides the normal server_bind() function
 
1970
1755
        to bind to an interface if one was specified, and also NOT to
 
 
1979
1764
                    self.socket.setsockopt(socket.SOL_SOCKET,
 
1980
1765
                                           SO_BINDTODEVICE,
 
1981
 
                                           str(self.interface + '\0'))
 
1982
1768
                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:
 
 
1769
                    if error[0] == errno.EPERM:
 
 
1770
                        logger.error("No permission to"
 
 
1771
                                     " bind to interface %s",
 
 
1773
                    elif error[0] == errno.ENOPROTOOPT:
 
1987
1774
                        logger.error("SO_BINDTODEVICE not available;"
 
1988
1775
                                     " cannot bind to interface %s",
 
1989
1776
                                     self.interface)
 
1990
 
                    elif error.errno == errno.ENODEV:
 
1991
 
                        logger.error("Interface %s does not exist,"
 
1992
 
                                     " cannot bind", self.interface)
 
1995
1779
        # Only bind(2) the socket if we really need to.
 
 
2056
1839
    def handle_ipc(self, source, condition, parent_pipe=None,
 
2057
1840
                   proc = None, client_object=None):
 
 
1842
            gobject.IO_IN: "IN",   # There is data to read.
 
 
1843
            gobject.IO_OUT: "OUT", # Data can be written (without
 
 
1845
            gobject.IO_PRI: "PRI", # There is urgent data to read.
 
 
1846
            gobject.IO_ERR: "ERR", # Error condition.
 
 
1847
            gobject.IO_HUP: "HUP"  # Hung up (the connection has been
 
 
1848
                                    # broken, usually for pipes and
 
 
1851
        conditions_string = ' | '.join(name
 
 
1853
                                       condition_names.iteritems()
 
 
1854
                                       if cond & condition)
 
2058
1855
        # error, or the other end of multiprocessing.Pipe has closed
 
2059
 
        if condition & (gobject.IO_ERR | gobject.IO_HUP):
 
 
1856
        if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
 
2060
1857
            # 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
1920
def string_to_delta(interval):
 
2223
1921
    """Parse a string and return a datetime.timedelta
 
 
2323
2014
    parser.add_argument("--no-dbus", action="store_false",
 
2324
2015
                        dest="use_dbus", help="Do not provide D-Bus"
 
2325
 
                        " system bus interface", default=None)
 
 
2016
                        " system bus interface")
 
2326
2017
    parser.add_argument("--no-ipv6", action="store_false",
 
2327
 
                        dest="use_ipv6", help="Do not use IPv6",
 
 
2018
                        dest="use_ipv6", help="Do not use IPv6")
 
2329
2019
    parser.add_argument("--no-restore", action="store_false",
 
2330
2020
                        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
2022
    parser.add_argument("--statedir", metavar="DIR",
 
2336
2023
                        help="Directory to save/restore state in")
 
2337
 
    parser.add_argument("--foreground", action="store_true",
 
2338
 
                        help="Run in foreground", default=None)
 
2340
2025
    options = parser.parse_args()
 
 
2369
2052
    # Convert the SafeConfigParser object to a dict
 
2370
2053
    server_settings = server_config.defaults()
 
2371
2054
    # Use the appropriate methods on the non-string config options
 
2372
 
    for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
 
 
2055
    for option in ("debug", "use_dbus", "use_ipv6"):
 
2373
2056
        server_settings[option] = server_config.getboolean("DEFAULT",
 
2375
2058
    if server_settings["port"]:
 
2376
2059
        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
2061
    del server_config
 
2389
2063
    # Override the settings from the config file with command line
 
 
2453
2118
                              use_ipv6=use_ipv6,
 
2454
2119
                              gnutls_priority=
 
2455
2120
                              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"
 
 
2123
        pidfilename = "/var/run/mandos.pid"
 
2465
2125
            pidfile = open(pidfilename, "w")
 
2466
 
        except IOError as e:
 
2467
 
            logger.error("Could not open file %r", pidfilename,
 
 
2127
            logger.error("Could not open file %r", pidfilename)
 
2470
 
    for name in ("_mandos", "mandos", "nobody"):
 
 
2130
        uid = pwd.getpwnam("_mandos").pw_uid
 
 
2131
        gid = pwd.getpwnam("_mandos").pw_gid
 
2472
 
            uid = pwd.getpwnam(name).pw_uid
 
2473
 
            gid = pwd.getpwnam(name).pw_gid
 
 
2134
            uid = pwd.getpwnam("mandos").pw_uid
 
 
2135
            gid = pwd.getpwnam("mandos").pw_gid
 
2475
2136
        except KeyError:
 
 
2138
                uid = pwd.getpwnam("nobody").pw_uid
 
 
2139
                gid = pwd.getpwnam("nobody").pw_gid
 
2483
2146
    except OSError as error:
 
2484
 
        if error.errno != errno.EPERM:
 
 
2147
        if error[0] != errno.EPERM:
 
2488
2151
        # Enable all possible GnuTLS debugging
 
 
2499
2162
         .gnutls_global_set_log_function(debug_gnutls))
 
2501
2164
        # Redirect stdin so all checkers get /dev/null
 
2502
 
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
 
 
2165
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
2503
2166
        os.dup2(null, sys.stdin.fileno())
 
2507
2170
    # Need to fork before connecting to D-Bus
 
2509
2172
        # 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
2175
    gobject.threads_init()
 
2516
2177
    global main_loop
 
2517
2178
    # From the Avahi example code
 
2518
 
    DBusGMainLoop(set_as_default=True)
 
 
2179
    DBusGMainLoop(set_as_default=True )
 
2519
2180
    main_loop = gobject.MainLoop()
 
2520
2181
    bus = dbus.SystemBus()
 
2521
2182
    # End of Avahi example code
 
 
2567
2221
                                                     (stored_state))
 
2568
2222
            os.remove(stored_state_path)
 
2569
2223
        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:",
 
 
2224
            logger.warning("Could not load persistent state: {0}"
 
 
2226
            if e.errno != errno.ENOENT:
 
2577
2228
        except EOFError as e:
 
2578
2229
            logger.warning("Could not load persistent state: "
 
2579
 
                           "EOFError:", exc_info=e)
 
 
2230
                           "EOFError: {0}".format(e))
 
2581
2232
    with PGPEngine() as pgp:
 
2582
2233
        for client_name, client in clients_data.iteritems():
 
2583
 
            # Skip removed clients
 
2584
 
            if client_name not in client_settings:
 
2587
2234
            # Decide which value to use after restoring saved state.
 
2588
2235
            # We have three different values: Old config file,
 
2589
2236
            # new config file, and saved state.
 
 
2604
2251
            # Clients who has passed its expire date can still be
 
2605
2252
            # 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
 
 
2253
            # whose checker failed before we stored its state is
 
 
2254
            # assumed to have failed all checkers during downtime.
 
2609
2255
            if client["enabled"]:
 
2610
2256
                if datetime.datetime.utcnow() >= client["expires"]:
 
2611
2257
                    if not client["last_checked_ok"]:
 
2612
2258
                        logger.warning(
 
2613
2259
                            "disabling client {0} - Client never "
 
2614
 
                            "performed a successful checker"
 
2615
 
                            .format(client_name))
 
 
2260
                            "performed a successfull checker"
 
 
2261
                            .format(client["name"]))
 
2616
2262
                        client["enabled"] = False
 
2617
2263
                    elif client["last_checker_status"] != 0:
 
2618
2264
                        logger.warning(
 
2619
2265
                            "disabling client {0} - Client "
 
2620
2266
                            "last checker failed with error code {1}"
 
2621
 
                            .format(client_name,
 
 
2267
                            .format(client["name"],
 
2622
2268
                                    client["last_checker_status"]))
 
2623
2269
                        client["enabled"] = False
 
 
2647
2294
    for client_name in (set(client_settings)
 
2648
2295
                        - set(old_client_settings)):
 
2649
2296
        clients_data[client_name] = client_settings[client_name]
 
2651
 
    # Create all client objects
 
 
2298
    # Create clients all clients
 
2652
2299
    for client_name, client in clients_data.iteritems():
 
2653
2300
        tcp_server.clients[client_name] = client_class(
 
2654
 
            name = client_name, settings = client,
 
2655
 
            server_settings = server_settings)
 
 
2301
            name = client_name, settings = client)
 
2657
2303
    if not tcp_server.clients:
 
2658
2304
        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",
 
 
2310
                pidfile.write(str(pid) + "\n".encode("utf-8"))
 
 
2313
            logger.error("Could not write to file %r with PID %d",
 
 
2316
            # "pidfile" was never created
 
2670
2318
        del pidfilename
 
 
2319
        signal.signal(signal.SIGINT, signal.SIG_IGN)
 
2672
2321
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
 
2673
2322
    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):
 
 
2325
        class MandosDBusService(dbus.service.Object):
 
2679
2326
            """A D-Bus proxy object"""
 
2680
2327
            def __init__(self):
 
2681
2328
                dbus.service.Object.__init__(self, bus, "/")
 
2682
2329
            _interface = "se.recompile.Mandos"
 
2684
 
            @dbus_interface_annotations(_interface)
 
2686
 
                return { "org.freedesktop.DBus.Property"
 
2687
 
                         ".EmitsChangedSignal":
 
2690
2331
            @dbus.service.signal(_interface, signature="o")
 
2691
2332
            def ClientAdded(self, objpath):
 
 
2774
2416
                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:
 
 
2419
            tempfd, tempname = tempfile.mkstemp(suffix=".pickle",
 
 
2422
                                                (stored_state_path))
 
 
2423
            with os.fdopen(tempfd, "wb") as stored_state:
 
2781
2424
                pickle.dump((clients, client_settings), stored_state)
 
2782
 
                tempname=stored_state.name
 
2783
2425
            os.rename(tempname, stored_state_path)
 
2784
2426
        except (IOError, OSError) as e:
 
 
2427
            logger.warning("Could not save persistent state: {0}"
 
2787
2431
                    os.remove(tempname)
 
2788
2432
                except NameError:
 
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:",
 
 
2434
            if e.errno not in set((errno.ENOENT, errno.EACCES,
 
2798
2438
        # Delete all clients, and settings from config
 
2799
2439
        while tcp_server.clients:
 
 
2826
2466
    service.port = tcp_server.socket.getsockname()[1]
 
2828
2468
        logger.info("Now listening on address %r, port %d,"
 
2829
 
                    " flowinfo %d, scope_id %d",
 
2830
 
                    *tcp_server.socket.getsockname())
 
 
2469
                    " flowinfo %d, scope_id %d"
 
 
2470
                    % tcp_server.socket.getsockname())
 
2832
 
        logger.info("Now listening on address %r, port %d",
 
2833
 
                    *tcp_server.socket.getsockname())
 
 
2472
        logger.info("Now listening on address %r, port %d"
 
 
2473
                    % tcp_server.socket.getsockname())
 
2835
2475
    #service.interface = tcp_server.socket.getsockname()[3]