/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-09-26 19:36:18 UTC
  • mfrom: (24.1.184 mandos)
  • Revision ID: teddy@fukt.bsnet.se-20110926193618-vtj5c9hena1maixx
Merge from Björn

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
# along with this program.  If not, see
29
29
# <http://www.gnu.org/licenses/>.
30
30
31
 
# Contact the authors at <mandos@recompile.se>.
 
31
# Contact the authors at <mandos@fukt.bsnet.se>.
32
32
33
33
 
34
34
from __future__ import (division, absolute_import, print_function,
62
62
import functools
63
63
import cPickle as pickle
64
64
import multiprocessing
65
 
import types
66
65
 
67
66
import dbus
68
67
import dbus.service
314
313
                          "created", "enabled", "fingerprint",
315
314
                          "host", "interval", "last_checked_ok",
316
315
                          "last_enabled", "name", "timeout")
317
 
    
 
316
        
318
317
    def timeout_milliseconds(self):
319
318
        "Return the 'timeout' attribute in milliseconds"
320
319
        return _timedelta_to_milliseconds(self.timeout)
321
 
    
 
320
 
322
321
    def extended_timeout_milliseconds(self):
323
322
        "Return the 'extended_timeout' attribute in milliseconds"
324
323
        return _timedelta_to_milliseconds(self.extended_timeout)    
326
325
    def interval_milliseconds(self):
327
326
        "Return the 'interval' attribute in milliseconds"
328
327
        return _timedelta_to_milliseconds(self.interval)
329
 
    
 
328
 
330
329
    def approval_delay_milliseconds(self):
331
330
        return _timedelta_to_milliseconds(self.approval_delay)
332
331
    
510
509
                                       'replace')))
511
510
                    for attr in
512
511
                    self.runtime_expansions)
513
 
                
 
512
 
514
513
                try:
515
514
                    command = self.checker_command % escaped_attrs
516
515
                except TypeError as error:
562
561
                raise
563
562
        self.checker = None
564
563
 
565
 
 
566
564
def dbus_service_property(dbus_interface, signature="v",
567
565
                          access="readwrite", byte_arrays=False):
568
566
    """Decorators for marking methods of a DBusObjectWithProperties to
614
612
 
615
613
class DBusObjectWithProperties(dbus.service.Object):
616
614
    """A D-Bus object with properties.
617
 
    
 
615
 
618
616
    Classes inheriting from this can use the dbus_service_property
619
617
    decorator to expose methods as D-Bus properties.  It exposes the
620
618
    standard Get(), Set(), and GetAll() methods on the D-Bus.
627
625
    def _get_all_dbus_properties(self):
628
626
        """Returns a generator of (name, attribute) pairs
629
627
        """
630
 
        return ((prop.__get__(self)._dbus_name, prop.__get__(self))
631
 
                for cls in self.__class__.__mro__
632
 
                for name, prop in inspect.getmembers(cls, self._is_dbus_property))
 
628
        return ((prop._dbus_name, prop)
 
629
                for name, prop in
 
630
                inspect.getmembers(self, self._is_dbus_property))
633
631
    
634
632
    def _get_dbus_property(self, interface_name, property_name):
635
633
        """Returns a bound method if one exists which is a D-Bus
636
634
        property with the specified name and interface.
637
635
        """
638
 
        for cls in  self.__class__.__mro__:
639
 
            for name, value in inspect.getmembers(cls, self._is_dbus_property):
640
 
                if value._dbus_name == property_name and value._dbus_interface == interface_name:
641
 
                    return value.__get__(self)
642
 
        
 
636
        for name in (property_name,
 
637
                     property_name + "_dbus_property"):
 
638
            prop = getattr(self, name, None)
 
639
            if (prop is None
 
640
                or not self._is_dbus_property(prop)
 
641
                or prop._dbus_name != property_name
 
642
                or (interface_name and prop._dbus_interface
 
643
                    and interface_name != prop._dbus_interface)):
 
644
                continue
 
645
            return prop
643
646
        # No such property
