/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos

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

Show diffs side-by-side

added added

removed removed

Lines of Context:
44
44
import argparse
45
45
import datetime
46
46
import errno
47
 
import gnutls.crypto
48
47
import gnutls.connection
49
48
import gnutls.errors
50
49
import gnutls.library.functions
104
103
if sys.version_info.major == 2:
105
104
    str = unicode
106
105
 
107
 
version = "1.6.9"
 
106
version = "1.7.1"
108
107
stored_state_file = "clients.pickle"
109
108
 
110
109
logger = logging.getLogger()
395
394
                    logger.error(bad_states[state] + ": %r", error)
396
395
            self.cleanup()
397
396
        elif state == avahi.SERVER_RUNNING:
398
 
            self.add()
 
397
            try:
 
398
                self.add()
 
399
            except dbus.exceptions.DBusException as error:
 
400
                if (error.get_dbus_name()
 
401
                    == "org.freedesktop.Avahi.CollisionError"):
 
402
                    logger.info("Local Zeroconf service name"
 
403
                                " collision.")
 
404
                    return self.rename(remove=False)
 
405
                else:
 
406
                    logger.critical("D-Bus Exception", exc_info=error)
 
407
                    self.cleanup()
 
408
                    os._exit(1)
399
409
        else:
400
410
            if error is None:
401
411
                logger.debug("Unknown state: %r", state)
424
434
            .format(self.name)))
425
435
        return ret
426
436
 
 
437
def call_pipe(connection,       # : multiprocessing.Connection
 
438
              func, *args, **kwargs):
 
439
    """This function is meant to be called by multiprocessing.Process
 
440
    
 
441
    This function runs func(*args, **kwargs), and writes the resulting
 
442
    return value on the provided multiprocessing.Connection.
 
443
    """
 
444
    connection.send(func(*args, **kwargs))
 
445
    connection.close()
427
446
 
