/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-28 10:59:18 UTC
  • Revision ID: teddy@recompile.se-20160228105918-tb8pt2p5j0tkcls3
Handle GnuTLS errors and partial sends in gnutls "module".

* mandos (GnuTLS.E_INTERRUPTED, GnuTLS.E_AGAIN): New.
  (GnuTLS.Error): Set error code as "code" attribute.
  (GnuTLS.ClientSession.send): Handle partial sends with a loop.
  (GnuTLS._retry_on_error): New function.
  (GnuTLS.record_send, GnuTLS.handshake, GnuTLS.bye): Set "errcheck"
                                                      attribute to
                                                    "_retry_on_error".
  (ClientHandler.handle): Remove loop for handling partial sends;
                          GnuTLS.ClientSession.send() will do that.

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
 
import gnutls.connection
49
 
import gnutls.errors
50
 
import gnutls.library.functions
51
 
import gnutls.library.constants
52
 
import gnutls.library.types
53
47
try:
54
48
    import ConfigParser as configparser
55
49
except ImportError:
104
98
if sys.version_info.major == 2:
105
99
    str = unicode
106
100
 
107
 
version = "1.6.9"
 
101
version = "1.7.1"
108
102
stored_state_file = "clients.pickle"
109
103
 
110
104
logger = logging.getLogger()
395
389
                    logger.error(bad_states[state] + ": %r", error)
396
390
            self.cleanup()
397
391
        elif state == avahi.SERVER_RUNNING:
398
 
            self.add()
 
392
            try:
 
393
                self.add()
 
394
            except dbus.exceptions.DBusException as error:
 
395
                if (error.get_dbus_name()
 
396
                    == "org.freedesktop.Avahi.CollisionError"):
 
397
                    logger.info("Local Zeroconf service name"
 
398
                                " collision.")
 
399
                    return self.rename(remove=False)
 
400
                else:
 
401
                    logger.critical("D-Bus Exception", exc_info=error)
 
402
                    self.cleanup()
 
403
                    os._exit(1)
399
404
        else:
400
405
            if error is None:
401
406
                logger.debug("Unknown state: %r", state)
424
429
            .format(self.name)))
425
430
        return ret
426
431
 
 
432
# Pretend that we have a GnuTLS module
 
433
class GnuTLS(object):
 
434
    """This isn't so much a class as it is a module-like namespace.
 
435
    It is instantiated once, and simulates having a GnuTLS module."""
 
436
    
 
437
    _library = ctypes.cdll.LoadLibrary(
 
438
        ctypes.util.find_library("gnutls"))
 
439
    _need_version = "3.3.0"
 
440
    def __init__(self):
 
441
        # Need to use class name "GnuTLS" here, since this method is
 
442
        # called before the assignment to the "gnutls" global variable
 
443
        # happens.
 
444
        if GnuTLS.check_version(self._need_version) is None:
 
445
            raise GnuTLS.Error("Needs GnuTLS {} or later"
 
446
                               .format(self._need_version))
 
447
    
 
448
    # Unless otherwise indicated, the constants and types below are
 
449
    # all from the gnutls/gnutls.h C header file.
 
450
    
 
451
    # Constants
 
452
    E_SUCCESS = 0
 
453
    E_INTERRUPTED = -52
 
454
    E_AGAIN = -28
 
455
    CRT_OPENPGP = 2
 
456
    CLIENT = 2
 
457
    SHUT_RDWR = 0
 
458
    CRD_CERTIFICATE = 1
 
459
    E_NO_CERTIFICATE_FOUND = -49
 
460
    OPENPGP_FMT_RAW = 0         # gnutls/openpgp.h
 
461
    
 
462
    # Types
 
463
    class session_int(ctypes.Structure):
 
464
        _fields_ = []
 
465
    session_t = ctypes.POINTER(session_int)
 
466
    class certificate_credentials_st(ctypes.Structure):
 
467
        _fields_ = []
 
468
    certificate_credentials_t = ctypes.POINTER(
 
469
        certificate_credentials_st)
 
470
    certificate_type_t = ctypes.c_int
 
471
    class datum_t(ctypes.Structure):
 
472
        _fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
 
473
                    ('size', ctypes.c_uint)]
 
474
    class openpgp_crt_int(ctypes.Structure):
 
475
        _fields_ = []
 
476
    openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
 
477
    openpgp_crt_fmt_t = ctypes.c_int # gnutls/openpgp.h
 
478
    log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
 
479
    credentials_type_t = ctypes.c_int # 
 
480
    transport_ptr_t = ctypes.c_void_p
 
481
    close_request_t = ctypes.c_int
 
482
    
 
483
    # Exceptions
 
484
    class Error(Exception):
 
485
        # We need to use the class name "GnuTLS" here, since this
 
486
        # exception might be raised from within GnuTLS.__init__,
 
487
        # which is called before the assignment to the "gnutls"
 
488
        # global variable has happened.
 
489
        def __init__(self, message = None, code = None, args=()):
 
490
            # Default usage is by a message string, but if a return
 
491
            # code is passed, convert it to a string with
 
492
            # gnutls.strerror()
 
493
            self.code = code
 
494
            if message is None and code is not None:
 
495
                message = GnuTLS.strerror(code)
 
496
            return super(GnuTLS.Error, self).__init__(
 
497
                message, *args)
 
498
    
 
499
    class CertificateSecurityError(Error):
 
500
        pass
 
501
    
 
502
    # Classes
 
503
    class Credentials(object):
 
504
        def __init__(self):
 
505
            self._c_object = gnutls.certificate_credentials_t()
 
506
            gnutls.certificate_allocate_credentials(
 
507
                ctypes.byref(self._c_object))
 