644
647
        raise DBusPropertyNotFound(self.dbus_object_path + ":"
645
648
                                   + interface_name + "."
679
682
    def GetAll(self, interface_name):
680
683
        """Standard D-Bus property GetAll() method, see D-Bus
681
684
        standard.
682
 
        
 
685
 
683
686
        Note: Will not include properties with access="write".
684
687
        """
685
688
        all = {}
754
757
    return dbus.String(dt.isoformat(),
755
758
                       variant_level=variant_level)
756
759
 
757
 
class AlternateDBusNamesMetaclass(DBusObjectWithProperties.__metaclass__):
758
 
    """Applied to an empty subclass of a D-Bus object, this metaclass
759
 
    will add additional D-Bus attributes matching a certain pattern.
760
 
    """
761
 
    def __new__(mcs, name, bases, attr):
762
 
        # Go through all the base classes which could have D-Bus
763
 
        # methods, signals, or properties in them
764
 
        for base in (b for b in bases
765
 
                     if issubclass(b, dbus.service.Object)):
766
 
            # Go though all attributes of the base class
767
 
            for attrname, attribute in inspect.getmembers(base):
768
 
                # Ignore non-D-Bus attributes, and D-Bus attributes
769
 
                # with the wrong interface name
770
 
                if (not hasattr(attribute, "_dbus_interface")
771
 
                    or not attribute._dbus_interface
772
 
                    .startswith("se.recompile.Mandos")):
773
 
                    continue
774
 
                # Create an alternate D-Bus interface name based on
775
 
                # the current name
776
 
                alt_interface = (attribute._dbus_interface
777
 
                                 .replace("se.recompile.Mandos",
778
 
                                          "se.bsnet.fukt.Mandos"))
779
 
                # Is this a D-Bus signal?
780
 
                if getattr(attribute, "_dbus_is_signal", False):
781
 
                    # Extract the original non-method function by
782
 
                    # black magic
783
 
                    nonmethod_func = (dict(
784
 
                            zip(attribute.func_code.co_freevars,
785
 
                                attribute.__closure__))["func"]
786
 
                                      .cell_contents)
787
 
                    # Create a new, but exactly alike, function
788
 
                    # object, and decorate it to be a new D-Bus signal
789
 
                    # with the alternate D-Bus interface name
790
 
                    new_function = (dbus.service.signal
791
 
                                    (alt_interface,
792
 
                                     attribute._dbus_signature)
793
 
                                    (types.FunctionType(
794
 
                                nonmethod_func.func_code,
795
 
                                nonmethod_func.func_globals,
796
 
                                nonmethod_func.func_name,
797
 
                                nonmethod_func.func_defaults,
798
 
                                nonmethod_func.func_closure)))
799
 
                    # Define a creator of a function to call both the
800
 
                    # old and new functions, so both the old and new
801
 
                    # signals gets sent when the function is called
802
 
                    def fixscope(func1, func2):
803
 
                        """This function is a scope container to pass
804
 
                        func1 and func2 to the "call_both" function
805
 
                        outside of its arguments"""
806
 
                        def call_both(*args, **kwargs):
807
 
                            """This function will emit two D-Bus
808
 
                            signals by calling func1 and func2"""
809
 
                            func1(*args, **kwargs)
810
 
                            func2(*args, **kwargs)
811
 
                        return call_both
812
 
                    # Create the "call_both" function and add it to
813
 
                    # the class
814
 
                    attr[attrname] = fixscope(attribute,
815
 
                                              new_function)
816
 
                # Is this a D-Bus method?
817
 
                elif getattr(attribute, "_dbus_is_method", False):
818
 
                    # Create a new, but exactly alike, function
819
 
                    # object.  Decorate it to be a new D-Bus method
820
 
                    # with the alternate D-Bus interface name.  Add it
821
 
                    # to the class.
822
 
                    attr[attrname] = (dbus.service.method
823
 
                                      (alt_interface,
824
 
                                       attribute._dbus_in_signature,
825
 
                                       attribute._dbus_out_signature)
826
 
                                      (types.FunctionType
827
 
                                       (attribute.func_code,
828
 
                                        attribute.func_globals,
829
 
                                        attribute.func_name,
830
 
                                        attribute.func_defaults,
831
 
                                        attribute.func_closure)))
832
 
                # Is this a D-Bus property?
833
 
                elif getattr(attribute, "_dbus_is_property", False):
834
 
                    # Create a new, but exactly alike, function
835
 
                    # object, and decorate it to be a new D-Bus
836
 
                    # property with the alternate D-Bus interface
837
 
                    # name.  Add it to the class.
838
 
                    attr[attrname] = (dbus_service_property
839
 
                                      (alt_interface,
840
 
                                       attribute._dbus_signature,
841
 
                                       attribute._dbus_access,
842
 
                                       attribute
843
 
                                       ._dbus_get_args_options
844
 
                                       ["byte_arrays"])
845
 
                                      (types.FunctionType
846
 
                                       (attribute.func_code,
847
 
                                        attribute.func_globals,
848
 
                                        attribute.func_name,
849
 
                                        attribute.func_defaults,
850
 
                                        attribute.func_closure)))
851
 
        return type.__new__(mcs, name, bases, attr)
852
 
 
853
760
class ClientDBus(Client, DBusObjectWithProperties):
854
761
    """A Client class using D-Bus
855
762
    
880
787
    def notifychangeproperty(transform_func,
881
788
                             dbus_name, type_func=lambda x: x,
882
789
                             variant_level=1):
883
 
        """ Modify a variable so that it's a property which announces
884
 
        its changes to DBus.
885
 
 
886
 
        transform_fun: Function that takes a value and transforms it
887
 
                       to a D-Bus type.
888
 
        dbus_name: D-Bus name of the variable
 
790
        """ Modify a variable so that its a property that announce its
 
791
        changes to DBus.
 
792
        transform_fun: Function that takes a value and transform it to
 
793
                       DBus type.
 
794
        dbus_name: DBus name of the variable
889
795
        type_func: Function that transform the value before sending it
890
 
                   to the D-Bus.  Default: no transform
891
 
        variant_level: D-Bus variant level.  Default: 1
 
796
                   to DBus
 
797
        variant_level: DBus variant level. default: 1
892
798
        """
893
799
        real_value = [None,]
894
800
        def setter(self, value):
900
806
                                                variant_level)