428
447
class Client(object):
429
448
    """A representation of a client host served by this server.
456
475
    last_checker_status: integer between 0 and 255 reflecting exit
457
476
                         status of last checker. -1 reflects crashed
458
477
                         checker, -2 means no checker completed yet.
 
478
    last_checker_signal: The signal which killed the last checker, if
 
479
                         last_checker_status is -1
459
480
    last_enabled: datetime.datetime(); (UTC) or None
460
481
    name:       string; from the config file, used in log messages and
461
482
                        D-Bus identifiers
635
656
        # Also start a new checker *right now*.
636
657
        self.start_checker()
637
658
    
638
 
    def checker_callback(self, pid, condition, command):
 
659
    def checker_callback(self, source, condition, connection,
 
660
                         command):
639
661
        """The checker has completed, so take appropriate actions."""
640
662
        self.checker_callback_tag = None
641
663
        self.checker = None
642
 
        if os.WIFEXITED(condition):
643
 
            self.last_checker_status = os.WEXITSTATUS(condition)
 
664
        # Read return code from connection (see call_pipe)
 
665
        returncode = connection.recv()
 
666
        connection.close()
 
667
        
 
668
        if returncode >= 0:
 
669
            self.last_checker_status = returncode
 
670
            self.last_checker_signal = None
644
671
            if self.last_checker_status == 0:
645
672
                logger.info("Checker for %(name)s succeeded",
646
673
                            vars(self))
649
676
                logger.info("Checker for %(name)s failed", vars(self))
650
677
        else:
651
678
            self.last_checker_status = -1
 
679
            self.last_checker_signal = -returncode
652
680
            logger.warning("Checker for %(name)s crashed?",
653
681
                           vars(self))
 
682
        return False
654
683
    
655
684
    def checked_ok(self):
656
685
        """Assert that the client has been seen, alive and well."""
657
686
        self.last_checked_ok = datetime.datetime.utcnow()
658
687
        self.last_checker_status = 0
 
688
        self.last_checker_signal = None
659
689
        self.bump_timeout()
660
690
    
661
691
    def bump_timeout(self, timeout=None):
687
717
        # than 'timeout' for the client to be disabled, which is as it
688
718
        # should be.
689
719
        
690
 
        # If a checker exists, make sure it is not a zombie
691
 
        try:
692
 
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
693
 
        except AttributeError:
694
 
            pass
695
 
        except OSError as error:
696
 
            if error.errno != errno.ECHILD:
697
 
                raise
698
 
        else:
699
 
            if pid:
700
 
                logger.warning("Checker was a zombie")
701
 
                gobject.source_remove(self.checker_callback_tag)
702
 
                self.checker_callback(pid, status,
703
 
                                      self.current_checker_command)
 
720
        if self.checker is not None and not self.checker.is_alive():
 
721
            logger.warning("Checker was not alive; joining")
 
722
            self.checker.join()
 
723
            self.checker = None
704
724
        # Start a new checker if needed
705
725
        if self.checker is None:
706
726
            # Escape attributes for the shell
715
735
                             exc_info=error)
716
736
                return True     # Try again later
717
737
            self.current_checker_command = command
718
 
            try:
719
 
                logger.info("Starting checker %r for %s", command,
720
 
                            self.name)
721
 
                # We don't need to redirect stdout and stderr, since
722
 
                # in normal mode, that is already done by daemon(),
723
 
                # and in debug mode we don't want to.  (Stdin is
724
 
                # always replaced by /dev/null.)
725
 
                # The exception is when not debugging but nevertheless
726
 
                # running in the foreground; use the previously
727
 
                # created wnull.
728
 
                popen_args = {}
729
 
                if (not self.server_settings["debug"]
730
 
                    and self.server_settings["foreground"]):
731
 
                    popen_args.update({"stdout": wnull,
732
 
                                       "stderr": wnull })
733
 
                self.checker = subprocess.Popen(command,
734
 
                                                close_fds=True,
735
 
                                                shell=True,
736
 
                                                cwd="/",
737
 
                                                **popen_args)
738
 
            except OSError as error:
739
 
                logger.error("Failed to start subprocess",
740
 
                             exc_info=error)
741
 
                return True
742
 
            self.checker_callback_tag = gobject.child_watch_add(
743
 
                self.checker.pid, self.checker_callback, data=command)
744
 
            # The checker may have completed before the gobject
745
 
            # watch was added.  Check for this.
746
 
            try:
747
 
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
748
 
            except OSError as error:
749
 
                if error.errno == errno.ECHILD:
750
 
                    # This should never happen
751
 
                    logger.error("Child process vanished",
752
 
                                 exc_info=error)
753
 
                    return True
754
 
                raise
755
 
            if pid:
756
 
                gobject.source_remove(self.checker_callback_tag)
757
 
                self.checker_callback(pid, status, command)
 
738
            logger.info("Starting checker %r for %s", command,
 
739
                        self.name)
 
740
            # We don't need to redirect stdout and stderr, since
 
741
            # in normal mode, that is already done by daemon(),
 
742
            # and in debug mode we don't want to.  (Stdin is
 
743
            # always replaced by /dev/null.)
 
744
            # The exception is when not debugging but nevertheless
 
745
            # running in the foreground; use the previously
 
746
            # created wnull.
 
747
            popen_args = { "close_fds": True,
 
748
                           "shell": True,
 
749
                           "cwd": "/" }
 
750
            if (not self.server_settings["debug"]
 
751
                and self.server_settings["foreground"]):
 
752
                popen_args.update({"stdout": wnull,
 
753
                                   "stderr": wnull })
 
754
            pipe = multiprocessing.Pipe(duplex = False)
 
755
            self.checker = multiprocessing.Process(
 
756
                target = call_pipe,
 
757
                args = (pipe[1], subprocess.call, command),
 
758
                kwargs = popen_args)
 
759
            self.checker.start()
 
760
            self.checker_callback_tag = gobject.io_add_watch(
 
761
                pipe[0].fileno(), gobject.IO_IN,
 
762
                self.checker_callback, pipe[0], command)
758
763
        # Re-run this periodically if run by gobject.timeout_add
759
764
        return True
760
765
    
766
771
        if getattr(self, "checker", None) is None:
767
772
            return
768
773
        logger.debug("Stopping checker for %(name)s", vars(self))
769
 
        try:
770
 
            self.checker.terminate()
771
 
            #time.sleep(0.5)
772
 
            #if self.checker.poll() is None:
773
 
            #    self.checker.kill()
774
 
        except OSError as error:
775
 
            if error.errno != errno.ESRCH: # No such process
776
 
                raise
 
774
        self.checker.terminate()
777
775
        self.checker = None
778
776
 
779
777
 
843
841
                           access="r")
844
842
    def Property_dbus_property(self):
845
843
        return dbus.Boolean(False)
 
844
    
 
845
    See also the DBusObjectWithAnnotations class.
846
846
    """
847
847
    
848
848
    def decorator(func):
870
870
    pass
871
871
 
872
872
 
873
 
class DBusObjectWithProperties(dbus.service.Object):
874
 
    """A D-Bus object with properties.
 
873
class DBusObjectWithAnnotations(dbus.service.Object):
 
874
    """A D-Bus object with annotations.
875
875
    
876
 
    Classes inheriting from this can use the dbus_service_property
877
 
    decorator to expose methods as D-Bus properties.  It exposes the
878
 
    standard Get(), Set(), and GetAll() methods on the D-Bus.
 