508
            self.type = gnutls.CRD_CERTIFICATE
 
509
        
 
510
        def __del__(self):
 
511
            gnutls.certificate_free_credentials(self._c_object)
 
512
    
 
513
    class ClientSession(object):
 
514
        def __init__(self, socket, credentials = None):
 
515
            self._c_object = gnutls.session_t()
 
516
            gnutls.init(ctypes.byref(self._c_object), gnutls.CLIENT)
 
517
            gnutls.set_default_priority(self._c_object)
 
518
            gnutls.transport_set_ptr(self._c_object, socket.fileno())
 
519
            gnutls.handshake_set_private_extensions(self._c_object,
 
520
                                                    True)
 
521
            self.socket = socket
 
522
            if credentials is None:
 
523
                credentials = gnutls.Credentials()
 
524
            gnutls.credentials_set(self._c_object, credentials.type,
 
525
                                   ctypes.cast(credentials._c_object,
 
526
                                               ctypes.c_void_p))
 
527
            self.credentials = credentials
 
528
        
 
529
        def __del__(self):
 
530
            gnutls.deinit(self._c_object)
 
531
        
 
532
        def handshake(self):
 
533
            return gnutls.handshake(self._c_object)
 
534
        
 
535
        def send(self, data):
 
536
            data = bytes(data)
 
537
            data_len = len(data)
 
538
            while data_len > 0:
 
539
                data_len -= gnutls.record_send(self._c_object,
 
540
                                               data[-data_len:],
 
541
                                               data_len)
 
542
        
 
543
        def bye(self):
 
544
            return gnutls.bye(self._c_object, gnutls.SHUT_RDWR)
 
545
    
 
546
    # Error handling functions
 
547
    def _error_code(result):
 
548
        """A function to raise exceptions on errors, suitable
 
549
        for the 'restype' attribute on ctypes functions"""
 
550
        if result >= 0:
 
551
            return result
 
552
        if result == gnutls.E_NO_CERTIFICATE_FOUND:
 
553
            raise gnutls.CertificateSecurityError(code = result)
 
554
        raise gnutls.Error(code = result)
 
555
    
 
556
    def _retry_on_error(result, func, arguments):
 
557
        """A function to retry on some errors, suitable
 
558
        for the 'errcheck' attribute on ctypes functions"""
 
559
        while result < 0:
 
560
            if result not in (gnutls.E_INTERRUPTED, gnutls.E_AGAIN):
 
561
                return _error_code(result)
 
562
            result = func(*arguments)
 
563
        return result
 
564
    
 
565
    # Unless otherwise indicated, the function declarations below are
 
566
    # all from the gnutls/gnutls.h C header file.
 
567
    
 
568
    # Functions
 
569
    priority_set_direct = _library.gnutls_priority_set_direct
 
570
    priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
 
571
                                    ctypes.POINTER(ctypes.c_char_p)]
 
572
    priority_set_direct.restype = _error_code
 
573
    
 
574
    init = _library.gnutls_init
 
575
    init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
 
576
    init.restype = _error_code
 
577
    
 
578
    set_default_priority = _library.gnutls_set_default_priority
 
579
    set_default_priority.argtypes = [session_t]
 
580
    set_default_priority.restype = _error_code
 
581
    
 
582
    record_send = _library.gnutls_record_send
 
583
    record_send.argtypes = [session_t, ctypes.c_void_p,
 
584
                            ctypes.c_size_t]
 
585
    record_send.restype = ctypes.c_ssize_t
 
586
    record_send.errcheck = _retry_on_error
 
587
    
 
588
    certificate_allocate_credentials = (
 
589
        _library.gnutls_certificate_allocate_credentials)
 
590
    certificate_allocate_credentials.argtypes = [
 
591
        ctypes.POINTER(certificate_credentials_t)]
 
592
    certificate_allocate_credentials.restype = _error_code
 
593
    
 
594
    certificate_free_credentials = (
 
595
        _library.gnutls_certificate_free_credentials)
 
596
    certificate_free_credentials.argtypes = [certificate_credentials_t]
 
597
    certificate_free_credentials.restype = None
 
598
    
 
599
    handshake_set_private_extensions = (
 
600
        _library.gnutls_handshake_set_private_extensions)
 
601
    handshake_set_private_extensions.argtypes = [session_t,
 
602
                                                 ctypes.c_int]
 
603
    handshake_set_private_extensions.restype = None
 
604
    
 
605
    credentials_set = _library.gnutls_credentials_set
 
606
    credentials_set.argtypes = [session_t, credentials_type_t,
 
607
                                ctypes.c_void_p]
 
608
    credentials_set.restype = _error_code
 
609
    
 
610
    strerror = _library.gnutls_strerror
 
611
    strerror.argtypes = [ctypes.c_int]
 
612
    strerror.restype = ctypes.c_char_p
 
613
    
 
614
    certificate_type_get = _library.gnutls_certificate_type_get
 
615
    certificate_type_get.argtypes = [session_t]
 
616
    certificate_type_get.restype = _error_code
 
617
    
 
618
    certificate_get_peers = _library.gnutls_certificate_get_peers
 
619
    certificate_get_peers.argtypes = [session_t,
 
620
                                      ctypes.POINTER(ctypes.c_uint)]
 
621
    certificate_get_peers.restype = ctypes.POINTER(datum_t)
 
622
    
 
623
    global_set_log_level = _library.gnutls_global_set_log_level
 
624
    global_set_log_level.argtypes = [ctypes.c_int]
 
625
    global_set_log_level.restype = None
 
626
    
 
627
    global_set_log_function = _library.gnutls_global_set_log_function
 
628
    global_set_log_function.argtypes = [log_func]
 
629
    global_set_log_function.restype = None
 