901
807
                    self.PropertyChanged(dbus.String(dbus_name),
902
808
                                         dbus_value)
903
 
        
 
809
 
904
810
        return property(lambda self: real_value[0], setter)
905
 
    
906
 
    
 
811
 
 
812
 
907
813
    expires = notifychangeproperty(datetime_to_dbus, "Expires")
908
814
    approvals_pending = notifychangeproperty(dbus.Boolean,
909
815
                                             "ApprovalPending",
961
867
        
962
868
        return Client.checker_callback(self, pid, condition, command,
963
869
                                       *args, **kwargs)
964
 
    
 
870
 
965
871
    def start_checker(self, *args, **kwargs):
966
872
        old_checker = self.checker
967
873
        if self.checker is not None:
989
895
    
990
896
    
991
897
    ## D-Bus methods, signals & properties
992
 
    _interface = "se.recompile.Mandos.Client"
 
898
    _interface = "se.bsnet.fukt.Mandos.Client"
993
899
    
994
900
    ## Signals
995
901
    
1184
1090
                            + datetime.timedelta(milliseconds = time_to_die))
1185
1091
            self.disable_initiator_tag = (gobject.timeout_add
1186
1092
                                          (time_to_die, self.disable))
1187
 
    
 
1093
 
1188
1094
    # ExtendedTimeout - property
1189
1095
    @dbus_service_property(_interface, signature="t",
1190
1096
                           access="readwrite")
1192
1098
        if value is None:       # get
1193
1099
            return dbus.UInt64(self.extended_timeout_milliseconds())
1194
1100
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1195
 
    
 
1101
 
1196
1102
    # Interval - property
1197
1103
    @dbus_service_property(_interface, signature="t",
1198
1104
                           access="readwrite")
1207
1113
        self.checker_initiator_tag = (gobject.timeout_add
1208
1114
                                      (value, self.start_checker))
1209
1115
        self.start_checker()    # Start one now, too
1210
 
    
 
1116
 