876
    Classes inheriting from this can use the dbus_annotations
 
877
    decorator to add annotations to methods or signals.
879
878
    """
880
879
    
881
880
    @staticmethod
897
896
                for name, athing in
898
897
                inspect.getmembers(cls, self._is_dbus_thing(thing)))
899
898
    
 
899
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
 
900
                         out_signature = "s",
 
901
                         path_keyword = 'object_path',
 
902
                         connection_keyword = 'connection')
 
903
    def Introspect(self, object_path, connection):
 
904
        """Overloading of standard D-Bus method.
 
905
        
 
906
        Inserts annotation tags on methods and signals.
 
907
        """
 
908
        xmlstring = dbus.service.Object.Introspect(self, object_path,
 
909
                                                   connection)
 
910
        try:
 
911
            document = xml.dom.minidom.parseString(xmlstring)
 
912
            
 
913
            for if_tag in document.getElementsByTagName("interface"):
 
914
                # Add annotation tags
 
915
                for typ in ("method", "signal"):
 
916
                    for tag in if_tag.getElementsByTagName(typ):
 
917
                        annots = dict()
 
918
                        for name, prop in (self.
 
919
                                           _get_all_dbus_things(typ)):
 
920
                            if (name == tag.getAttribute("name")
 
921
                                and prop._dbus_interface
 
922
                                == if_tag.getAttribute("name")):
 
923
                                annots.update(getattr(
 
924
                                    prop, "_dbus_annotations", {}))
 
925
                        for name, value in annots.items():
 
926
                            ann_tag = document.createElement(
 
927
                                "annotation")
 
928
                            ann_tag.setAttribute("name", name)
 
929
                            ann_tag.setAttribute("value", value)
 
930
                            tag.appendChild(ann_tag)
 
931
                # Add interface annotation tags
 
932
                for annotation, value in dict(
 
933
                    itertools.chain.from_iterable(
 
934
                        annotations().items()
 
935
                        for name, annotations
 
936
                        in self._get_all_dbus_things("interface")
 
937
                        if name == if_tag.getAttribute("name")
 
938
                        )).items():
 
939
                    ann_tag = document.createElement("annotation")
 
940
                    ann_tag.setAttribute("name", annotation)
 
941
                    ann_tag.setAttribute("value", value)
 
942
                    if_tag.appendChild(ann_tag)
 
943
                # Fix argument name for the Introspect method itself
 
944
                if (if_tag.getAttribute("name")
 
945
                                == dbus.INTROSPECTABLE_IFACE):
 
946
                    for cn in if_tag.getElementsByTagName("method"):
 
947
                        if cn.getAttribute("name") == "Introspect":
 
948
                            for arg in cn.getElementsByTagName("arg"):
 
949
                                if (arg.getAttribute("direction")
 
950
                                    == "out"):
 
951
                                    arg.setAttribute("name",
 
952
                                                     "xml_data")
 
953
            xmlstring = document.toxml("utf-8")
 
954
            document.unlink()
 
955
        except (AttributeError, xml.dom.DOMException,
 
956
                xml.parsers.expat.ExpatError) as error:
 
957
            logger.error("Failed to override Introspection method",
 
958
                         exc_info=error)
 
959
        return xmlstring
 
960
 
 
961
 
 
962
class DBusObjectWithProperties(DBusObjectWithAnnotations):
 
963
    """A D-Bus object with properties.
 
964
    
 
965
    Classes inheriting from this can use the dbus_service_property
 
966
    decorator to expose methods as D-Bus properties.  It exposes the
 
967
    standard Get(), Set(), and GetAll() methods on the D-Bus.
 
968
    """
 
969
    
900
970
    def _get_dbus_property(self, interface_name, property_name):
901
971
        """Returns a bound method if one exists which is a D-Bus
902
972
        property with the specified name and interface.
912
982
        raise DBusPropertyNotFound("{}:{}.{}".format(
913
983
            self.dbus_object_path, interface_name, property_name))
914
984
    
 
985
    @classmethod
 
986
    def _get_all_interface_names(cls):
 
987
        """Get a sequence of all interfaces supported by an object"""
 
988
        return (name for name in set(getattr(getattr(x, attr),
 
989
                                             "_dbus_interface", None)
 
990
                                     for x in (inspect.getmro(cls))
 
991
                                     for attr in dir(x))
 
992
                if name is not None)
 
993
    
915
994
    @dbus.service.method(dbus.PROPERTIES_IFACE,
916
995
                         in_signature="ss",
917
996
                         out_signature="v")
987
1066
        
988
1067
        Inserts property tags and interface annotation tags.
