/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: Björn Påhlsson
  • Date: 2011-09-26 18:47:38 UTC
  • mto: This revision was merged to the branch mainline in revision 502.
  • Revision ID: belorn@fukt.bsnet.se-20110926184738-ee8kz5vc9pb3393m
updated TODO

Show diffs side-by-side

added added

removed removed

Lines of Context:
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 + "."
646
649
                                   + property_name)
647
 
 
648
650
    
649
651
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
650
652
                         out_signature="v")
680
682
    def GetAll(self, interface_name):
681
683
        """Standard D-Bus property GetAll() method, see D-Bus
682
684
        standard.
683
 
        
 
685
 
684
686
        Note: Will not include properties with access="write".
685
687
        """
686
688
        all = {}
755
757
    return dbus.String(dt.isoformat(),
756
758
                       variant_level=variant_level)
757
759
 
758
 
class transitional_dbus_metaclass(DBusObjectWithProperties.__metaclass__):
759
 
    def __new__(mcs, name, bases, attr):
760
 
        for attrname, old_dbusobj in inspect.getmembers(bases[0]):
761
 
            new_interface = getattr(old_dbusobj, "_dbus_interface", "").replace("se.bsnet.fukt.", "se.recompile.")
762
 
            if (getattr(old_dbusobj, "_dbus_is_signal", False)
763
 
                and old_dbusobj._dbus_interface.startswith("se.bsnet.fukt.Mandos")):
764
 
                unwrappedfunc = dict(zip(old_dbusobj.func_code.co_freevars,
765
 
                                    old_dbusobj.__closure__))["func"].cell_contents
766
 
                newfunc = types.FunctionType(unwrappedfunc.func_code,
767
 
                                             unwrappedfunc.func_globals,
768
 
                                             unwrappedfunc.func_name,
769
 
                                             unwrappedfunc.func_defaults,
770
 
                                             unwrappedfunc.func_closure)
771
 
                new_dbusfunc = dbus.service.signal(
772
 
                    new_interface, old_dbusobj._dbus_signature)(newfunc)            
773
 
                attr["_transitional_" + attrname] = new_dbusfunc
774
 
 
775
 
                def fixscope(func1, func2):
776
 
                    def newcall(*args, **kwargs):
777
 
                        func1(*args, **kwargs)
778
 
                        func2(*args, **kwargs)
779
 
                    return newcall
780
 
 
781
 
                attr[attrname] = fixscope(old_dbusobj, new_dbusfunc)
782
 
            
783
 
            elif (getattr(old_dbusobj, "_dbus_is_method", False)
784
 
                and old_dbusobj._dbus_interface.startswith("se.bsnet.fukt.Mandos")):
785
 
                new_dbusfunc = (dbus.service.method
786
 
                                (new_interface,
787
 
                                 old_dbusobj._dbus_in_signature,
788
 
                                 old_dbusobj._dbus_out_signature)
789
 
                                (types.FunctionType
790
 
                                 (old_dbusobj.func_code,
791
 
                                  old_dbusobj.func_globals,
792
 
                                  old_dbusobj.func_name,
793
 
                                  old_dbusobj.func_defaults,
794
 
                                  old_dbusobj.func_closure)))
795
 
 
796
 
                attr[attrname] = new_dbusfunc
797
 
            elif (getattr(old_dbusobj, "_dbus_is_property", False)
798
 
                  and old_dbusobj._dbus_interface.startswith("se.bsnet.fukt.Mandos")):
799
 
                new_dbusfunc = (dbus_service_property
800
 
                                (new_interface,
801
 
                                 old_dbusobj._dbus_signature,
802
 
                                 old_dbusobj._dbus_access,
803
 
                                 old_dbusobj._dbus_get_args_options["byte_arrays"])
804
 
                                (types.FunctionType
805
 
                                 (old_dbusobj.func_code,
806
 
                                  old_dbusobj.func_globals,
807
 
                                  old_dbusobj.func_name,
808
 
                                  old_dbusobj.func_defaults,
809
 
                                  old_dbusobj.func_closure)))
810
 
 
811
 
                attr[attrname] = new_dbusfunc
812
 
        return type.__new__(mcs, name, bases, attr)