1211
1117
    # Checker - property
1212
1118
    @dbus_service_property(_interface, signature="s",
1213
1119
                           access="readwrite")
1247
1153
        self._pipe.send(('init', fpr, address))
1248
1154
        if not self._pipe.recv():
1249
1155
            raise KeyError()
1250
 
    
 
1156
 
1251
1157
    def __getattribute__(self, name):
1252
1158
        if(name == '_pipe'):
1253
1159
            return super(ProxyClient, self).__getattribute__(name)
1260
1166
                self._pipe.send(('funcall', name, args, kwargs))
1261
1167
                return self._pipe.recv()[1]
1262
1168
            return func
1263
 
    
 
1169
 
1264
1170
    def __setattr__(self, name, value):
1265
1171
        if(name == '_pipe'):
1266
1172
            return super(ProxyClient, self).__setattr__(name, value)
1267
1173
        self._pipe.send(('setattr', name, value))
1268
1174
 
1269
 
class ClientDBusTransitional(ClientDBus):
1270
 
    __metaclass__ = AlternateDBusNamesMetaclass
1271
1175
 
1272
1176
class ClientHandler(socketserver.BaseRequestHandler, object):
1273
1177
    """A class to handle client connections.
1281
1185
                        unicode(self.client_address))
1282
1186
            logger.debug("Pipe FD: %d",
1283
1187
                         self.server.child_pipe.fileno())
1284
 
            
 
1188
 
1285
1189
            session = (gnutls.connection
1286
1190
                       .ClientSession(self.request,
1287
1191
                                      gnutls.connection
1288
1192
                                      .X509Credentials()))
1289
 
            
 
1193
 
1290
1194
            # Note: gnutls.connection.X509Credentials is really a
1291
1195
            # generic GnuTLS certificate credentials object so long as
1292
1196
            # no X.509 keys are added to it.  Therefore, we can use it
1293
1197
            # here despite using OpenPGP certificates.
1294
 
            
 
1198
 
1295
1199
            #priority = ':'.join(("NONE", "+VERS-TLS1.1",
1296
1200
            #                      "+AES-256-CBC", "+SHA1",
1297
1201
            #                      "+COMP-NULL", "+CTYPE-OPENPGP",
1303
1207
            (gnutls.library.functions
1304
1208
             .gnutls_priority_set_direct(session._c_object,
1305
1209
                                         priority, None))
1306
 
            
 
1210
 
1307
1211
            # Start communication using the Mandos protocol
1308
1212
            # Get protocol number
1309
1213
            line = self.request.makefile().readline()
1314
1218
            except (ValueError, IndexError, RuntimeError) as error:
1315
1219
                logger.error("Unknown protocol version: %s", error)
1316
1220
                return
1317
 
            
 
1221
 
1318
1222
            # Start GnuTLS connection
1319
1223
            try:
1320
1224
                session.handshake()
1324
1228
                # established.  Just abandon the request.
1325
1229
                return
1326
1230
            logger.debug("Handshake succeeded")
1327
 
            
 
1231
 
1328
1232
            approval_required = False
1329
1233
            try:
1330
1234
                try:
1335
1239
                    logger.warning("Bad certificate: %s", error)
1336
1240
                    return
1337
1241
                logger.debug("Fingerprint: %s", fpr)
1338
 
                
 
1242
 
1339
1243
                try:
1340
1244
                    client = ProxyClient(child_pipe, fpr,
1341
1245
                                         self.client_address)
1407
1311
                                 sent, len(client.secret)
1408
1312
                                 - (sent_size + sent))
1409
1313
                    sent_size += sent
1410
 
                
 
1314
 
1411
1315
                logger.info("Sending secret to %s", client.name)
1412
1316
                # bump the timeout as if seen
1413
1317
                client.checked_ok(client.extended_timeout)
1501
1405
        multiprocessing.Process(target = self.sub_process_main,
1502
1406
                                args = (request, address)).start()
1503
1407
 
1504
 
 
1505
1408
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1506
1409
    """ adds a pipe to the MixIn """
1507
1410
    def process_request(self, request, client_address):
1510
1413
        This function creates a new pipe in self.pipe
1511
1414
        """
