/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: 2011-12-31 23:00:36 UTC
  • Revision ID: teddy@recompile.se-20111231230036-dokyri4qljllv7ro
* mandos: Break some long lines.
  (ClientDBus.Timeout_dbus_property): If changed, only reschedule
                                      timeout if enabled.

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
# "AvahiService" class, and some lines in "main".
12
12
13
13
# Everything else is
14
 
# Copyright © 2008-2012 Teddy Hogeborn
15
 
# Copyright © 2008-2012 Björn Påhlsson
 
14
# Copyright © 2008-2011 Teddy Hogeborn
 
15
# Copyright © 2008-2011 Björn Påhlsson
16
16
17
17
# This program is free software: you can redistribute it and/or modify
18
18
# it under the terms of the GNU General Public License as published by
65
65
import types
66
66
import binascii
67
67
import tempfile
68
 
import itertools
69
68
 
70
69
import dbus
71
70
import dbus.service
86
85
    except ImportError:
87
86
        SO_BINDTODEVICE = None
88
87
 
89
 
version = "1.5.3"
 
88
version = "1.4.1"
90
89
stored_state_file = "clients.pickle"
91
90
 
92
91
logger = logging.getLogger()
176
175
    
177
176
    def encrypt(self, data, password):
178
177
        self.gnupg.passphrase = self.password_encode(password)
179
 
        with open(os.devnull, "w") as devnull:
 
178
        with open(os.devnull) as devnull:
180
179
            try:
181
180
                proc = self.gnupg.run(['--symmetric'],
182
181
                                      create_fhs=['stdin', 'stdout'],
193
192
    
194
193
    def decrypt(self, data, password):
195
194
        self.gnupg.passphrase = self.password_encode(password)
196
 
        with open(os.devnull, "w") as devnull:
 
195
        with open(os.devnull) as devnull:
197
196
            try:
198
197
                proc = self.gnupg.run(['--decrypt'],
199
198
                                      create_fhs=['stdin', 'stdout'],
200
199
                                      attach_fhs={'stderr': devnull})
201
 
                with contextlib.closing(proc.handles['stdin']) as f:
 
200
                with contextlib.closing(proc.handles['stdin'] ) as f:
202
201
                    f.write(data)
203
202
                with contextlib.closing(proc.handles['stdout']) as f:
204
203
                    decrypted_plaintext = f.read()
416
415
    last_checked_ok: datetime.datetime(); (UTC) or None
417
416
    last_checker_status: integer between 0 and 255 reflecting exit
418
417
                         status of last checker. -1 reflects crashed
419
 
                         checker, -2 means no checker completed yet.
 
418
                         checker, or None.
420
419
    last_enabled: datetime.datetime(); (UTC) or None
421
420
    name:       string; from the config file, used in log messages and
422
421
                        D-Bus identifiers
423
422
    secret:     bytestring; sent verbatim (over TLS) to client
424
423
    timeout:    datetime.timedelta(); How long from last_checked_ok
425
424
                                      until this client is disabled
426
 
    extended_timeout:   extra long timeout when secret has been sent
 
425
    extended_timeout:   extra long timeout when password has been sent
427
426
    runtime_expansions: Allowed attributes for runtime expansion.
428
427
    expires:    datetime.datetime(); time (UTC) when a client will be
429
428
                disabled, or None
502
501
            client["checker_command"] = section["checker"]
503
502
            client["last_approval_request"] = None
504
503
            client["last_checked_ok"] = None
505
 
            client["last_checker_status"] = -2
 
504
            client["last_checker_status"] = None
506
505
        
507
506
        return settings
508
507
        
627
626
            logger.warning("Checker for %(name)s crashed?",
628
627
                           vars(self))
629
628
    
630
 
    def checked_ok(self):
631
 
        """Assert that the client has been seen, alive and well."""
632
 
        self.last_checked_ok = datetime.datetime.utcnow()
633
 
        self.last_checker_status = 0
634
 
        self.bump_timeout()
635
 
    
636
 
    def bump_timeout(self, timeout=None):
637
 
        """Bump up the timeout for this client."""
 
629
    def checked_ok(self, timeout=None):
 
630
        """Bump up the timeout for this client.
 
631
        
 
632
        This should only be called when the client has been seen,
 
633
        alive and well.
 
634
        """
638
635
        if timeout is None:
639
636
            timeout = self.timeout
 
637
        self.last_checked_ok = datetime.datetime.utcnow()
640
638
        if self.disable_initiator_tag is not None:
641
639
            gobject.source_remove(self.disable_initiator_tag)
642
640
        if getattr(self, "enabled", False):
772
770
    return decorator
773
771
 
774
772
 
775
 
def dbus_interface_annotations(dbus_interface):
776
 
    """Decorator for marking functions returning interface annotations.
777
 
    
778
 
    Usage:
779
 
    
780
 
    @dbus_interface_annotations("org.example.Interface")
781
 
    def _foo(self):  # Function name does not matter
782
 
        return {"org.freedesktop.DBus.Deprecated": "true",
783
 
                "org.freedesktop.DBus.Property.EmitsChangedSignal":
784
 
                    "false"}
785
 
    """
786
 
    def decorator(func):
787
 
        func._dbus_is_interface = True
788
 
        func._dbus_interface = dbus_interface
789
 
        func._dbus_name = dbus_interface
790
 
        return func
791
 
    return decorator
792
 
 
793
 
 
794
 
def dbus_annotations(annotations):
795
 
    """Decorator to annotate D-Bus methods, signals or properties
796
 
    Usage:
797
 
    
798
 
    @dbus_service_property("org.example.Interface", signature="b",
799
 
                           access="r")
800
 
    @dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
801
 
                        "org.freedesktop.DBus.Property."
802
 
                        "EmitsChangedSignal": "false"})
803
 
    def Property_dbus_property(self):
804
 
        return dbus.Boolean(False)
805
 
    """
806
 
    def decorator(func):
807
 
        func._dbus_annotations = annotations
808
 
        return func
809
 
    return decorator
810
 
 
811
 
 
812
773
class DBusPropertyException(dbus.exceptions.DBusException):
813
774
    """A base class for D-Bus property-related exceptions
814
775
    """
837
798
    """
838
799
    
839
800
    @staticmethod
840
 
    def _is_dbus_thing(thing):
841
 
        """Returns a function testing if an attribute is a D-Bus thing
842
 
        
843
 
        If called like _is_dbus_thing("method") it returns a function
844
 
        suitable for use as predicate to inspect.getmembers().
845
 
        """
846
 
        return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
847
 
                                   False)
 
801
    def _is_dbus_property(obj):
 
802
        return getattr(obj, "_dbus_is_property", False)
848
803
    
849
 
    def _get_all_dbus_things(self, thing):
 
804
    def _get_all_dbus_properties(self):
850
805
        """Returns a generator of (name, attribute) pairs
851
806
        """
852
 
        return ((getattr(athing.__get__(self), "_dbus_name",
853
 
                         name),
854
 
                 athing.__get__(self))
 
807
        return ((prop.__get__(self)._dbus_name, prop.__get__(self))
855
808
                for cls in self.__class__.__mro__
856
 
                for name, athing in
857
 
                inspect.getmembers(cls,
858
 
                                   self._is_dbus_thing(thing)))
 
809
                for name, prop in
 
810
                inspect.getmembers(cls, self._is_dbus_property))
859
811
    
860
812
    def _get_dbus_property(self, interface_name, property_name):
861
813
        """Returns a bound method if one exists which is a D-Bus
863
815
        """
864
816
        for cls in  self.__class__.__mro__:
865
817
            for name, value in (inspect.getmembers
866
 
                                (cls,
867
 
                                 self._is_dbus_thing("property"))):
 
818
                                (cls, self._is_dbus_property)):
868
819
                if (value._dbus_name == property_name
869
820
                    and value._dbus_interface == interface_name):
870
821
                    return value.__get__(self)
899
850
            # signatures other than "ay".
900
851
            if prop._dbus_signature != "ay":
901
852
                raise ValueError
902
 
            value = dbus.ByteArray(b''.join(chr(byte)
903
 
                                            for byte in value))
 
853
            value = dbus.ByteArray(''.join(unichr(byte)
 
854
                                           for byte in value))
904
855
        prop(value)
905
856
    
906
857
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
912
863
        Note: Will not include properties with access="write".
913
864
        """
914
865
        properties = {}
915
 
        for name, prop in self._get_all_dbus_things("property"):
 
866
        for name, prop in self._get_all_dbus_properties():
916
867
            if (interface_name
917
868
                and interface_name != prop._dbus_interface):
918
869
                # Interface non-empty but did not match
933
884
                         path_keyword='object_path',
934
885
                         connection_keyword='connection')
935
886
    def Introspect(self, object_path, connection):
936
 
        """Overloading of standard D-Bus method.
937
 
        
938
 
        Inserts property tags and interface annotation tags.
 
887
        """Standard D-Bus method, overloaded to insert property tags.
