/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: 2019-08-24 14:52:59 UTC
  • Revision ID: teddy@recompile.se-20190824145259-ifatm1r12kyp4z25
Server: Use new GLib.io_add_watch() call signature

* INSTALL: Increase version requirement of PyGObject to 3.8.
* mandos: When calling GLib.io_add_watch(), always pass priority as
          the second argument, which is supported by PyGObject 3.8 or
          later.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!/usr/bin/python
2
 
# -*- mode: python; coding: utf-8 -*-
 
2
# -*- mode: python; after-save-hook: (lambda () (let ((command (if (fboundp 'file-local-name) (file-local-name (buffer-file-name)) (or (file-remote-p (buffer-file-name) 'localname) (buffer-file-name))))) (if (= (progn (if (get-buffer "*Test*") (kill-buffer "*Test*")) (process-file-shell-command (format "%s --check" (shell-quote-argument command)) nil "*Test*")) 0) (let ((w (get-buffer-window "*Test*"))) (if w (delete-window w))) (progn (with-current-buffer "*Test*" (compilation-mode)) (display-buffer "*Test*" '(display-buffer-in-side-window)))))); coding: utf-8 -*-
3
3
#
4
4
# Mandos server - give out binary blobs to connecting clients.
5
5
#
11
11
# "AvahiService" class, and some lines in "main".
12
12
#
13
13
# Everything else is
14
 
# Copyright © 2008-2018 Teddy Hogeborn
15
 
# Copyright © 2008-2018 Björn Påhlsson
 
14
# Copyright © 2008-2019 Teddy Hogeborn
 
15
# Copyright © 2008-2019 Björn Påhlsson
16
16
#
17
17
# This file is part of Mandos.
18
18
#
77
77
import itertools
78
78
import collections
79
79
import codecs
 
80
import unittest
80
81
 
81
82
import dbus
82
83
import dbus.service
 
84
import gi
83
85
from gi.repository import GLib
84
86
from dbus.mainloop.glib import DBusGMainLoop
85
87
import ctypes
87
89
import xml.dom.minidom
88
90
import inspect
89
91
 
 
92
if sys.version_info.major == 2:
 
93
    __metaclass__ = type
 
94
 
 
95
# Show warnings by default
 
96
if not sys.warnoptions:
 
97
    import warnings
 
98
    warnings.simplefilter("default")
 
99
 
90
100
# Try to find the value of SO_BINDTODEVICE:
91
101
try:
92
102
    # This is where SO_BINDTODEVICE is in Python 3.3 (or 3.4?) and
115
125
if sys.version_info.major == 2:
116
126
    str = unicode
117
127
 
118
 
version = "1.7.17"
 
128
if sys.version_info < (3, 2):
 
129
    configparser.Configparser = configparser.SafeConfigParser
 
130
 
 
131
version = "1.8.8"
119
132
stored_state_file = "clients.pickle"
120
133
 
121
134
logger = logging.getLogger()
 
135
logging.captureWarnings(True)   # Show warnings via the logging system
122
136
syslogger = None
123
137
 
124
138
try:
179
193
    pass
180
194
 
181
195
 
182
 
class PGPEngine(object):
 
196
class PGPEngine:
183
197
    """A simple class for OpenPGP symmetric encryption & decryption"""
184
198
 
185
199
    def __init__(self):
275
289
 
276
290
 
277
291
# Pretend that we have an Avahi module
278
 
class Avahi(object):
279
 
    """This isn't so much a class as it is a module-like namespace.
280
 
    It is instantiated once, and simulates having an Avahi module."""
 
292
class avahi:
 
293
    """This isn't so much a class as it is a module-like namespace."""
281
294
    IF_UNSPEC = -1               # avahi-common/address.h
282
295
    PROTO_UNSPEC = -1            # avahi-common/address.h
283
296
    PROTO_INET = 0               # avahi-common/address.h
287
300
    DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
288
301
    DBUS_PATH_SERVER = "/"
289
302
 
290
 
    def string_array_to_txt_array(self, t):
 
303
    @staticmethod
 
304
    def string_array_to_txt_array(t):
291
305
        return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
292
306
                           for s in t), signature="ay")
293
307
    ENTRY_GROUP_ESTABLISHED = 2  # avahi-common/defs.h