989
1068
        """
990
 
        xmlstring = dbus.service.Object.Introspect(self, object_path,
991
 
                                                   connection)
 
1069
        xmlstring = DBusObjectWithAnnotations.Introspect(self,
 
1070
                                                         object_path,
 
1071
                                                         connection)
992
1072
        try:
993
1073
            document = xml.dom.minidom.parseString(xmlstring)
994
1074
            
1007
1087
                            if prop._dbus_interface
1008
1088
                            == if_tag.getAttribute("name")):
1009
1089
                    if_tag.appendChild(tag)
1010
 
                # Add annotation tags
1011
 
                for typ in ("method", "signal", "property"):
1012
 
                    for tag in if_tag.getElementsByTagName(typ):
1013
 
                        annots = dict()
1014
 
                        for name, prop in (self.
1015
 
                                           _get_all_dbus_things(typ)):
1016
 
                            if (name == tag.getAttribute("name")
1017
 
                                and prop._dbus_interface
1018
 
                                == if_tag.getAttribute("name")):
1019
 
                                annots.update(getattr(
1020
 
                                    prop, "_dbus_annotations", {}))
1021
 
                        for name, value in annots.items():
1022
 
                            ann_tag = document.createElement(
1023
 
                                "annotation")
1024
 
                            ann_tag.setAttribute("name", name)
1025
 
                            ann_tag.setAttribute("value", value)
1026
 
                            tag.appendChild(ann_tag)
1027
 
                # Add interface annotation tags
1028
 
                for annotation, value in dict(
1029
 
                    itertools.chain.from_iterable(
1030
 
                        annotations().items()
1031
 
                        for name, annotations
1032
 
                        in self._get_all_dbus_things("interface")
1033
 
                        if name == if_tag.getAttribute("name")
1034
 
                        )).items():
1035
 
                    ann_tag = document.createElement("annotation")
1036
 
                    ann_tag.setAttribute("name", annotation)
1037
 
                    ann_tag.setAttribute("value", value)
1038
 
                    if_tag.appendChild(ann_tag)
 
1090
                # Add annotation tags for properties
 
1091
                for tag in if_tag.getElementsByTagName("property"):
 
1092
                    annots = dict()
 
1093
                    for name, prop in self._get_all_dbus_things(
 
1094
                            "property"):
 
1095
                        if (name == tag.getAttribute("name")
 
1096
                            and prop._dbus_interface
 
1097
                            == if_tag.getAttribute("name")):
 
1098
                            annots.update(getattr(
 
1099
                                prop, "_dbus_annotations", {}))
 
1100
                    for name, value in annots.items():
 
1101
                        ann_tag = document.createElement(
 
1102
                            "annotation")
 
1103
                        ann_tag.setAttribute("name", name)
 
1104
                        ann_tag.setAttribute("value", value)
 
1105
                        tag.appendChild(ann_tag)
1039
1106
                # Add the names to the return values for the
1040
1107
                # "org.freedesktop.DBus.Properties" methods
1041
1108
                if (if_tag.getAttribute("name")
1059
1126
                         exc_info=error)
1060
1127
        return xmlstring
1061
1128
 
 
1129
try:
 
1130
    dbus.OBJECT_MANAGER_IFACE
 
1131
except AttributeError:
 
1132
    dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
 
1133
 
 
1134
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
 
1135
    """A D-Bus object with an ObjectManager.
 
1136
    
 
1137
    Classes inheriting from this exposes the standard
 
1138
    GetManagedObjects call and the InterfacesAdded and
 
1139
    InterfacesRemoved signals on the standard
 
1140
    "org.freedesktop.DBus.ObjectManager" interface.
 
1141
    
 
1142
    Note: No signals are sent automatically; they must be sent
 
1143
    manually.
 
1144
    """
 
1145
    @dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
 
1146
                         out_signature = "a{oa{sa{sv}}}")
 
1147
    def GetManagedObjects(self):
 
1148
        """This function must be overridden"""
 
1149
        raise NotImplementedError()
 
1150
    
 
1151
    @dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
 
1152
                         signature = "oa{sa{sv}}")
 
1153
    def InterfacesAdded(self, object_path, interfaces_and_properties):
 
1154
        pass
 
1155
    
 
1156
    @dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
 
1157
    def InterfacesRemoved(self, object_path, interfaces):
 
1158
        pass
 
1159
    
 
1160
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
 
1161
                         out_signature = "s",
 
1162
                         path_keyword = 'object_path',
 
1163
                         connection_keyword = 'connection')
 
1164
    def Introspect(self, object_path, connection):
 
1165
        """Overloading of standard D-Bus method.
 
1166
        
 
1167
        Override return argument name of GetManagedObjects to be
 
1168
        "objpath_interfaces_and_properties"
 