630
    
 
631
    deinit = _library.gnutls_deinit
 
632
    deinit.argtypes = [session_t]
 
633
    deinit.restype = None
 
634
    
 
635
    handshake = _library.gnutls_handshake
 
636
    handshake.argtypes = [session_t]
 
637
    handshake.restype = _error_code
 
638
    handshake.errcheck = _retry_on_error
 
639
    
 
640
    transport_set_ptr = _library.gnutls_transport_set_ptr
 
641
    transport_set_ptr.argtypes = [session_t, transport_ptr_t]
 
642
    transport_set_ptr.restype = None
 
643
    
 
644
    bye = _library.gnutls_bye
 
645
    bye.argtypes = [session_t, close_request_t]
 
646
    bye.restype = _error_code
 
647
    bye.errcheck = _retry_on_error
 
648
    
 
649
    check_version = _library.gnutls_check_version
 
650
    check_version.argtypes = [ctypes.c_char_p]
 
651
    check_version.restype = ctypes.c_char_p
 
652
    
 
653
    # All the function declarations below are from gnutls/openpgp.h
 
654
    
 
655
    openpgp_crt_init = _library.gnutls_openpgp_crt_init
 
656
    openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
 
657
    openpgp_crt_init.restype = _error_code
 
658
    
 
659
    openpgp_crt_import = _library.gnutls_openpgp_crt_import
 
660
    openpgp_crt_import.argtypes = [openpgp_crt_t,
 
661
                                   ctypes.POINTER(datum_t),
 
662
                                   openpgp_crt_fmt_t]
 
663
    openpgp_crt_import.restype = _error_code
 
664
    
 
665
    openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
 
666
    openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
 
667
                                        ctypes.POINTER(ctypes.c_uint)]
 
668
    openpgp_crt_verify_self.restype = _error_code
 
669
    
 
670
    openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
 
671
    openpgp_crt_deinit.argtypes = [openpgp_crt_t]
 
672
    openpgp_crt_deinit.restype = None
 
673
    
 
674
    openpgp_crt_get_fingerprint = (
 
675
        _library.gnutls_openpgp_crt_get_fingerprint)
 
676
    openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
 
677
                                            ctypes.c_void_p,
 
678
                                            ctypes.POINTER(
 
679
                                                ctypes.c_size_t)]
 
680
    openpgp_crt_get_fingerprint.restype = _error_code
 
681
    
 
682
    # Remove non-public functions
 
683
    del _error_code, _retry_on_error
 
684
# Create the global "gnutls" object, simulating a module
 
685
gnutls = GnuTLS()
 
686
 
427
687
def call_pipe(connection,       # : multiprocessing.Connection
428
688
              func, *args, **kwargs):
429
689
    """This function is meant to be called by multiprocessing.Process
831
1091
                           access="r")
832
1092
    def Property_dbus_property(self):
833
1093
        return dbus.Boolean(False)
 
1094
    
 
1095
    See also the DBusObjectWithAnnotations class.
834
1096
    """
835
1097
    
836
1098
    def decorator(func):
858
1120
    pass
859
1121
 
860
1122
 
861
 
class DBusObjectWithProperties(dbus.service.Object):
862
 
    """A D-Bus object with properties.
 
1123
class DBusObjectWithAnnotations(dbus.service.Object):
 
1124
    """A D-Bus object with annotations.
863
1125
    
864
 
    Classes inheriting from this can use the dbus_service_property
865
 
    decorator to expose methods as D-Bus properties.  It exposes the
866
 
    standard Get(), Set(), and GetAll() methods on the D-Bus.
 
1126
    Classes inheriting from this can use the dbus_annotations
 
1127
    decorator to add annotations to methods or signals.
867
1128
    """
868
1129
    
869
1130
    @staticmethod
885
1146
                for name, athing in
886
1147
                inspect.getmembers(cls, self._is_dbus_thing(thing)))
887
1148
    
 
1149
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
 
1150
                         out_signature = "s",
 
1151
                         path_keyword = 'object_path',
 
1152
                         connection_keyword = 'connection')
 
1153
    def Introspect(self, object_path, connection):
 
1154
        """Overloading of standard D-Bus method.
 
1155
        
 
1156
        Inserts annotation tags on methods and signals.
 
1157
        """
 
1158
        xmlstring = dbus.service.Object.Introspect(self, object_path,
 
1159
                                                   connection)
 
1160
        try:
 
1161
            document = xml.dom.minidom.parseString(xmlstring)
 
1162
            
 
1163
            for if_tag in document.getElementsByTagName("interface"):
 
1164
                # Add annotation tags
 
1165
                for typ in ("method", "signal"):
 
1166
                    for tag in if_tag.getElementsByTagName(typ):
 
1167
                        annots = dict()
 
1168
                        for name, prop in (self.
 
1169
                                           _get_all_dbus_things(typ)):
 
1170
                            if (name == tag.getAttribute("name")
 
1171
                                and prop._dbus_interface
 
1172
                                == if_tag.getAttribute("name")):
 
1173
                                annots.update(getattr(
 
1174
                                    prop, "_dbus_annotations", {}))
 
1175
                        for name, value in annots.items():
 
1176
                            ann_tag = document.createElement(
 
1177
                                "annotation")
 
1178
                            ann_tag.setAttribute("name", name)
 
1179
                            ann_tag.setAttribute("value", value)
 
1180
                            tag.appendChild(ann_tag)
 
1181
                # Add interface annotation tags
 
1182
                for annotation, value in dict(
 
1183
                    itertools.chain.from_iterable(
 
1184
                        annotations().items()
 
1185
                        for name, annotations
 
1186
                        in self._get_all_dbus_things("interface")
 
1187
                        if name == if_tag.getAttribute("name")
 
1188
                        )).items():
 
1189
                    ann_tag = document.createElement("annotation")
 
1190
                    ann_tag.setAttribute("name", annotation)
 
1191
                    ann_tag.setAttribute("value", value)
 
1192
                    if_tag.appendChild(ann_tag)
 
1193
                # Fix argument name for the Introspect method itself
 
1194
                if (if_tag.getAttribute("name")
 
1195
                                == dbus.INTROSPECTABLE_IFACE):
 
1196
                    for cn in if_tag.getElementsByTagName("method"):
 
1197
                        if cn.getAttribute("name") == "Introspect":
 
1198
                            for arg in cn.getElementsByTagName("arg"):
 
1199
                                if (arg.getAttribute("direction")
 
1200
                                    == "out"):
 
1201
                                    arg.setAttribute("name",
 
1202
                                                     "xml_data")
 
1203
            xmlstring = document.toxml("utf-8")
 
1204
            document.unlink()
 
1205
        except (AttributeError, xml.dom.DOMException,
 
1206
                xml.parsers.expat.ExpatError) as error:
 
1207
            logger.error("Failed to override Introspection method",
 
1208
                         exc_info=error)
 
1209
        return xmlstring
 
1210
 
 
1211
 
 
1212
class DBusObjectWithProperties(DBusObjectWithAnnotations):
 
1213
    """A D-Bus object with properties.
 