298
312
    SERVER_RUNNING = 2           # avahi-common/defs.h
299
313
    SERVER_COLLISION = 3         # avahi-common/defs.h
300
314
    SERVER_FAILURE = 4           # avahi-common/defs.h
301
 
avahi = Avahi()
302
315
 
303
316
 
304
317
class AvahiError(Exception):
316
329
    pass
317
330
 
318
331
 
319
 
class AvahiService(object):
 
332
class AvahiService:
320
333
    """An Avahi (Zeroconf) service.
321
334
 
322
335
    Attributes:
504
517
 
505
518
 
506
519
# Pretend that we have a GnuTLS module
507
 
class GnuTLS(object):
508
 
    """This isn't so much a class as it is a module-like namespace.
509
 
    It is instantiated once, and simulates having a GnuTLS module."""
 
520
class gnutls:
 
521
    """This isn't so much a class as it is a module-like namespace."""
510
522
 
511
523
    library = ctypes.util.find_library("gnutls")
512
524
    if library is None:
513
525
        library = ctypes.util.find_library("gnutls-deb0")
514
526
    _library = ctypes.cdll.LoadLibrary(library)
515
527
    del library
516
 
    _need_version = b"3.3.0"
517
 
 
518
 
    def __init__(self):
519
 
        # Need to use "self" here, since this method is called before
520
 
        # the assignment to the "gnutls" global variable happens.
521
 
        if self.check_version(self._need_version) is None:
522
 
            raise self.Error("Needs GnuTLS {} or later"
523
 
                             .format(self._need_version))
524
528
 
525
529
    # Unless otherwise indicated, the constants and types below are
526
530
    # all from the gnutls/gnutls.h C header file.
530
534
    E_INTERRUPTED = -52
531
535
    E_AGAIN = -28
532
536
    CRT_OPENPGP = 2
 
537
    CRT_RAWPK = 3
533
538
    CLIENT = 2
534
539
    SHUT_RDWR = 0
535
540
    CRD_CERTIFICATE = 1
536
541
    E_NO_CERTIFICATE_FOUND = -49
 
542
    X509_FMT_DER = 0
 
543
    NO_TICKETS = 1<<10
 
544
    ENABLE_RAWPK = 1<<18
 
545
    CTYPE_PEERS = 3
 
546
    KEYID_USE_SHA256 = 1        # gnutls/x509.h
537
547
    OPENPGP_FMT_RAW = 0         # gnutls/openpgp.h
538
548
 
539
549
    # Types
562
572
 
563
573
    # Exceptions
564
574
    class Error(Exception):
565
 
        # We need to use the class name "GnuTLS" here, since this
566
 
        # exception might be raised from within GnuTLS.__init__,
567
 
        # which is called before the assignment to the "gnutls"
568
 
        # global variable has happened.
569
575
        def __init__(self, message=None, code=None, args=()):
570
576
            # Default usage is by a message string, but if a return
571
577
            # code is passed, convert it to a string with
572
578
            # gnutls.strerror()
573
579
            self.code = code
574
580
            if message is None and code is not None:
575
 
                message = GnuTLS.strerror(code)