1169
        """
 
1170
        xmlstring = DBusObjectWithAnnotations.Introspect(self,
 
1171
                                                         object_path,
 
1172
                                                         connection)
 
1173
        try:
 
1174
            document = xml.dom.minidom.parseString(xmlstring)
 
1175
            
 
1176
            for if_tag in document.getElementsByTagName("interface"):
 
1177
                # Fix argument name for the GetManagedObjects method
 
1178
                if (if_tag.getAttribute("name")
 
1179
                                == dbus.OBJECT_MANAGER_IFACE):
 
1180
                    for cn in if_tag.getElementsByTagName("method"):
 
1181
                        if (cn.getAttribute("name")
 
1182
                            == "GetManagedObjects"):
 
1183
                            for arg in cn.getElementsByTagName("arg"):
 
1184
                                if (arg.getAttribute("direction")
 
1185
                                    == "out"):
 
1186
                                    arg.setAttribute(
 
1187
                                        "name",
 
1188
                                        "objpath_interfaces"
 
1189
                                        "_and_properties")
 
1190
            xmlstring = document.toxml("utf-8")
 
1191
            document.unlink()
 
1192
        except (AttributeError, xml.dom.DOMException,
 
1193
                xml.parsers.expat.ExpatError) as error:
 
1194
            logger.error("Failed to override Introspection method",
 
1195
                         exc_info = error)
 
1196
        return xmlstring
1062
1197
 
1063
1198
def datetime_to_dbus(dt, variant_level=0):
1064
1199
    """Convert a UTC datetime.datetime() to a D-Bus type."""
1111
1246
                interface_names.add(alt_interface)
1112
1247
                # Is this a D-Bus signal?
1113
1248
                if getattr(attribute, "_dbus_is_signal", False):
1114
 
                    # Extract the original non-method undecorated
1115
 
                    # function by black magic
1116
 
                    nonmethod_func = (dict(
1117
 
                        zip(attribute.func_code.co_freevars,
1118
 
                            attribute.__closure__))
1119
 
                                      ["func"].cell_contents)
 
1249
                    if sys.version_info.major == 2:
 
1250
                        # Extract the original non-method undecorated
 
1251
                        # function by black magic
 
1252
                        nonmethod_func = (dict(
 
1253
                            zip(attribute.func_code.co_freevars,
 
1254
                                attribute.__closure__))
 
1255
                                          ["func"].cell_contents)
 
1256
                    else:
 
1257
                        nonmethod_func = attribute
1120
1258
                    # Create a new, but exactly alike, function
1121
1259
                    # object, and decorate it to be a new D-Bus signal
1122
1260
                    # with the alternate D-Bus interface name
 
1261
                    if sys.version_info.major == 2:
 
1262
                        new_function = types.FunctionType(
 
1263
                            nonmethod_func.func_code,
 
1264
                            nonmethod_func.func_globals,
 
1265
                            nonmethod_func.func_name,
 
1266
                            nonmethod_func.func_defaults,
 
1267
                            nonmethod_func.func_closure)
 
1268
                    else:
 
1269
                        new_function = types.FunctionType(
 
1270
                            nonmethod_func.__code__,
 
1271
                            nonmethod_func.__globals__,
 
1272
                            nonmethod_func.__name__,
 
1273
                            nonmethod_func.__defaults__,
 
1274
                            nonmethod_func.__closure__)
1123
1275
                    new_function = (dbus.service.signal(
1124
 
                        alt_interface, attribute._dbus_signature)
1125
 
                                    (types.FunctionType(
1126
 
                                        nonmethod_func.func_code,
1127
 
                                        nonmethod_func.func_globals,
1128
 
                                        nonmethod_func.func_name,
1129
 
                                        nonmethod_func.func_defaults,
1130
 
                                        nonmethod_func.func_closure)))
 
1276
                        alt_interface,
 
1277
                        attribute._dbus_signature)(new_function))
1131
1278
                    # Copy annotations, if any
1132
1279
                    try:
1133
1280
                        new_function._dbus_annotations = dict(
1143
1290
                        func1 and func2 to the "call_both" function
1144
1291
                        outside of its arguments"""
1145
1292
                        
 
1293
                        @functools.wraps(func2)
1146
1294
                        def call_both(*args, **kwargs):
1147
1295
                            """This function will emit two D-Bus
1148
1296
                            signals by calling func1 and func2"""
1149
1297
                            func1(*args, **kwargs)
1150
1298
                            func2(*args, **kwargs)
 
1299
                        # Make wrapper function look like a D-Bus signal
 
1300
                        for name, attr in inspect.getmembers(func2):
 
1301
                            if name.startswith("_dbus_"):
 
1302
                                setattr(call_both, name, attr)