1512
1415
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
1513
 
        
 
1416
 
1514
1417
        super(MultiprocessingMixInWithPipe,
1515
1418
              self).process_request(request, client_address)
1516
1419
        self.child_pipe.close()
1517
1420
        self.add_pipe(parent_pipe)
1518
 
    
 
1421
 
1519
1422
    def add_pipe(self, parent_pipe):
1520
1423
        """Dummy function; override as necessary"""
1521
1424
        raise NotImplementedError
1522
1425
 
1523
 
 
1524
1426
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1525
1427
                     socketserver.TCPServer, object):
1526
1428
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1674
1576
            kwargs = request[3]
1675
1577
            
1676
1578
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1677
 
        
 
1579
 
1678
1580
        if command == 'getattr':
1679
1581
            attrname = request[1]
1680
1582
            if callable(client_object.__getattribute__(attrname)):
1686
1588
            attrname = request[1]
1687
1589
            value = request[2]
1688
1590
            setattr(client_object, attrname, value)
1689
 
        
 
1591
 
1690
1592
        return True
1691
1593
 
1692
1594
 
1871
1773
    debuglevel = server_settings["debuglevel"]
1872
1774
    use_dbus = server_settings["use_dbus"]
1873
1775
    use_ipv6 = server_settings["use_ipv6"]
1874
 
    
 
1776
 
1875
1777
    if server_settings["servicename"] != "Mandos":
1876
1778
        syslogger.setFormatter(logging.Formatter
1877
1779
                               ('Mandos (%s) [%%(process)d]:'
1938
1840
        level = getattr(logging, debuglevel.upper())
1939
1841
        syslogger.setLevel(level)
1940
1842
        console.setLevel(level)
1941
 
    
 
1843
 
1942
1844
    if debug:
1943
1845
        # Enable all possible GnuTLS debugging
1944
1846
        
1975
1877
    # End of Avahi example code
1976
1878
    if use_dbus:
1977
1879
        try:
1978
 
            bus_name = dbus.service.BusName("se.recompile.Mandos",
 
1880
            bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
1979
1881
                                            bus, do_not_queue=True)
1980
 
            old_bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
1981
 
                                                bus, do_not_queue=True)
1982
1882
        except dbus.exceptions.NameExistsException as e:
1983
1883
            logger.error(unicode(e) + ", disabling D-Bus")
1984
1884
            use_dbus = False
1997
1897
    
1998
1898
    client_class = Client
1999
1899
    if use_dbus:
2000
 
        client_class = functools.partial(ClientDBusTransitional, bus = bus)        
 
1900
        client_class = functools.partial(ClientDBus, bus = bus)
2001
1901
    def client_config_items(config, section):
2002
1902
        special_settings = {
2003
1903
            "approved_by_default":
2033
1933
        del pidfilename
2034
1934
        
2035
1935
        signal.signal(signal.SIGINT, signal.SIG_IGN)
2036
 
    
 
1936
 
2037
1937
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2038
1938
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2039
1939
    
2042
1942
            """A D-Bus proxy object"""
2043
1943
            def __init__(self):
2044
1944
                dbus.service.Object.__init__(self, bus, "/")
2045
 
            _interface = "se.recompile.Mandos"
 
1945
            _interface = "se.bsnet.fukt.Mandos"
2046
1946
            
2047
1947
            @dbus.service.signal(_interface, signature="o")
2048
1948
            def ClientAdded(self, objpath):
2090
1990
            
2091
1991
            del _interface
2092
1992
        
2093
 
        class MandosDBusServiceTransitional(MandosDBusService):
2094
 
            __metaclass__ = AlternateDBusNamesMetaclass
2095
 
        mandos_dbus_service = MandosDBusServiceTransitional()
 
1993
        mandos_dbus_service = MandosDBusService()
2096
1994
    
2097
1995
    def cleanup():
2098
1996
        "Cleanup function; run on exit"
2162
2060
    # Must run before the D-Bus bus name gets deregistered
2163
2061
    cleanup()
2164
2062
 
2165
 
 
2166
2063
if __name__ == '__main__':
2167
2064
    main()