939
888
        """
940
889
        xmlstring = dbus.service.Object.Introspect(self, object_path,
941
890
                                                   connection)
948
897
                e.setAttribute("access", prop._dbus_access)
949
898
                return e
950
899
            for if_tag in document.getElementsByTagName("interface"):
951
 
                # Add property tags
952
900
                for tag in (make_tag(document, name, prop)
953
901
                            for name, prop
954
 
                            in self._get_all_dbus_things("property")
 
902
                            in self._get_all_dbus_properties()
955
903
                            if prop._dbus_interface
956
904
                            == if_tag.getAttribute("name")):
957
905
                    if_tag.appendChild(tag)
958
 
                # Add annotation tags
959
 
                for typ in ("method", "signal", "property"):
960
 
                    for tag in if_tag.getElementsByTagName(typ):
961
 
                        annots = dict()
962
 
                        for name, prop in (self.
963
 
                                           _get_all_dbus_things(typ)):
964
 
                            if (name == tag.getAttribute("name")
965
 
                                and prop._dbus_interface
966
 
                                == if_tag.getAttribute("name")):
967
 
                                annots.update(getattr
968
 
                                              (prop,
969
 
                                               "_dbus_annotations",
970
 
                                               {}))
971
 
                        for name, value in annots.iteritems():
972
 
                            ann_tag = document.createElement(
973
 
                                "annotation")
974
 
                            ann_tag.setAttribute("name", name)
975
 
                            ann_tag.setAttribute("value", value)
976
 
                            tag.appendChild(ann_tag)
977
 
                # Add interface annotation tags
978
 
                for annotation, value in dict(
979
 
                    itertools.chain(
980
 
                        *(annotations().iteritems()
981
 
                          for name, annotations in
982
 
                          self._get_all_dbus_things("interface")
983
 
                          if name == if_tag.getAttribute("name")
984
 
                          ))).iteritems():
985
 
                    ann_tag = document.createElement("annotation")
986
 
                    ann_tag.setAttribute("name", annotation)
987
 
                    ann_tag.setAttribute("value", value)
988
 
                    if_tag.appendChild(ann_tag)
989
906
                # Add the names to the return values for the
990
907
                # "org.freedesktop.DBus.Properties" methods
991
908
                if (if_tag.getAttribute("name")
1026
943
    def __new__(mcs, name, bases, attr):
1027
944
        # Go through all the base classes which could have D-Bus
1028
945
        # methods, signals, or properties in them
1029
 
        old_interface_names = []
1030
946
        for base in (b for b in bases
1031
947
                     if issubclass(b, dbus.service.Object)):
1032
948
            # Go though all attributes of the base class
1042
958
                alt_interface = (attribute._dbus_interface
1043
959
                                 .replace("se.recompile.Mandos",
1044
960
                                          "se.bsnet.fukt.Mandos"))
1045
 
                if alt_interface != attribute._dbus_interface:
1046
 
                    old_interface_names.append(alt_interface)
1047
961
                # Is this a D-Bus signal?
1048
962
                if getattr(attribute, "_dbus_is_signal", False):
1049
963
                    # Extract the original non-method function by
1064
978
                                nonmethod_func.func_name,
1065
979
                                nonmethod_func.func_defaults,
1066
980
                                nonmethod_func.func_closure)))
1067
 
                    # Copy annotations, if any
1068
 
                    try:
1069
 
                        new_function._dbus_annotations = (
1070
 
                            dict(attribute._dbus_annotations))
1071
 
                    except AttributeError:
1072
 
                        pass
1073
981
                    # Define a creator of a function to call both the
1074
982
                    # old and new functions, so both the old and new
1075
983
                    # signals gets sent when the function is called
1103
1011
                                        attribute.func_name,
1104
1012
                                        attribute.func_defaults,
1105
1013
                                        attribute.func_closure)))
1106
 
                    # Copy annotations, if any
1107
 
                    try:
1108
 
                        attr[attrname]._dbus_annotations = (
1109
 
                            dict(attribute._dbus_annotations))
1110
 
                    except AttributeError:
1111
 
                        pass
1112
1014
                # Is this a D-Bus property?
1113
1015
                elif getattr(attribute, "_dbus_is_property", False):
1114
1016
                    # Create a new, but exactly alike, function
1128
1030
                                        attribute.func_name,
1129
1031
                                        attribute.func_defaults,
1130
1032
                                        attribute.func_closure)))
1131
 
                    # Copy annotations, if any
1132
 
                    try:
1133
 
                        attr[attrname]._dbus_annotations = (
1134
 
                            dict(attribute._dbus_annotations))
1135
 
                    except AttributeError:
1136
 
                        pass
1137
 
                # Is this a D-Bus interface?
1138
 
                elif getattr(attribute, "_dbus_is_interface", False):
1139
 
                    # Create a new, but exactly alike, function
1140
 
                    # object.  Decorate it to be a new D-Bus interface
1141
 
                    # with the alternate D-Bus interface name.  Add it
1142
 
                    # to the class.
1143
 
                    attr[attrname] = (dbus_interface_annotations
1144
 
                                      (alt_interface)
1145
 
                                      (types.FunctionType
1146
 
                                       (attribute.func_code,
1147
 
                                        attribute.func_globals,
1148
 
                                        attribute.func_name,
1149
 
                                        attribute.func_defaults,
1150
 
                                        attribute.func_closure)))
1151
 
        # Deprecate all old interfaces
1152
 
        basename="_AlternateDBusNamesMetaclass_interface_annotation{0}"
1153
 
        for old_interface_name in old_interface_names:
1154
 
            @dbus_interface_annotations(old_interface_name)
1155
 
            def func(self):
1156
 
                return { "org.freedesktop.DBus.Deprecated": "true" }
1157
 
            # Find an unused name
1158
 
            for aname in (basename.format(i) for i in
1159
 
                          itertools.count()):
1160
 
                if aname not in attr:
1161
 
                    attr[aname] = func
1162
 
                    break
1163
1033
        return type.__new__(mcs, name, bases, attr)
1164
1034
 
1165
1035
 
1179
1049
    def __init__(self, bus = None, *args, **kwargs):
1180
1050
        self.bus = bus
1181
1051
        Client.__init__(self, *args, **kwargs)
 
1052
        self._approvals_pending = 0
 
1053
        
 
1054
        self._approvals_pending = 0
1182
1055
        # Only now, when this client is initialized, can it show up on
1183
1056
        # the D-Bus
1184
1057
        client_object_name = unicode(self.name).translate(
1230
1103
                                       checker is not None)
1231
1104
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
1232
1105
                                           "LastCheckedOK")
1233
 
    last_checker_status = notifychangeproperty(dbus.Int16,
1234
 
                                               "LastCheckerStatus")
1235
1106
    last_approval_request = notifychangeproperty(
1236
1107
        datetime_to_dbus, "LastApprovalRequest")
1237
1108
    approved_by_default = notifychangeproperty(dbus.Boolean,
1315
1186
    ## D-Bus methods, signals & properties
1316
1187
    _interface = "se.recompile.Mandos.Client"
1317
1188
    
1318
 
    ## Interfaces
1319
 
    
1320
 
    @dbus_interface_annotations(_interface)
1321
 
    def _foo(self):
1322
 
        return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
1323
 
                     "false"}
1324
 
    
1325
1189
    ## Signals
1326
1190
    
1327
1191
    # CheckerCompleted - signal
1363
1227
        "D-Bus signal"
1364
1228
        return self.need_approval()
1365
1229
    
 
1230
    # NeRwequest - signal
 
1231
    @dbus.service.signal(_interface, signature="s")
 
1232
    def NewRequest(self, ip):
 
1233
        """D-Bus signal
 