1151
1303
                        
1152
1304
                        return call_both
1153
1305
                    # Create the "call_both" function and add it to
1356
1508
            DBusObjectWithProperties.__del__(self, *args, **kwargs)
1357
1509
        Client.__del__(self, *args, **kwargs)
1358
1510
    
1359
 
    def checker_callback(self, pid, condition, command,
1360
 
                         *args, **kwargs):
1361
 
        self.checker_callback_tag = None
1362
 
        self.checker = None
1363
 
        if os.WIFEXITED(condition):
1364
 
            exitstatus = os.WEXITSTATUS(condition)
 
1511
    def checker_callback(self, source, condition,
 
1512
                         connection, command, *args, **kwargs):
 
1513
        ret = Client.checker_callback(self, source, condition,
 
1514
                                      connection, command, *args,
 
1515
                                      **kwargs)
 
1516
        exitstatus = self.last_checker_status
 
1517
        if exitstatus >= 0:
1365
1518
            # Emit D-Bus signal
1366
1519
            self.CheckerCompleted(dbus.Int16(exitstatus),
1367
 
                                  dbus.Int64(condition),
 
1520
                                  # This is specific to GNU libC
 
1521
                                  dbus.Int64(exitstatus << 8),
1368
1522
                                  dbus.String(command))
1369
1523
        else:
1370
1524
            # Emit D-Bus signal
1371
1525
            self.CheckerCompleted(dbus.Int16(-1),
1372
 
                                  dbus.Int64(condition),
 
1526
                                  dbus.Int64(
 
1527
                                      # This is specific to GNU libC
 
1528
                                      (exitstatus << 8)
 
1529
                                      | self.last_checker_signal),
1373
1530
                                  dbus.String(command))
1374
 
        
1375
 
        return Client.checker_callback(self, pid, condition, command,
1376
 
                                       *args, **kwargs)
 
1531
        return ret
1377
1532
    
1378
1533
    def start_checker(self, *args, **kwargs):
1379
1534
        old_checker_pid = getattr(self.checker, "pid", None)
1454
1609
        self.checked_ok()
1455
1610
    
1456
1611
    # Enable - method
 
1612
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1457
1613
    @dbus.service.method(_interface)
1458
1614
    def Enable(self):
1459
1615
        "D-Bus method"
1460
1616
        self.enable()
1461
1617
    
1462
1618
    # StartChecker - method
 
1619
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1463
1620
    @dbus.service.method(_interface)
1464
1621
    def StartChecker(self):
1465
1622
        "D-Bus method"
1466
1623
        self.start_checker()
1467
1624
    
1468
1625
    # Disable - method
 
1626
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1469
1627
    @dbus.service.method(_interface)
1470
1628
    def Disable(self):
1471
1629
        "D-Bus method"
1472
1630
        self.disable()
1473
1631
    
1474
1632
    # StopChecker - method
 
1633
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1475
1634
    @dbus.service.method(_interface)
1476
1635
    def StopChecker(self):
1477
1636
        self.stop_checker()
1513
1672
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
1514
1673
    
1515
1674
    # Name - property
 