576
 
            return super(GnuTLS.Error, self).__init__(
 
581
                message = gnutls.strerror(code)
 
582
            return super(gnutls.Error, self).__init__(
577
583
                message, *args)
578
584
 
579
585
    class CertificateSecurityError(Error):
580
586
        pass
581
587
 
582
588
    # Classes
583
 
    class Credentials(object):
 
589
    class Credentials:
584
590
        def __init__(self):
585
591
            self._c_object = gnutls.certificate_credentials_t()
586
592
            gnutls.certificate_allocate_credentials(
590
596
        def __del__(self):
591
597
            gnutls.certificate_free_credentials(self._c_object)
592
598
 
593
 
    class ClientSession(object):
 
599
    class ClientSession:
594
600
        def __init__(self, socket, credentials=None):
595
601
            self._c_object = gnutls.session_t()
596
 
            gnutls.init(ctypes.byref(self._c_object), gnutls.CLIENT)
 
602
            gnutls_flags = gnutls.CLIENT
 
603
            if gnutls.check_version(b"3.5.6"):
 
604
                gnutls_flags |= gnutls.NO_TICKETS
 
605
            if gnutls.has_rawpk:
 
606
                gnutls_flags |= gnutls.ENABLE_RAWPK
 
607
            gnutls.init(ctypes.byref(self._c_object), gnutls_flags)
 
608
            del gnutls_flags
597
609
            gnutls.set_default_priority(self._c_object)
598
610
            gnutls.transport_set_ptr(self._c_object, socket.fileno())
599
611
            gnutls.handshake_set_private_extensions(self._c_object,
731
743
    check_version.argtypes = [ctypes.c_char_p]
732
744
    check_version.restype = ctypes.c_char_p
733
745
 
734
 
    # All the function declarations below are from gnutls/openpgp.h
735
 
 
736
 
    openpgp_crt_init = _library.gnutls_openpgp_crt_init
737
 
    openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
738
 
    openpgp_crt_init.restype = _error_code
739
 
 
740
 
    openpgp_crt_import = _library.gnutls_openpgp_crt_import
741
 
    openpgp_crt_import.argtypes = [openpgp_crt_t,
742
 
                                   ctypes.POINTER(datum_t),
743
 
                                   openpgp_crt_fmt_t]
744
 
    openpgp_crt_import.restype = _error_code
745
 
 
746
 
    openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
747
 
    openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
748
 
                                        ctypes.POINTER(ctypes.c_uint)]
749
 
    openpgp_crt_verify_self.restype = _error_code
750
 
 
751
 
    openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
752
 
    openpgp_crt_deinit.argtypes = [openpgp_crt_t]
753
 
    openpgp_crt_deinit.restype = None
754
 
 
755
 
    openpgp_crt_get_fingerprint = (
756
 
        _library.gnutls_openpgp_crt_get_fingerprint)
757
 
    openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
758
 
                                            ctypes.c_void_p,
759
 
                                            ctypes.POINTER(
760
 
                                                ctypes.c_size_t)]
761
 
    openpgp_crt_get_fingerprint.restype = _error_code
 
746
    _need_version = b"3.3.0"
 
747
    if check_version(_need_version) is None:
 
748
        raise self.Error("Needs GnuTLS {} or later"
 
749
                         .format(_need_version))
 
750
 
 
751
    _tls_rawpk_version = b"3.6.6"
 
752
    has_rawpk = bool(check_version(_tls_rawpk_version))
 
753
 
 
754
    if has_rawpk:
 
755
        # Types
 
756
        class pubkey_st(ctypes.Structure):
 
757
            _fields = []
 
758
        pubkey_t = ctypes.POINTER(pubkey_st)
 
759
 
 
760
        x509_crt_fmt_t = ctypes.c_int
 
761
 
 
762
        # All the function declarations below are from gnutls/abstract.h
 
763
        pubkey_init = _library.gnutls_pubkey_init
 
764
        pubkey_init.argtypes = [ctypes.POINTER(pubkey_t)]
 
765
        pubkey_init.restype = _error_code
 
766
 
 
767
        pubkey_import = _library.gnutls_pubkey_import
 
768
        pubkey_import.argtypes = [pubkey_t, ctypes.POINTER(datum_t),
 
769
                                  x509_crt_fmt_t]
 
770
        pubkey_import.restype = _error_code
 
771
 
 
772
        pubkey_get_key_id = _library.gnutls_pubkey_get_key_id
 
773
        pubkey_get_key_id.argtypes = [pubkey_t, ctypes.c_int,
 
774
                                      ctypes.POINTER(ctypes.c_ubyte),
 
775
                                      ctypes.POINTER(ctypes.c_size_t)]
 
776
        pubkey_get_key_id.restype = _error_code
 
777
 
 
778
        pubkey_deinit = _library.gnutls_pubkey_deinit
 
779
        pubkey_deinit.argtypes = [pubkey_t]
 
780
        pubkey_deinit.restype = None
 
781
    else:
 
782
        # All the function declarations below are from gnutls/openpgp.h
 
783
 
 
784
        openpgp_crt_init = _library.gnutls_openpgp_crt_init
 
785
        openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
 
786
        openpgp_crt_init.restype = _error_code
 
787
 
 
788
        openpgp_crt_import = _library.gnutls_openpgp_crt_import
 
789
        openpgp_crt_import.argtypes = [openpgp_crt_t,
 
790
                                       ctypes.POINTER(datum_t),
 
791
                                       openpgp_crt_fmt_t]
 
792
        openpgp_crt_import.restype = _error_code
 
793
 
 
794
        openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
 
795
        openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
 
796
                                            ctypes.POINTER(ctypes.c_uint)]
 