813
 
 
814
760
class ClientDBus(Client, DBusObjectWithProperties):
815
761
    """A Client class using D-Bus
816
762
    
860
806
                                                variant_level)
861
807
                    self.PropertyChanged(dbus.String(dbus_name),
862
808
                                         dbus_value)
863
 
        
 
809
 
864
810
        return property(lambda self: real_value[0], setter)
865
 
    
866
 
    
 
811
 
 
812
 
867
813
    expires = notifychangeproperty(datetime_to_dbus, "Expires")
868
814
    approvals_pending = notifychangeproperty(dbus.Boolean,
869
815
                                             "ApprovalPending",
921
867
        
922
868
        return Client.checker_callback(self, pid, condition, command,
923
869
                                       *args, **kwargs)
924
 
    
 
870
 
925
871
    def start_checker(self, *args, **kwargs):
926
872
        old_checker = self.checker
927
873
        if self.checker is not None:
950
896
    
951
897
    ## D-Bus methods, signals & properties
952
898
    _interface = "se.bsnet.fukt.Mandos.Client"
953
 
 
 
899
    
954
900
    ## Signals
955
901
    
956
902
    # CheckerCompleted - signal
1144
1090
                            + datetime.timedelta(milliseconds = time_to_die))
1145
1091
            self.disable_initiator_tag = (gobject.timeout_add
1146
1092
                                          (time_to_die, self.disable))
1147
 
    
 
1093
 
1148
1094
    # ExtendedTimeout - property
1149
1095
    @dbus_service_property(_interface, signature="t",
1150
1096
                           access="readwrite")
1152
1098
        if value is None:       # get
1153
1099
            return dbus.UInt64(self.extended_timeout_milliseconds())
1154
1100
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1155
 
    
 
1101
 
1156
1102
    # Interval - property
1157
1103
    @dbus_service_property(_interface, signature="t",
1158
1104
                           access="readwrite")
1167
1113
        self.checker_initiator_tag = (gobject.timeout_add
1168
1114
                                      (value, self.start_checker))
1169
1115
        self.start_checker()    # Start one now, too
1170
 
    
 
1116
 
1171
1117
    # Checker - property
1172
1118
    @dbus_service_property(_interface, signature="s",
1173
1119
                           access="readwrite")
1207
1153
        self._pipe.send(('init', fpr, address))
1208
1154
        if not self._pipe.recv():
1209
1155
            raise KeyError()
1210
 
    
 
1156
 
1211
1157
    def __getattribute__(self, name):
1212
1158
        if(name == '_pipe'):
1213
1159
            return super(ProxyClient, self).__getattribute__(name)
1220
1166
                self._pipe.send(('funcall', name, args, kwargs))
1221
1167
                return self._pipe.recv()[1]
1222
1168
            return func
1223
 
    
 
1169
 
1224
1170
    def __setattr__(self, name, value):
1225
1171
        if(name == '_pipe'):
1226
1172
            return super(ProxyClient, self).__setattr__(name, value)
1227
1173
        self._pipe.send(('setattr', name, value))
1228
1174
 
1229
 
class ClientDBusTransitional(ClientDBus):
1230
 
    __metaclass__ = transitional_dbus_metaclass
1231
1175
 
1232
1176
class ClientHandler(socketserver.BaseRequestHandler, object):
1233
1177
    """A class to handle client connections.
1241
1185
                        unicode(self.client_address))
1242
1186
            logger.debug("Pipe FD: %d",
1243
1187
                         self.server.child_pipe.fileno())
1244
 
            
 
1188
 
1245
1189
            session = (gnutls.connection
1246
1190
                       .ClientSession(self.request,
1247
1191
                                      gnutls.connection
1248
1192
                                      .X509Credentials()))
1249
 
            
 
1193
 
1250
1194
            # Note: gnutls.connection.X509Credentials is really a
1251
1195
            # generic GnuTLS certificate credentials object so long as
1252
1196
            # no X.509 keys are added to it.  Therefore, we can use it
1253
1197
            # here despite using OpenPGP certificates.
1254
 
            
 
1198
 
1255
1199
            #priority = ':'.join(("NONE", "+VERS-TLS1.1",
1256
1200
            #                      "+AES-256-CBC", "+SHA1",
1257
1201
            #                      "+COMP-NULL", "+CTYPE-OPENPGP",
1263
1207
            (gnutls.library.functions
1264
1208
             .gnutls_priority_set_direct(session._c_object,
1265
1209
                                         priority, None))
1266
 
            
 
1210
 
1267
1211
            # Start communication using the Mandos protocol
1268
1212
            # Get protocol number
1269
1213
            line = self.request.makefile().readline()
1274
1218
            except (ValueError, IndexError, RuntimeError) as error:
1275
1219
                logger.error("Unknown protocol version: %s", error)
1276
1220
                return
1277
 
            
 
1221
 
1278
1222
            # Start GnuTLS connection
1279
1223
            try:
1280
1224
                session.handshake()
1284
1228
                # established.  Just abandon the request.
1285
1229
                return
1286
1230
            logger.debug("Handshake succeeded")
1287
 
            
 
1231
 
1288
1232
            approval_required = False