1214
    
 
1215
    Classes inheriting from this can use the dbus_service_property
 
1216
    decorator to expose methods as D-Bus properties.  It exposes the
 
1217
    standard Get(), Set(), and GetAll() methods on the D-Bus.
 
1218
    """
 
1219
    
888
1220
    def _get_dbus_property(self, interface_name, property_name):
889
1221
        """Returns a bound method if one exists which is a D-Bus
890
1222
        property with the specified name and interface.
900
1232
        raise DBusPropertyNotFound("{}:{}.{}".format(
901
1233
            self.dbus_object_path, interface_name, property_name))
902
1234
    
 
1235
    @classmethod
 
1236
    def _get_all_interface_names(cls):
 
1237
        """Get a sequence of all interfaces supported by an object"""
 
1238
        return (name for name in set(getattr(getattr(x, attr),
 
1239
                                             "_dbus_interface", None)
 
1240
                                     for x in (inspect.getmro(cls))
 
1241
                                     for attr in dir(x))
 
1242
                if name is not None)
 
1243
    
903
1244
    @dbus.service.method(dbus.PROPERTIES_IFACE,
904
1245
                         in_signature="ss",
905
1246
                         out_signature="v")
975
1316
        
976
1317
        Inserts property tags and interface annotation tags.
977
1318
        """
978
 
        xmlstring = dbus.service.Object.Introspect(self, object_path,
979
 
                                                   connection)
 
1319
        xmlstring = DBusObjectWithAnnotations.Introspect(self,
 
1320
                                                         object_path,
 
1321
                                                         connection)
980
1322
        try:
981
1323
            document = xml.dom.minidom.parseString(xmlstring)
982
1324
            
995
1337
                            if prop._dbus_interface
996
1338
                            == if_tag.getAttribute("name")):
997
1339
                    if_tag.appendChild(tag)
998
 
                # Add annotation tags
999
 
                for typ in ("method", "signal", "property"):
1000
 
                    for tag in if_tag.getElementsByTagName(typ):
1001
 
                        annots = dict()
1002
 
                        for name, prop in (self.
1003
 
                                           _get_all_dbus_things(typ)):
1004
 
                            if (name == tag.getAttribute("name")
1005
 
                                and prop._dbus_interface
1006
 
                                == if_tag.getAttribute("name")):
1007
 
                                annots.update(getattr(
1008
 
                                    prop, "_dbus_annotations", {}))
1009
 
                        for name, value in annots.items():
1010
 
                            ann_tag = document.createElement(
1011
 
                                "annotation")
1012
 
                            ann_tag.setAttribute("name", name)
1013
 
                            ann_tag.setAttribute("value", value)
1014
 
                            tag.appendChild(ann_tag)
1015
 
                # Add interface annotation tags
1016
 
                for annotation, value in dict(
1017
 
                    itertools.chain.from_iterable(
1018
 
                        annotations().items()
1019
 
                        for name, annotations
1020
 
                        in self._get_all_dbus_things("interface")
1021
 
                        if name == if_tag.getAttribute("name")
1022
 
                        )).items():
1023
 
                    ann_tag = document.createElement("annotation")
1024
 
                    ann_tag.setAttribute("name", annotation)
1025
 
                    ann_tag.setAttribute("value", value)
1026
 
                    if_tag.appendChild(ann_tag)
 
1340
                # Add annotation tags for properties
 
1341
                for tag in if_tag.getElementsByTagName("property"):
 
1342
                    annots = dict()
 
1343
                    for name, prop in self._get_all_dbus_things(
 
1344
                            "property"):
 
1345
                        if (name == tag.getAttribute("name")
 
1346
                            and prop._dbus_interface
 
1347
                            == if_tag.getAttribute("name")):
 
1348
                            annots.update(getattr(
 
1349
                                prop, "_dbus_annotations", {}))
 
1350
                    for name, value in annots.items():
 
1351
                        ann_tag = document.createElement(
 
1352
                            "annotation")
 
1353
                        ann_tag.setAttribute("name", name)
 
1354
                        ann_tag.setAttribute("value", value)
 
1355
                        tag.appendChild(ann_tag)
1027
1356
                # Add the names to the return values for the
1028
1357
                # "org.freedesktop.DBus.Properties" methods
1029
1358
                if (if_tag.getAttribute("name")
1047
1376
                         exc_info=error)
1048
1377
        return xmlstring
1049
1378
 
 
1379
try:
 
1380
    dbus.OBJECT_MANAGER_IFACE
 
1381
except AttributeError:
 
1382
    dbus.OBJECT_MANAGER_IFACE = "org.freedesktop.DBus.ObjectManager"
 
1383
 
 
1384
class DBusObjectWithObjectManager(DBusObjectWithAnnotations):
 
1385
    """A D-Bus object with an ObjectManager.
 