797
        openpgp_crt_verify_self.restype = _error_code
 
798
 
 
799
        openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
 
800
        openpgp_crt_deinit.argtypes = [openpgp_crt_t]
 
801
        openpgp_crt_deinit.restype = None
 
802
 
 
803
        openpgp_crt_get_fingerprint = (
 
804
            _library.gnutls_openpgp_crt_get_fingerprint)
 
805
        openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
 
806
                                                ctypes.c_void_p,
 
807
                                                ctypes.POINTER(
 
808
                                                    ctypes.c_size_t)]
 
809
        openpgp_crt_get_fingerprint.restype = _error_code
 
810
 
 
811
    if check_version(b"3.6.4"):
 
812
        certificate_type_get2 = _library.gnutls_certificate_type_get2
 
813
        certificate_type_get2.argtypes = [session_t, ctypes.c_int]
 
814
        certificate_type_get2.restype = _error_code
762
815
 
763
816
    # Remove non-public functions
764
817
    del _error_code, _retry_on_error
765
 
# Create the global "gnutls" object, simulating a module
766
 
gnutls = GnuTLS()
767
818
 
768
819
 
769
820
def call_pipe(connection,       # : multiprocessing.Connection
777
828
    connection.close()
778
829
 
779
830
 
780
 
class Client(object):
 
831
class Client:
781
832
    """A representation of a client host served by this server.
782
833
 
783
834
    Attributes:
784
835
    approved:   bool(); 'None' if not yet approved/disapproved
785
836
    approval_delay: datetime.timedelta(); Time to wait for approval
786
837
    approval_duration: datetime.timedelta(); Duration of one approval
787
 
    checker:    subprocess.Popen(); a running checker process used
788
 
                                    to see if the client lives.
789
 
                                    'None' if no process is running.
 
838
    checker: multiprocessing.Process(); a running checker process used
 
839
             to see if the client lives. 'None' if no process is
 
840
             running.
790
841
    checker_callback_tag: a GLib event source tag, or None
791
842
    checker_command: string; External command which is run to check
792
843
                     if client lives.  %() expansions are done at
800
851
    disable_initiator_tag: a GLib event source tag, or None
801
852
    enabled:    bool()
802
853
    fingerprint: string (40 or 32 hexadecimal digits); used to
803
 
                 uniquely identify the client
 
854
                 uniquely identify an OpenPGP client
 
855
    key_id: string (64 hexadecimal digits); used to uniquely identify
 
856
            a client using raw public keys
804
857
    host:       string; available for use by the checker command
805
858
    interval:   datetime.timedelta(); How often to start a new checker
806
859
    last_approval_request: datetime.datetime(); (UTC) or None
824
877
    """
825
878
 
826
879
    runtime_expansions = ("approval_delay", "approval_duration",
827
 
                          "created", "enabled", "expires",
 
880
                          "created", "enabled", "expires", "key_id",
828
881
                          "fingerprint", "host", "interval",
829
882
                          "last_approval_request", "last_checked_ok",
830
883
                          "last_enabled", "name", "timeout")
860
913
            client["enabled"] = config.getboolean(client_name,
861
914
                                                  "enabled")
862
915
 
863
 
            # Uppercase and remove spaces from fingerprint for later
864
 
            # comparison purposes with return value from the
865
 
            # fingerprint() function
 
916
            # Uppercase and remove spaces from key_id and fingerprint
 
917
            # for later comparison purposes with return value from the
 
918
            # key_id() and fingerprint() functions
 
919
            client["key_id"] = (section.get("key_id", "").upper()
 
920
                                .replace(" ", ""))
866
921
            client["fingerprint"] = (section["fingerprint"].upper()
867
922
                                     .replace(" ", ""))
868
923
            if "secret" in section:
912
967
            self.expires = None
913
968
 
914
969
        logger.debug("Creating client %r", self.name)
 
970
        logger.debug("  Key ID: %s", self.key_id)
915
971
        logger.debug("  Fingerprint: %s", self.fingerprint)
916
972
        self.created = settings.get("created",
917
973
                                    datetime.datetime.utcnow())
994
1050
    def checker_callback(self, source, condition, connection,
995
1051
                         command):
996
1052
        """The checker has completed, so take appropriate actions."""
997
 
        self.checker_callback_tag = None
998
 
        self.checker = None
999
1053
        # Read return code from connection (see call_pipe)
1000
1054
        returncode = connection.recv()
1001
1055
        connection.close()
 
1056
        self.checker.join()
 
1057
        self.checker_callback_tag = None
 
1058
        self.checker = None
1002
1059
 
1003
1060
        if returncode >= 0:
1004
1061
            self.last_checker_status = returncode
1093
1150
                kwargs=popen_args)