1234
        Is sent after a client request a password.
 
1235
        """
 
1236
        pass
 
1237
    
1366
1238
    ## Methods
1367
1239
    
1368
1240
    # Approve - method
1478
1350
            return
1479
1351
        return datetime_to_dbus(self.last_checked_ok)
1480
1352
    
1481
 
    # LastCheckerStatus - property
1482
 
    @dbus_service_property(_interface, signature="n",
1483
 
                           access="read")
1484
 
    def LastCheckerStatus_dbus_property(self):
1485
 
        return dbus.Int16(self.last_checker_status)
1486
 
    
1487
1353
    # Expires - property
1488
1354
    @dbus_service_property(_interface, signature="s", access="read")
1489
1355
    def Expires_dbus_property(self):
1681
1547
                except KeyError:
1682
1548
                    return
1683
1549
                
 
1550
                if self.server.use_dbus:
 
1551
                    # Emit D-Bus signal
 
1552
                    client.NewRequest(str(self.client_address))
 
1553
                
1684
1554
                if client.approval_delay:
1685
1555
                    delay = client.approval_delay
1686
1556
                    client.approvals_pending += 1
1750
1620
                
1751
1621
                logger.info("Sending secret to %s", client.name)
1752
1622
                # bump the timeout using extended_timeout
1753
 
                client.bump_timeout(client.extended_timeout)
 
1623
                client.checked_ok(client.extended_timeout)
1754
1624
                if self.server.use_dbus:
1755
1625
                    # Emit D-Bus signal
1756
1626
                    client.GotSecret()
2099
1969
        sys.exit()
2100
1970
    if not noclose:
2101
1971
        # Close all standard open file descriptors
2102
 
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
 
1972
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2103
1973
        if not stat.S_ISCHR(os.fstat(null).st_mode):
2104
1974
            raise OSError(errno.ENODEV,
2105
1975
                          "%s not a character device"
2106
 
                          % os.devnull)
 
1976
                          % os.path.devnull)
2107
1977
        os.dup2(null, sys.stdin.fileno())
2108
1978
        os.dup2(null, sys.stdout.fileno())
2109
1979
        os.dup2(null, sys.stderr.fileno())
2292
2162
         .gnutls_global_set_log_function(debug_gnutls))
2293
2163
        
2294
2164
        # Redirect stdin so all checkers get /dev/null
2295
 
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
 
2165
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2296
2166
        os.dup2(null, sys.stdin.fileno())
2297
2167
        if null > 2:
2298
2168
            os.close(null)
2306
2176
    
2307
2177
    global main_loop
2308
2178
    # From the Avahi example code
2309
 
    DBusGMainLoop(set_as_default=True)
 
2179
    DBusGMainLoop(set_as_default=True )
2310
2180
    main_loop = gobject.MainLoop()
2311
2181
    bus = dbus.SystemBus()
2312
2182
    # End of Avahi example code
2380
2250
            
2381
2251
            # Clients who has passed its expire date can still be
2382
2252
            # enabled if its last checker was successful.  Clients
2383
 
            # whose checker succeeded before we stored its state is
2384
 
            # assumed to have successfully run all checkers during
2385
 
            # downtime.
 
2253
            # whose checker failed before we stored its state is
 
2254
            # assumed to have failed all checkers during downtime.
2386
2255
            if client["enabled"]:
2387
2256
                if datetime.datetime.utcnow() >= client["expires"]:
2388
2257
                    if not client["last_checked_ok"]:
2389
2258
                        logger.warning(
2390
2259
                            "disabling client {0} - Client never "
2391
 
                            "performed a successful checker"
2392
 
                            .format(client_name))
 
2260
                            "performed a successfull checker"
 
2261
                            .format(client["name"]))
2393
2262
                        client["enabled"] = False
2394
2263
                    elif client["last_checker_status"] != 0:
2395
2264
                        logger.warning(
2396
2265
                            "disabling client {0} - Client "
2397
2266
                            "last checker failed with error code {1}"
2398
 
                            .format(client_name,
 
2267
                            .format(client["name"],
2399
2268
                                    client["last_checker_status"]))
2400
2269
                        client["enabled"] = False
2401
2270
                    else:
2404
2273
                                             + client["timeout"])
2405
2274
                        logger.debug("Last checker succeeded,"
2406
2275
                                     " keeping {0} enabled"
2407
 
                                     .format(client_name))
 
2276
                                     .format(client["name"]))
2408
2277
            try:
2409
2278
                client["secret"] = (
2410
2279
                    pgp.decrypt(client["encrypted_secret"],
2426
2295
                        - set(old_client_settings)):
2427
2296
        clients_data[client_name] = client_settings[client_name]
2428
2297
 
2429
 
    # Create all client objects
 
2298
    # Create clients all clients
2430
2299
    for client_name, client in clients_data.iteritems():
2431
2300
        tcp_server.clients[client_name] = client_class(
2432
2301
            name = client_name, settings = client)
2453
2322
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2454
2323
    
2455
2324
    if use_dbus:
2456
 
        class MandosDBusService(DBusObjectWithProperties):
 
2325
        class MandosDBusService(dbus.service.Object):
2457
2326
            """A D-Bus proxy object"""
2458
2327
            def __init__(self):
2459
2328
                dbus.service.Object.__init__(self, bus, "/")
2460
2329
            _interface = "se.recompile.Mandos"
2461
2330
            
2462
 
            @dbus_interface_annotations(_interface)
2463
 
            def _foo(self):
2464
 
                return { "org.freedesktop.DBus.Property"
2465
 
                         ".EmitsChangedSignal":
2466
 
                             "false"}
2467
 
            
2468
2331
            @dbus.service.signal(_interface, signature="o")
2469
2332
            def ClientAdded(self, objpath):
2470
2333
                "D-Bus signal"