1289
1233
            try:
1290
1234
                try:
1295
1239
                    logger.warning("Bad certificate: %s", error)
1296
1240
                    return
1297
1241
                logger.debug("Fingerprint: %s", fpr)
1298
 
                
 
1242
 
1299
1243
                try:
1300
1244
                    client = ProxyClient(child_pipe, fpr,
1301
1245
                                         self.client_address)
1367
1311
                                 sent, len(client.secret)
1368
1312
                                 - (sent_size + sent))
1369
1313
                    sent_size += sent
1370
 
                
 
1314
 
1371
1315
                logger.info("Sending secret to %s", client.name)
1372
1316
                # bump the timeout as if seen
1373
1317
                client.checked_ok(client.extended_timeout)
1461
1405
        multiprocessing.Process(target = self.sub_process_main,
1462
1406
                                args = (request, address)).start()
1463
1407
 
1464
 
 
1465
1408
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1466
1409
    """ adds a pipe to the MixIn """
1467
1410
    def process_request(self, request, client_address):
1470
1413
        This function creates a new pipe in self.pipe
1471
1414
        """
1472
1415
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
1473
 
        
 
1416
 
1474
1417
        super(MultiprocessingMixInWithPipe,
1475
1418
              self).process_request(request, client_address)
1476
1419
        self.child_pipe.close()
1477
1420
        self.add_pipe(parent_pipe)
1478
 
    
 
1421
 
1479
1422
    def add_pipe(self, parent_pipe):
1480
1423
        """Dummy function; override as necessary"""
1481
1424
        raise NotImplementedError
1482
1425
 
1483
 
 
1484
1426
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1485
1427
                     socketserver.TCPServer, object):
1486
1428
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1634
1576
            kwargs = request[3]
1635
1577
            
1636
1578
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1637
 
        
 
1579
 
1638
1580
        if command == 'getattr':
1639
1581
            attrname = request[1]
1640
1582
            if callable(client_object.__getattribute__(attrname)):
1646
1588
            attrname = request[1]
1647
1589
            value = request[2]
1648
1590
            setattr(client_object, attrname, value)
1649
 
        
 
1591
 
1650
1592
        return True
1651
1593
 
1652
1594
 
1831
1773
    debuglevel = server_settings["debuglevel"]
1832
1774
    use_dbus = server_settings["use_dbus"]
1833
1775
    use_ipv6 = server_settings["use_ipv6"]
1834
 
    
 
1776
 
1835
1777
    if server_settings["servicename"] != "Mandos":
1836
1778
        syslogger.setFormatter(logging.Formatter
1837
1779
                               ('Mandos (%s) [%%(process)d]:'
1898
1840
        level = getattr(logging, debuglevel.upper())
1899
1841
        syslogger.setLevel(level)
1900
1842
        console.setLevel(level)
1901
 
    
 
1843
 
1902
1844
    if debug:
1903
1845
        # Enable all possible GnuTLS debugging
1904
1846
        
1937
1879
        try:
1938
1880
            bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
1939
1881
                                            bus, do_not_queue=True)
1940
 
            bus_name2 = dbus.service.BusName("se.recompile.Mandos",
1941
 
                                            bus, do_not_queue=True)
1942
1882
        except dbus.exceptions.NameExistsException as e:
1943
1883
            logger.error(unicode(e) + ", disabling D-Bus")
1944
1884
            use_dbus = False
1957
1897
    
1958
1898
    client_class = Client
1959
1899
    if use_dbus:
1960
 
        client_class = functools.partial(ClientDBusTransitional, bus = bus)        
 
1900
        client_class = functools.partial(ClientDBus, bus = bus)
1961
1901
    def client_config_items(config, section):
1962
1902
        special_settings = {
1963
1903
            "approved_by_default":
1993
1933
        del pidfilename
1994
1934
        
1995
1935
        signal.signal(signal.SIGINT, signal.SIG_IGN)
1996
 
    
 
1936
 
1997
1937
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1998
1938
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1999
1939
    
2050
1990
            
2051
1991
            del _interface
2052
1992
        
2053
 
        class MandosDBusServiceTransitional(MandosDBusService):
2054
 
            __metaclass__ = transitional_dbus_metaclass
2055
 
        mandos_dbus_service = MandosDBusServiceTransitional()
 
1993
        mandos_dbus_service = MandosDBusService()
2056
1994
    
2057
1995
    def cleanup():
2058
1996
        "Cleanup function; run on exit"
2122
2060
    # Must run before the D-Bus bus name gets deregistered
2123
2061
    cleanup()
2124
2062
 
2125
 
 
2126
2063
if __name__ == '__main__':
2127
2064
    main()