1094
1151
            self.checker.start()
1095
1152
            self.checker_callback_tag = GLib.io_add_watch(
1096
 
                pipe[0].fileno(), GLib.IO_IN,
 
1153
                pipe[0].fileno(), GLib.PRIORITY_DEFAULT, GLib.IO_IN,
1097
1154
                self.checker_callback, pipe[0], command)
1098
1155
        # Re-run this periodically if run by GLib.timeout_add
1099
1156
        return True
1999
2056
    def Name_dbus_property(self):
2000
2057
        return dbus.String(self.name)
2001
2058
 
 
2059
    # KeyID - property
 
2060
    @dbus_annotations(
 
2061
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
 
2062
    @dbus_service_property(_interface, signature="s", access="read")
 
2063
    def KeyID_dbus_property(self):
 
2064
        return dbus.String(self.key_id)
 
2065
 
2002
2066
    # Fingerprint - property
2003
2067
    @dbus_annotations(
2004
2068
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
2159
2223
    del _interface
2160
2224
 
2161
2225
 
2162
 
class ProxyClient(object):
2163
 
    def __init__(self, child_pipe, fpr, address):
 
2226
class ProxyClient:
 
2227
    def __init__(self, child_pipe, key_id, fpr, address):
2164
2228
        self._pipe = child_pipe
2165
 
        self._pipe.send(('init', fpr, address))
 
2229
        self._pipe.send(('init', key_id, fpr, address))
2166
2230
        if not self._pipe.recv():
2167
 
            raise KeyError(fpr)
 
2231
            raise KeyError(key_id or fpr)
2168
2232
 
2169
2233
    def __getattribute__(self, name):
2170
2234
        if name == '_pipe':
2237
2301
 
2238
2302
            approval_required = False
2239
2303
            try:
2240
 
                try:
2241
 
                    fpr = self.fingerprint(
2242
 
                        self.peer_certificate(session))
2243
 
                except (TypeError, gnutls.Error) as error:
2244
 
                    logger.warning("Bad certificate: %s", error)
2245
 
                    return
2246
 
                logger.debug("Fingerprint: %s", fpr)
2247
 
 
2248
 
                try:
2249
 
                    client = ProxyClient(child_pipe, fpr,
 
2304
                if gnutls.has_rawpk:
 
2305
                    fpr = b""
 
2306
                    try:
 
2307
                        key_id = self.key_id(
 
2308
                            self.peer_certificate(session))
 
2309
                    except (TypeError, gnutls.Error) as error:
 
2310
                        logger.warning("Bad certificate: %s", error)
 
2311
                        return
 
2312
                    logger.debug("Key ID: %s", key_id)
 
2313
 
 
2314
                else:
 
2315
                    key_id = b""
 
2316
                    try:
 
2317
                        fpr = self.fingerprint(
 
2318
                            self.peer_certificate(session))
 
2319
                    except (TypeError, gnutls.Error) as error:
 
2320
                        logger.warning("Bad certificate: %s", error)
 
2321
                        return
 
2322
                    logger.debug("Fingerprint: %s", fpr)
 
2323
 
 
2324
                try:
 
2325
                    client = ProxyClient(child_pipe, key_id, fpr,
2250
2326
                                         self.client_address)
2251
2327
                except KeyError:
2252
2328
                    return
2329
2405
 
2330
2406
    @staticmethod
2331
2407
    def peer_certificate(session):
2332
 
        "Return the peer's OpenPGP certificate as a bytestring"
2333
 
        # If not an OpenPGP certificate...
2334
 
        if (gnutls.certificate_type_get(session._c_object)
2335
 
            != gnutls.CRT_OPENPGP):
 
2408
        "Return the peer's certificate as a bytestring"
 
2409
        try:
 
2410
            cert_type = gnutls.certificate_type_get2(session._c_object,
 
2411
                                                     gnutls.CTYPE_PEERS)
 
2412
        except AttributeError:
 
2413
            cert_type = gnutls.certificate_type_get(session._c_object)
 
2414
        if gnutls.has_rawpk:
 
2415
            valid_cert_types = frozenset((gnutls.CRT_RAWPK,))
 
2416
        else:
 
2417
            valid_cert_types = frozenset((gnutls.CRT_OPENPGP,))
 
2418
        # If not a valid certificate type...
 
2419
        if cert_type not in valid_cert_types:
 
2420
            logger.info("Cert type %r not in %r", cert_type,
 
2421
                        valid_cert_types)
2336
2422
            # ...return invalid data
2337
2423
            return b""
2338
2424
        list_size = ctypes.c_uint(1)
2346
2432
        return ctypes.string_at(cert.data, cert.size)
2347
2433
 
2348
2434
    @staticmethod
 
2435
    def key_id(certificate):
 
2436
        "Convert a certificate bytestring to a hexdigit key ID"
 
2437
        # New GnuTLS "datum" with the public key
 
2438
        datum = gnutls.datum_t(
 
2439
            ctypes.cast(ctypes.c_char_p(certificate),
 
2440
                        ctypes.POINTER(ctypes.c_ubyte)),
 
2441
            ctypes.c_uint(len(certificate)))
 
2442
        # XXX all these need to be created in the gnutls "module"
 
2443
        # New empty GnuTLS certificate
 
2444
        pubkey = gnutls.pubkey_t()
 
2445
        gnutls.pubkey_init(ctypes.byref(pubkey))
 
2446
        # Import the raw public key into the certificate
 
2447
        gnutls.pubkey_import(pubkey,
 
2448
                             ctypes.byref(datum),
 
2449
                             gnutls.X509_FMT_DER)
 
2450
        # New buffer for the key ID
 
2451
        buf = ctypes.create_string_buffer(32)
 
2452
        buf_len = ctypes.c_size_t(len(buf))
 
2453
        # Get the key ID from the raw public key into the buffer
 
2454
        gnutls.pubkey_get_key_id(pubkey,
 
2455
                                 gnutls.KEYID_USE_SHA256,
 
2456
                                 ctypes.cast(ctypes.byref(buf),
 
2457
                                             ctypes.POINTER(ctypes.c_ubyte)),
 
2458
                                 ctypes.byref(buf_len))
 
2459
        # Deinit the certificate
 
2460
        gnutls.pubkey_deinit(pubkey)
 
2461
 
 
2462
        # Convert the buffer to a Python bytestring
 
2463
        key_id = ctypes.string_at(buf, buf_len.value)
 
2464
        # Convert the bytestring to hexadecimal notation
 
2465
        hex_key_id = binascii.hexlify(key_id).upper()
 
2466
        return hex_key_id
 
2467
 
 
2468
    @staticmethod
2349
2469
    def fingerprint(openpgp):
2350
2470
        "Convert an OpenPGP bytestring to a hexdigit fingerprint"
2351
2471
        # New GnuTLS "datum" with the OpenPGP public key
2365
2485
                                       ctypes.byref(crtverify))
2366
2486
        if crtverify.value != 0:
2367
2487
            gnutls.openpgp_crt_deinit(crt)
2368
 
            raise gnutls.CertificateSecurityError("Verify failed")
 
2488
            raise gnutls.CertificateSecurityError(code
 
2489
                                                  =crtverify.value)
2369
2490
        # New buffer for the fingerprint
2370
2491
        buf = ctypes.create_string_buffer(20)
2371
2492
        buf_len = ctypes.c_size_t()
2381
2502
        return hex_fpr
2382
2503
 
2383
2504
 
2384
 
class MultiprocessingMixIn(object):
 
2505
class MultiprocessingMixIn:
2385
2506
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
2386
2507
 
2387
2508
    def sub_process_main(self, request, address):
2399
2520
        return proc
2400
2521
 
2401
2522
 
2402
 
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
 
2523
class MultiprocessingMixInWithPipe(MultiprocessingMixIn):
2403
2524
    """ adds a pipe to the MixIn """
2404
2525
 
2405
2526
    def process_request(self, request, client_address):
2420
2541
 
2421
2542
 
2422
2543
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
2423
 
                     socketserver.TCPServer, object):
 
2544
                     socketserver.TCPServer):