1386
    
 
1387
    Classes inheriting from this exposes the standard
 
1388
    GetManagedObjects call and the InterfacesAdded and
 
1389
    InterfacesRemoved signals on the standard
 
1390
    "org.freedesktop.DBus.ObjectManager" interface.
 
1391
    
 
1392
    Note: No signals are sent automatically; they must be sent
 
1393
    manually.
 
1394
    """
 
1395
    @dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
 
1396
                         out_signature = "a{oa{sa{sv}}}")
 
1397
    def GetManagedObjects(self):
 
1398
        """This function must be overridden"""
 
1399
        raise NotImplementedError()
 
1400
    
 
1401
    @dbus.service.signal(dbus.OBJECT_MANAGER_IFACE,
 
1402
                         signature = "oa{sa{sv}}")
 
1403
    def InterfacesAdded(self, object_path, interfaces_and_properties):
 
1404
        pass
 
1405
    
 
1406
    @dbus.service.signal(dbus.OBJECT_MANAGER_IFACE, signature = "oas")
 
1407
    def InterfacesRemoved(self, object_path, interfaces):
 
1408
        pass
 
1409
    
 
1410
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
 
1411
                         out_signature = "s",
 
1412
                         path_keyword = 'object_path',
 
1413
                         connection_keyword = 'connection')
 
1414
    def Introspect(self, object_path, connection):
 
1415
        """Overloading of standard D-Bus method.
 
1416
        
 
1417
        Override return argument name of GetManagedObjects to be
 
1418
        "objpath_interfaces_and_properties"
 
1419
        """
 
1420
        xmlstring = DBusObjectWithAnnotations.Introspect(self,
 
1421
                                                         object_path,
 
1422
                                                         connection)
 
1423
        try:
 
1424
            document = xml.dom.minidom.parseString(xmlstring)
 
1425
            
 
1426
            for if_tag in document.getElementsByTagName("interface"):
 
1427
                # Fix argument name for the GetManagedObjects method
 
1428
                if (if_tag.getAttribute("name")
 
1429
                                == dbus.OBJECT_MANAGER_IFACE):
 
1430
                    for cn in if_tag.getElementsByTagName("method"):
 
1431
                        if (cn.getAttribute("name")
 
1432
                            == "GetManagedObjects"):
 
1433
                            for arg in cn.getElementsByTagName("arg"):
 
1434
                                if (arg.getAttribute("direction")
 
1435
                                    == "out"):
 
1436
                                    arg.setAttribute(
 
1437
                                        "name",
 
1438
                                        "objpath_interfaces"
 
1439
                                        "_and_properties")
 
1440
            xmlstring = document.toxml("utf-8")
 
1441
            document.unlink()
 
1442
        except (AttributeError, xml.dom.DOMException,
 
1443
                xml.parsers.expat.ExpatError) as error:
 
1444
            logger.error("Failed to override Introspection method",
 
1445
                         exc_info = error)
 
1446
        return xmlstring
1050
1447
 
1051
1448
def datetime_to_dbus(dt, variant_level=0):
1052
1449
    """Convert a UTC datetime.datetime() to a D-Bus type."""
1143
1540
                        func1 and func2 to the "call_both" function
1144
1541
                        outside of its arguments"""
1145
1542
                        
 
1543
                        @functools.wraps(func2)
1146
1544
                        def call_both(*args, **kwargs):
1147
1545
                            """This function will emit two D-Bus
1148
1546
                            signals by calling func1 and func2"""
1149
1547
                            func1(*args, **kwargs)
1150
1548
                            func2(*args, **kwargs)
 
1549
                        # Make wrapper function look like a D-Bus signal
 
1550
                        for name, attr in inspect.getmembers(func2):
 
1551
                            if name.startswith("_dbus_"):
 
1552
                                setattr(call_both, name, attr)
1151
1553
                        
1152
1554
                        return call_both
1153
1555
                    # Create the "call_both" function and add it to
1365
1767
        if exitstatus >= 0:
1366
1768
            # Emit D-Bus signal
1367
1769
            self.CheckerCompleted(dbus.Int16(exitstatus),
1368
 
                                  dbus.Int64(0),
 
1770
                                  # This is specific to GNU libC
 
1771
                                  dbus.Int64(exitstatus << 8),
1369
1772
                                  dbus.String(command))
1370
1773
        else:
1371
1774
            # Emit D-Bus signal
1372
1775
            self.CheckerCompleted(dbus.Int16(-1),
1373
1776
                                  dbus.Int64(
1374
 
                                      self.last_checker_signal),
 
1777
                                      # This is specific to GNU libC
 
1778
                                      (exitstatus << 8)
 
1779
                                      | self.last_checker_signal),
1375
1780
                                  dbus.String(command))
1376
1781
        return ret
1377
1782
    
1454
1859
        self.checked_ok()
1455
1860
    
1456
1861
    # Enable - method
 
1862
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1457
1863
    @dbus.service.method(_interface)
1458
1864
    def Enable(self):
1459
1865
        "D-Bus method"
1460
1866
        self.enable()
1461
1867
    
1462
1868
    # StartChecker - method
 
1869
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1463
1870
    @dbus.service.method(_interface)