1675
    @dbus_annotations(
 
1676
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1516
1677
    @dbus_service_property(_interface, signature="s", access="read")
1517
1678
    def Name_dbus_property(self):
1518
1679
        return dbus.String(self.name)
1519
1680
    
1520
1681
    # Fingerprint - property
 
1682
    @dbus_annotations(
 
1683
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1521
1684
    @dbus_service_property(_interface, signature="s", access="read")
1522
1685
    def Fingerprint_dbus_property(self):
1523
1686
        return dbus.String(self.fingerprint)
1532
1695
        self.host = str(value)
1533
1696
    
1534
1697
    # Created - property
 
1698
    @dbus_annotations(
 
1699
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1535
1700
    @dbus_service_property(_interface, signature="s", access="read")
1536
1701
    def Created_dbus_property(self):
1537
1702
        return datetime_to_dbus(self.created)
1652
1817
            self.stop_checker()
1653
1818
    
1654
1819
    # ObjectPath - property
 
1820
    @dbus_annotations(
 
1821
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const",
 
1822
         "org.freedesktop.DBus.Deprecated": "true"})
1655
1823
    @dbus_service_property(_interface, signature="o", access="read")
1656
1824
    def ObjectPath_dbus_property(self):
1657
1825
        return self.dbus_object_path # is already a dbus.ObjectPath
1658
1826
    
1659
1827
    # Secret = property
 
1828
    @dbus_annotations(
 
1829
        {"org.freedesktop.DBus.Property.EmitsChangedSignal":
 
1830
         "invalidates"})
1660
1831
    @dbus_service_property(_interface,
1661
1832
                           signature="ay",
1662
1833
                           access="write",
1672
1843
        self._pipe = child_pipe
1673
1844
        self._pipe.send(('init', fpr, address))
1674
1845
        if not self._pipe.recv():
1675
 
            raise KeyError()
 
1846
            raise KeyError(fpr)
1676
1847
    
1677
1848
    def __getattribute__(self, name):
1678
1849
        if name == '_pipe':
1709
1880
                         self.server.child_pipe.fileno())
1710
1881
            
1711
1882
            session = gnutls.connection.ClientSession(
1712
 
                self.request, gnutls.connection .X509Credentials())
 
1883
                self.request, gnutls.connection.X509Credentials())
1713
1884
            
1714
1885
            # Note: gnutls.connection.X509Credentials is really a
1715
1886
            # generic GnuTLS certificate credentials object so long as
2141
2312
        
2142
2313
        if command == 'getattr':
2143
2314
            attrname = request[1]
2144
 
            if callable(client_object.__getattribute__(attrname)):
 
2315
            if isinstance(client_object.__getattribute__(attrname),
 
2316
                          collections.Callable):
2145
2317
                parent_pipe.send(('function', ))
2146
2318
            else:
2147
2319
                parent_pipe.send((
2182
2354
    # avoid excessive use of external libraries.
2183
2355
    
2184
2356
    # New type for defining tokens, syntax, and semantics all-in-one
2185
 
    Token = collections.namedtuple("Token",
2186
 
                                   ("regexp", # To match token; if
2187
 
                                              # "value" is not None,
2188
 
                                              # must have a "group"
2189
 
                                              # containing digits
2190
 
                                    "value",  # datetime.timedelta or
2191
 
                                              # None
2192
 
                                    "followers")) # Tokens valid after
2193
 
                                                  # this token
2194
2357
    Token = collections.namedtuple("Token", (
2195
2358
        "regexp",  # To match token; if "value" is not None, must have
2196
2359
                   # a "group" containing digits
2231
2394
    # Define starting values
2232
2395
    value = datetime.timedelta() # Value so far
2233
2396
    found_token = None
2234
 
    followers = frozenset((token_duration,)) # Following valid tokens
 
2397
    followers = frozenset((token_duration, )) # Following valid tokens
2235
2398
    s = duration                # String left to parse
2236
2399
    # Loop until end token is found
2237
2400
    while found_token is not token_end:
2254
2417
                break
2255
2418
        else:
2256
2419
            # No currently valid tokens were found
2257
 
            raise ValueError("Invalid RFC 3339 duration")
 
2420
            raise ValueError("Invalid RFC 3339 duration: {!r}"
 
2421
                             .format(duration))
2258
2422
    # End token found
2259
2423
    return value
2260
2424
 
2393
2557
                        "debug": "False",
2394
2558
                        "priority":
2395
2559
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2396
 
                        ":+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
 
2560
                        ":+SIGN-DSA-SHA256",
2397
2561
                        "servicename": "Mandos",
2398
2562
                        "use_dbus": "True",
2399
2563
                        "use_ipv6": "True",
2655
2819
                    pass
2656
2820
            
2657
2821
            # Clients who has passed its expire date can still be
2658
 
            # enabled if its last checker was successful.  Clients
 
2822
            # enabled if its last checker was successful.  A Client
2659
2823
            # whose checker succeeded before we stored its state is
2660
2824
            # assumed to have successfully run all checkers during
2661
2825
            # downtime.
2730
2894
        
2731
2895
        @alternate_dbus_interfaces(
2732
2896
            { "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
2733
 
        class MandosDBusService(DBusObjectWithProperties):
 
2897
        class MandosDBusService(DBusObjectWithObjectManager):
2734
2898
            """A D-Bus proxy object"""
2735
2899
            
2736
2900
            def __init__(self):
2738
2902
            
2739
2903
            _interface = "se.recompile.Mandos"
2740
2904
            
2741
 
            @dbus_interface_annotations(_interface)
2742
 
            def _foo(self):
2743
 
                return {
2744
 
                    "org.freedesktop.DBus.Property.EmitsChangedSignal":
2745
 
                    "false" }
2746
 
            
2747
2905
            @dbus.service.signal(_interface, signature="o")
2748
2906
            def ClientAdded(self, objpath):
2749
2907
                "D-Bus signal"
2754
2912
                "D-Bus signal"
2755
2913
                pass
2756
2914
            
 
2915
            @dbus_annotations({"org.freedesktop.DBus.Deprecated":
 
2916
                               "true"})
2757
2917
            @dbus.service.signal(_interface, signature="os")
2758
2918
            def ClientRemoved(self, objpath, name):
2759
2919
                "D-Bus signal"
2760
2920
                pass
2761
2921
            
 
2922
            @dbus_annotations({"org.freedesktop.DBus.Deprecated":
 
2923
                               "true"})
2762
2924
            @dbus.service.method(_interface, out_signature="ao")
2763
2925
            def GetAllClients(self):
2764
2926
                "D-Bus method"
2765
2927
                return dbus.Array(c.dbus_object_path for c in
2766
2928
                                  tcp_server.clients.itervalues())
2767
2929
            
 
2930
            @dbus_annotations({"org.freedesktop.DBus.Deprecated":
 
2931
                               "true"})
2768
2932
            @dbus.service.method(_interface,
2769
2933
                                 out_signature="a{oa{sv}}")
2770
2934
            def GetAllClientsWithProperties(self):
2771
2935
                "D-Bus method"
2772
2936
                return dbus.Dictionary(
2773
 
                    { c.dbus_object_path: c.GetAll("")
 
2937
                    { c.dbus_object_path: c.GetAll(
 
2938
                        "se.recompile.Mandos.Client")
2774
2939
                      for c in tcp_server.clients.itervalues() },
2775
2940
                    signature="oa{sv}")
2776
2941
            
2781
2946
                    if c.dbus_object_path == object_path:
2782
2947
                        del tcp_server.clients[c.name]
2783
2948
                        c.remove_from_connection()
2784
 
                        # Don't signal anything except ClientRemoved
 
2949
                        # Don't signal the disabling
2785
2950
                        c.disable(quiet=True)
2786
 
                        # Emit D-Bus signal
2787
 
                        self.ClientRemoved(object_path, c.name)
 
2951
                        # Emit D-Bus signal for removal
 
2952
                        self.client_removed_signal(c)
2788
2953
                        return
2789
2954
                raise KeyError(object_path)
2790
2955
            
2791
2956
            del _interface
 
2957
            
 
2958
            @dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
 
2959
                                 out_signature = "a{oa{sa{sv}}}")
 
2960
            def GetManagedObjects(self):
 
2961
                """D-Bus method"""
 
2962
                return dbus.Dictionary(
 
2963
                    { client.dbus_object_path:
 
2964
                      dbus.Dictionary(
 
2965
                          { interface: client.GetAll(interface)
 
2966
                            for interface in
 
2967
                                 client._get_all_interface_names()})
 
2968
                      for client in tcp_server.clients.values()})
 
2969
            
 
2970
            def client_added_signal(self, client):
 
2971
                """Send the new standard signal and the old signal"""
 
2972
                if use_dbus:
 
2973
                    # New standard signal
 
2974
                    self.InterfacesAdded(
 
2975
                        client.dbus_object_path,
 
2976
                        dbus.Dictionary(
 
2977
                            { interface: client.GetAll(interface)
 
2978
                              for interface in
 
2979
                              client._get_all_interface_names()}))
 
2980
                    # Old signal
 
2981
                    self.ClientAdded(client.dbus_object_path)
 
2982
            
 
2983
            def client_removed_signal(self, client):
 
2984
                """Send the new standard signal and the old signal"""
 
2985
                if use_dbus:
 
2986
                    # New standard signal
 
2987
                    self.InterfacesRemoved(
 
2988
                        client.dbus_object_path,
 
2989
                        client._get_all_interface_names())
 
2990
                    # Old signal
 
2991
                    self.ClientRemoved(client.dbus_object_path,
 
2992
                                       client.name)
2792
2993
        
2793
2994
        mandos_dbus_service = MandosDBusService()
2794
2995
    
2859
3060
            name, client = tcp_server.clients.popitem()
2860
3061
            if use_dbus:
2861
3062
                client.remove_from_connection()
2862
 
            # Don't signal anything except ClientRemoved
 
3063
            # Don't signal the disabling
2863
3064
            client.disable(quiet=True)
 
3065
            # Emit D-Bus signal for removal
2864
3066
            if use_dbus:
2865
 
                # Emit D-Bus signal
2866
 
                mandos_dbus_service.ClientRemoved(
2867
 
                    client.dbus_object_path, client.name)
 
3067
                mandos_dbus_service.client_removed_signal(client)
2868
3068
        client_settings.clear()
2869
3069
    
2870
3070
    atexit.register(cleanup)
2871
3071
    
2872
3072
    for client in tcp_server.clients.itervalues():
2873
3073
        if use_dbus:
2874
 
            # Emit D-Bus signal
2875
 
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
 
3074
            # Emit D-Bus signal for adding
 
3075
            mandos_dbus_service.client_added_signal(client)
2876
3076
        # Need to initiate checking of clients
2877
3077
        if client.enabled:
2878
3078
            client.init_checker()