2424
2545
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
2425
2546
 
2426
2547
    Attributes:
2499
2620
                    raise
2500
2621
        # Only bind(2) the socket if we really need to.
2501
2622
        if self.server_address[0] or self.server_address[1]:
 
2623
            if self.server_address[1]:
 
2624
                self.allow_reuse_address = True
2502
2625
            if not self.server_address[0]:
2503
2626
                if self.address_family == socket.AF_INET6:
2504
2627
                    any_address = "::"  # in6addr_any
2557
2680
    def add_pipe(self, parent_pipe, proc):
2558
2681
        # Call "handle_ipc" for both data and EOF events
2559
2682
        GLib.io_add_watch(
2560
 
            parent_pipe.fileno(),
 
2683
            parent_pipe.fileno(), GLib.PRIORITY_DEFAULT,
2561
2684
            GLib.IO_IN | GLib.IO_HUP,
2562
2685
            functools.partial(self.handle_ipc,
2563
2686
                              parent_pipe=parent_pipe,
2578
2701
        command = request[0]
2579
2702
 
2580
2703
        if command == 'init':
2581
 
            fpr = request[1].decode("ascii")
2582
 
            address = request[2]
 
2704
            key_id = request[1].decode("ascii")
 
2705
            fpr = request[2].decode("ascii")
 
2706
            address = request[3]
2583
2707
 
2584
2708
            for c in self.clients.values():
2585
 
                if c.fingerprint == fpr:
 
2709
                if key_id == "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855":
 
2710
                    continue
 
2711
                if key_id and c.key_id == key_id:
 
2712
                    client = c
 
2713
                    break
 
2714
                if fpr and c.fingerprint == fpr:
2586
2715
                    client = c
2587
2716
                    break
2588
2717
            else:
2589
 
                logger.info("Client not found for fingerprint: %s, ad"
2590
 
                            "dress: %s", fpr, address)
 
2718
                logger.info("Client not found for key ID: %s, address"
 
2719
                            ": %s", key_id or fpr, address)
2591
2720
                if self.use_dbus:
2592
2721
                    # Emit D-Bus signal
2593
 
                    mandos_dbus_service.ClientNotFound(fpr,
 
2722
                    mandos_dbus_service.ClientNotFound(key_id or fpr,
2594
2723
                                                       address[0])
2595
2724
                parent_pipe.send(False)
2596
2725
                return False
2597
2726
 
2598
2727
            GLib.io_add_watch(
2599
 
                parent_pipe.fileno(),
 
2728
                parent_pipe.fileno(), GLib.PRIORITY_DEFAULT,
2600
2729
                GLib.IO_IN | GLib.IO_HUP,
2601
2730
                functools.partial(self.handle_ipc,
2602
2731
                                  parent_pipe=parent_pipe,
2853
2982
 
2854
2983
    options = parser.parse_args()
2855
2984
 
2856
 
    if options.check:
2857
 
        import doctest
2858
 
        fail_count, test_count = doctest.testmod()
2859
 
        sys.exit(os.EX_OK if fail_count == 0 else 1)
2860
 
 
2861
2985
    # Default values for config file for server-global settings
 
2986
    if gnutls.has_rawpk:
 
2987
        priority = ("SECURE128:!CTYPE-X.509:+CTYPE-RAWPK:!RSA"
 
2988
                    ":!VERS-ALL:+VERS-TLS1.3:%PROFILE_ULTRA")
 
2989
    else:
 
2990
        priority = ("SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
 
2991
                    ":+SIGN-DSA-SHA256")
2862
2992
    server_defaults = {"interface": "",
2863
2993
                       "address": "",
2864
2994
                       "port": "",
2865
2995
                       "debug": "False",
2866
 
                       "priority":
2867
 
                       "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2868
 
                       ":+SIGN-DSA-SHA256",
 
2996
                       "priority": priority,
2869
2997
                       "servicename": "Mandos",
2870
2998
                       "use_dbus": "True",
2871
2999
                       "use_ipv6": "True",
2876
3004
                       "foreground": "False",
2877
3005
                       "zeroconf": "True",
2878
3006
                       }
 
3007
    del priority
2879
3008
 
2880
3009
    # Parse config file for server-global settings
2881
 
    server_config = configparser.SafeConfigParser(server_defaults)
 
3010
    server_config = configparser.ConfigParser(server_defaults)
2882
3011
    del server_defaults
2883
3012
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
2884
 
    # Convert the SafeConfigParser object to a dict
 
3013
    # Convert the ConfigParser object to a dict
2885
3014
    server_settings = server_config.defaults()
2886
3015
    # Use the appropriate methods on the non-string config options
2887
3016
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
2959
3088
                                  server_settings["servicename"])))
2960
3089
 
2961
3090
    # Parse config file with clients
2962
 
    client_config = configparser.SafeConfigParser(Client
2963
 
                                                  .client_defaults)
 
3091
    client_config = configparser.ConfigParser(Client.client_defaults)
2964
3092
    client_config.read(os.path.join(server_settings["configdir"],
2965
3093
                                    "clients.conf"))
2966
3094
 
3037
3165
        # Close all input and output, do double fork, etc.
3038
3166
        daemon()
3039
3167
 
3040
 
    # multiprocessing will use threads, so before we use GLib we need
3041
 
    # to inform GLib that threads will be used.
3042
 
    GLib.threads_init()
 
3168
    if gi.version_info < (3, 10, 2):
 
3169
        # multiprocessing will use threads, so before we use GLib we
 
3170
        # need to inform GLib that threads will be used.
 
3171
        GLib.threads_init()
3043
3172
 
3044
3173
    global main_loop
3045
3174
    # From the Avahi example code
3125
3254
                        for k in ("name", "host"):
3126
3255
                            if isinstance(value[k], bytes):
3127
3256
                                value[k] = value[k].decode("utf-8")
 
3257
                        if "key_id" not in value:
 
3258
                            value["key_id"] = ""
 
3259
                        elif "fingerprint" not in value:
 
3260
                            value["fingerprint"] = ""
3128
3261
                    #  old_client_settings
3129
3262
                    # .keys()
3130
3263
                    old_client_settings = {
3267
3400
                pass
3268
3401
 
3269
3402
            @dbus.service.signal(_interface, signature="ss")
3270
 
            def ClientNotFound(self, fingerprint, address):
 
3403
            def ClientNotFound(self, key_id, address):
3271
3404
                "D-Bus signal"
3272
3405
                pass
3273
3406
 
3469
3602
                sys.exit(1)
3470
3603
            # End of Avahi example code
3471
3604
 
3472
 
        GLib.io_add_watch(tcp_server.fileno(), GLib.IO_IN,
 
3605
        GLib.io_add_watch(tcp_server.fileno(), GLib.PRIORITY_DEFAULT,
 
3606
                          GLib.IO_IN,
3473
3607
                          lambda *args, **kwargs:
3474
3608
                          (tcp_server.handle_request
3475
3609
                           (*args[2:], **kwargs) or True))
3488
3622
    # Must run before the D-Bus bus name gets deregistered
3489
3623
    cleanup()
3490
3624
 
 
3625
 
 
3626
def should_only_run_tests():
 
3627
    parser = argparse.ArgumentParser(add_help=False)
 
3628
    parser.add_argument("--check", action='store_true')
 
3629
    args, unknown_args = parser.parse_known_args()
 
3630
    run_tests = args.check
 
3631
    if run_tests:
 
3632
        # Remove --check argument from sys.argv
 
3633
        sys.argv[1:] = unknown_args
 
3634
    return run_tests
 
3635
 
 
3636
# Add all tests from doctest strings
 
3637
def load_tests(loader, tests, none):
 
3638
    import doctest
 
3639
    tests.addTests(doctest.DocTestSuite())
 
3640
    return tests
3491
3641
 
3492
3642
if __name__ == '__main__':
3493
 
    main()
 
3643
    try:
 
3644
        if should_only_run_tests():
 
3645
            # Call using ./mandos --check [--verbose]
 
3646
            unittest.main()
 
3647
        else:
 
3648
            main()
 
3649
    finally:
 
3650
        logging.shutdown()