1464
1871
    def StartChecker(self):
1465
1872
        "D-Bus method"
1466
1873
        self.start_checker()
1467
1874
    
1468
1875
    # Disable - method
 
1876
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1469
1877
    @dbus.service.method(_interface)
1470
1878
    def Disable(self):
1471
1879
        "D-Bus method"
1472
1880
        self.disable()
1473
1881
    
1474
1882
    # StopChecker - method
 
1883
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1475
1884
    @dbus.service.method(_interface)
1476
1885
    def StopChecker(self):
1477
1886
        self.stop_checker()
1513
1922
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
1514
1923
    
1515
1924
    # Name - property
 
1925
    @dbus_annotations(
 
1926
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1516
1927
    @dbus_service_property(_interface, signature="s", access="read")
1517
1928
    def Name_dbus_property(self):
1518
1929
        return dbus.String(self.name)
1519
1930
    
1520
1931
    # Fingerprint - property
 
1932
    @dbus_annotations(
 
1933
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1521
1934
    @dbus_service_property(_interface, signature="s", access="read")
1522
1935
    def Fingerprint_dbus_property(self):
1523
1936
        return dbus.String(self.fingerprint)
1532
1945
        self.host = str(value)
1533
1946
    
1534
1947
    # Created - property
 
1948
    @dbus_annotations(
 
1949
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1535
1950
    @dbus_service_property(_interface, signature="s", access="read")
1536
1951
    def Created_dbus_property(self):
1537
1952
        return datetime_to_dbus(self.created)
1652
2067
            self.stop_checker()
1653
2068
    
1654
2069
    # ObjectPath - property
 
2070
    @dbus_annotations(
 
2071
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const",
 
2072
         "org.freedesktop.DBus.Deprecated": "true"})
1655
2073
    @dbus_service_property(_interface, signature="o", access="read")
1656
2074
    def ObjectPath_dbus_property(self):
1657
2075
        return self.dbus_object_path # is already a dbus.ObjectPath
1658
2076
    
1659
2077
    # Secret = property
 
2078
    @dbus_annotations(
 
2079
        {"org.freedesktop.DBus.Property.EmitsChangedSignal":
 
2080
         "invalidates"})
1660
2081
    @dbus_service_property(_interface,
1661
2082
                           signature="ay",
1662
2083
                           access="write",
1708
2129
            logger.debug("Pipe FD: %d",
1709
2130
                         self.server.child_pipe.fileno())
1710
2131
            
1711
 
            session = gnutls.connection.ClientSession(
1712
 
                self.request, gnutls.connection .X509Credentials())
1713
 
            
1714
 
            # Note: gnutls.connection.X509Credentials is really a
1715
 
            # generic GnuTLS certificate credentials object so long as
1716
 
            # no X.509 keys are added to it.  Therefore, we can use it
1717
 
            # here despite using OpenPGP certificates.
 
2132
            session = gnutls.ClientSession(self.request)
1718
2133
            
1719
2134
            #priority = ':'.join(("NONE", "+VERS-TLS1.1",
1720
2135
            #                      "+AES-256-CBC", "+SHA1",
1724
2139
            priority = self.server.gnutls_priority
1725
2140
            if priority is None:
1726
2141
                priority = "NORMAL"
1727
 
            gnutls.library.functions.gnutls_priority_set_direct(
1728
 
                session._c_object, priority, None)
 
2142
            gnutls.priority_set_direct(session._c_object, priority,
 
2143
                                       None)
1729
2144
            
1730
2145
            # Start communication using the Mandos protocol
1731
2146
            # Get protocol number
1741
2156
            # Start GnuTLS connection
1742
2157
            try:
1743
2158
                session.handshake()
1744
 
            except gnutls.errors.GNUTLSError as error:
 
2159
            except gnutls.Error as error:
1745
2160
                logger.warning("Handshake failed: %s", error)
1746
2161
                # Do not run session.bye() here: the session is not
1747
2162
                # established.  Just abandon the request.
1753
2168
                try:
1754
2169
                    fpr = self.fingerprint(
1755
2170
                        self.peer_certificate(session))
1756
 
                except (TypeError,
1757
 
                        gnutls.errors.GNUTLSError) as error:
 
2171
                except (TypeError, gnutls.Error) as error:
1758
2172
                    logger.warning("Bad certificate: %s", error)
1759
2173
                    return
1760
2174
                logger.debug("Fingerprint: %s", fpr)
1818
2232
                    else:
1819
2233
                        delay -= time2 - time
1820
2234
                
1821
 
                sent_size = 0
1822
 
                while sent_size < len(client.secret):
1823
 
                    try:
1824
 
                        sent = session.send(client.secret[sent_size:])
1825
 
                    except gnutls.errors.GNUTLSError as error:
1826
 
                        logger.warning("gnutls send failed",
1827
 
                                       exc_info=error)
1828
 
                        return
1829
 
                    logger.debug("Sent: %d, remaining: %d", sent,
1830
 
                                 len(client.secret) - (sent_size
1831
 
                                                       + sent))
1832
 
                    sent_size += sent
 
2235
                try:
 
2236
                    session.send(client.secret)
 
2237
                except gnutls.Error as error:
 
2238
                    logger.warning("gnutls send failed",
 
2239
                                   exc_info = error)
 
2240
                    return
1833
2241
                
1834
2242
                logger.info("Sending secret to %s", client.name)
1835
2243
                # bump the timeout using extended_timeout
1843
2251
                    client.approvals_pending -= 1
1844
2252
                try:
1845
2253
                    session.bye()
1846
 
                except gnutls.errors.GNUTLSError as error:
 
2254
                except gnutls.Error as error:
1847
2255
                    logger.warning("GnuTLS bye failed",
1848
2256
                                   exc_info=error)
1849
2257
    
1851
2259
    def peer_certificate(session):
1852
2260
        "Return the peer's OpenPGP certificate as a bytestring"
1853
2261
        # If not an OpenPGP certificate...
1854
 
        if (gnutls.library.functions.gnutls_certificate_type_get(
1855
 
                session._c_object)
1856
 
            != gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1857
 
            # ...do the normal thing
1858
 
            return session.peer_certificate
 
2262
        if (gnutls.certificate_type_get(session._c_object)
 
2263
            != gnutls.CRT_OPENPGP):
 
2264
            # ...return invalid data
 
2265
            return b""
1859
2266
        list_size = ctypes.c_uint(1)
1860
 
        cert_list = (gnutls.library.functions
1861
 
                     .gnutls_certificate_get_peers
 
2267
        cert_list = (gnutls.certificate_get_peers
1862
2268
                     (session._c_object, ctypes.byref(list_size)))
1863
2269
        if not bool(cert_list) and list_size.value != 0:
1864
 
            raise gnutls.errors.GNUTLSError("error getting peer"
1865
 
                                            " certificate")
 
2270
            raise gnutls.Error("error getting peer certificate")
1866
2271
        if list_size.value == 0:
1867
2272
            return None
1868
2273
        cert = cert_list[0]
1872
2277
    def fingerprint(openpgp):
1873
2278
        "Convert an OpenPGP bytestring to a hexdigit fingerprint"
1874
2279
        # New GnuTLS "datum" with the OpenPGP public key
1875
 
        datum = gnutls.library.types.gnutls_datum_t(
 
2280
        datum = gnutls.datum_t(
1876
2281
            ctypes.cast(ctypes.c_char_p(openpgp),
1877
2282
                        ctypes.POINTER(ctypes.c_ubyte)),
1878
2283
            ctypes.c_uint(len(openpgp)))
1879
2284
        # New empty GnuTLS certificate
1880
 
        crt = gnutls.library.types.gnutls_openpgp_crt_t()
1881
 
        gnutls.library.functions.gnutls_openpgp_crt_init(
1882
 
            ctypes.byref(crt))
 
2285
        crt = gnutls.openpgp_crt_t()
 
2286
        gnutls.openpgp_crt_init(ctypes.byref(crt))
1883
2287
        # Import the OpenPGP public key into the certificate
1884
 
        gnutls.library.functions.gnutls_openpgp_crt_import(
1885
 
            crt, ctypes.byref(datum),
1886
 
            gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
 
2288
        gnutls.openpgp_crt_import(crt, ctypes.byref(datum),
 
2289
                                  gnutls.OPENPGP_FMT_RAW)
1887
2290
        # Verify the self signature in the key
1888
2291
        crtverify = ctypes.c_uint()
1889
 
        gnutls.library.functions.gnutls_openpgp_crt_verify_self(
1890
 
            crt, 0, ctypes.byref(crtverify))
 
2292
        gnutls.openpgp_crt_verify_self(crt, 0,
 
2293
                                       ctypes.byref(crtverify))
1891
2294
        if crtverify.value != 0:
1892
 
            gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1893
 
            raise gnutls.errors.CertificateSecurityError(
1894
 
                "Verify failed")
 
2295
            gnutls.openpgp_crt_deinit(crt)
 
2296
            raise gnutls.CertificateSecurityError("Verify failed")
1895
2297
        # New buffer for the fingerprint
1896
2298
        buf = ctypes.create_string_buffer(20)
1897
2299
        buf_len = ctypes.c_size_t()
1898
2300
        # Get the fingerprint from the certificate into the buffer
1899
 
        gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint(
1900
 
            crt, ctypes.byref(buf), ctypes.byref(buf_len))
 
2301
        gnutls.openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
 
2302
                                           ctypes.byref(buf_len))
1901
2303
        # Deinit the certificate
1902
 
        gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
 
2304
        gnutls.openpgp_crt_deinit(crt)
1903
2305
        # Convert the buffer to a Python bytestring
1904
2306
        fpr = ctypes.string_at(buf, buf_len.value)
1905
2307
        # Convert the bytestring to hexadecimal notation
2386
2788
                        "debug": "False",
2387
2789
                        "priority":
2388
2790
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2389
 
                        ":+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
 
2791
                        ":+SIGN-DSA-SHA256",
2390
2792
                        "servicename": "Mandos",
2391
2793
                        "use_dbus": "True",
2392
2794
                        "use_ipv6": "True",
2531
2933
        
2532
2934
        # "Use a log level over 10 to enable all debugging options."
2533
2935
        # - GnuTLS manual
2534
 
        gnutls.library.functions.gnutls_global_set_log_level(11)
 
2936
        gnutls.global_set_log_level(11)
2535
2937
        
2536
 
        @gnutls.library.types.gnutls_log_func
 
2938
        @gnutls.log_func
2537
2939
        def debug_gnutls(level, string):
2538
2940
            logger.debug("GnuTLS: %s", string[:-1])
2539
2941
        
2540
 
        gnutls.library.functions.gnutls_global_set_log_function(
2541
 
            debug_gnutls)
 
2942
        gnutls.global_set_log_function(debug_gnutls)
2542
2943
        
2543
2944
        # Redirect stdin so all checkers get /dev/null
2544
2945
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2723
3124
        
2724
3125
        @alternate_dbus_interfaces(
2725
3126
            { "se.recompile.Mandos": "se.bsnet.fukt.Mandos" })
2726
 
        class MandosDBusService(DBusObjectWithProperties):
 
3127
        class MandosDBusService(DBusObjectWithObjectManager):
2727
3128
            """A D-Bus proxy object"""
2728
3129
            
2729
3130
            def __init__(self):
2731
3132
            
2732
3133
            _interface = "se.recompile.Mandos"
2733
3134
            
2734
 
            @dbus_interface_annotations(_interface)
2735
 
            def _foo(self):
2736
 
                return {
2737
 
                    "org.freedesktop.DBus.Property.EmitsChangedSignal":
2738
 
                    "false" }
2739
 
            
2740
3135
            @dbus.service.signal(_interface, signature="o")
2741
3136
            def ClientAdded(self, objpath):
2742
3137
                "D-Bus signal"
2747
3142
                "D-Bus signal"
2748
3143
                pass
2749
3144
            
 
3145
            @dbus_annotations({"org.freedesktop.DBus.Deprecated":
 
3146
                               "true"})
2750
3147
            @dbus.service.signal(_interface, signature="os")
2751
3148
            def ClientRemoved(self, objpath, name):
2752
3149
                "D-Bus signal"
2753
3150
                pass
2754
3151
            
 
3152
            @dbus_annotations({"org.freedesktop.DBus.Deprecated":
 
3153
                               "true"})
2755
3154
            @dbus.service.method(_interface, out_signature="ao")
2756
3155
            def GetAllClients(self):
2757
3156
                "D-Bus method"
2758
3157
                return dbus.Array(c.dbus_object_path for c in
2759
3158
                                  tcp_server.clients.itervalues())
2760
3159
            
 
3160
            @dbus_annotations({"org.freedesktop.DBus.Deprecated":
 
3161
                               "true"})
2761
3162
            @dbus.service.method(_interface,
2762
3163
                                 out_signature="a{oa{sv}}")
2763
3164
            def GetAllClientsWithProperties(self):
2764
3165
                "D-Bus method"
2765
3166
                return dbus.Dictionary(
2766
 
                    { c.dbus_object_path: c.GetAll("")
 
3167
                    { c.dbus_object_path: c.GetAll(
 
3168
                        "se.recompile.Mandos.Client")
2767
3169
                      for c in tcp_server.clients.itervalues() },
2768
3170
                    signature="oa{sv}")
2769
3171
            
2774
3176
                    if c.dbus_object_path == object_path:
2775
3177
                        del tcp_server.clients[c.name]
2776
3178
                        c.remove_from_connection()
2777
 
                        # Don't signal anything except ClientRemoved
 
3179
                        # Don't signal the disabling
2778
3180
                        c.disable(quiet=True)
2779
 
                        # Emit D-Bus signal
2780
 
                        self.ClientRemoved(object_path, c.name)
 
3181
                        # Emit D-Bus signal for removal
 
3182
                        self.client_removed_signal(c)
2781
3183
                        return
2782
3184
                raise KeyError(object_path)
2783
3185
            
2784
3186
            del _interface
 
3187
            
 
3188
            @dbus.service.method(dbus.OBJECT_MANAGER_IFACE,
 
3189
                                 out_signature = "a{oa{sa{sv}}}")
 
3190
            def GetManagedObjects(self):
 
3191
                """D-Bus method"""
 
3192
                return dbus.Dictionary(
 
3193
                    { client.dbus_object_path:
 
3194
                      dbus.Dictionary(
 
3195
                          { interface: client.GetAll(interface)
 
3196
                            for interface in
 
3197
                                 client._get_all_interface_names()})
 
3198
                      for client in tcp_server.clients.values()})
 
3199
            
 
3200
            def client_added_signal(self, client):
 
3201
                """Send the new standard signal and the old signal"""
 
3202
                if use_dbus:
 
3203
                    # New standard signal
 
3204
                    self.InterfacesAdded(
 
3205
                        client.dbus_object_path,
 
3206
                        dbus.Dictionary(
 
3207
                            { interface: client.GetAll(interface)
 
3208
                              for interface in
 
3209
                              client._get_all_interface_names()}))
 
3210
                    # Old signal
 
3211
                    self.ClientAdded(client.dbus_object_path)
 
3212
            
 
3213
            def client_removed_signal(self, client):
 
3214
                """Send the new standard signal and the old signal"""
 
3215
                if use_dbus:
 
3216
                    # New standard signal
 
3217
                    self.InterfacesRemoved(
 
3218
                        client.dbus_object_path,
 
3219
                        client._get_all_interface_names())
 
3220
                    # Old signal
 
3221
                    self.ClientRemoved(client.dbus_object_path,
 
3222
                                       client.name)
2785
3223
        
2786
3224
        mandos_dbus_service = MandosDBusService()
2787
3225
    
2852
3290
            name, client = tcp_server.clients.popitem()
2853
3291
            if use_dbus:
2854
3292
                client.remove_from_connection()
2855
 
            # Don't signal anything except ClientRemoved
 
3293
            # Don't signal the disabling
2856
3294
            client.disable(quiet=True)
 
3295
            # Emit D-Bus signal for removal
2857
3296
            if use_dbus:
2858
 
                # Emit D-Bus signal
2859
 
                mandos_dbus_service.ClientRemoved(
2860
 
                    client.dbus_object_path, client.name)
 
3297
                mandos_dbus_service.client_removed_signal(client)
2861
3298
        client_settings.clear()
2862
3299
    
2863
3300
    atexit.register(cleanup)
2864
3301
    
2865
3302
    for client in tcp_server.clients.itervalues():
2866
3303
        if use_dbus:
2867
 
            # Emit D-Bus signal
2868
 
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
 
3304
            # Emit D-Bus signal for adding
 
3305
            mandos_dbus_service.client_added_signal(client)
2869
3306
        # Need to initiate checking of clients
2870
3307
        if client.enabled:
2871
3308
            client.init_checker()