/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: 2021-02-04 17:57:58 UTC
  • Revision ID: teddy@recompile.se-20210204175758-t9gywlxuhe1nseqo
dracut-module/password-agent.c: Add extra diagnostic info

* dracut-module/password-agent.c
  (test_send_password_to_socket_EMSGSIZE): If send() fails, also show
  the message size.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python
2
 
# -*- mode: python; coding: utf-8 -*-
 
1
#!/usr/bin/python3 -bI
 
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-2020 Teddy Hogeborn
 
15
# Copyright © 2008-2020 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
 
81
import random
 
82
import shlex
80
83
 
81
84
import dbus
82
85
import dbus.service
 
86
import gi
83
87
from gi.repository import GLib
84
88
from dbus.mainloop.glib import DBusGMainLoop
85
89
import ctypes
87
91
import xml.dom.minidom
88
92
import inspect
89
93
 
 
94
if sys.version_info.major == 2:
 
95
    __metaclass__ = type
 
96
    str = unicode
 
97
 
 
98
# Add collections.abc.Callable if it does not exist
 
99
try:
 
100
    collections.abc.Callable
 
101
except AttributeError:
 
102
    class abc:
 
103
        Callable = collections.Callable
 
104
    collections.abc = abc
 
105
    del abc
 
106
 
 
107
# Add shlex.quote if it does not exist
 
108
try:
 
109
    shlex.quote
 
110
except AttributeError:
 
111
    shlex.quote = re.escape
 
112
 
 
113
# Show warnings by default
 
114
if not sys.warnoptions:
 
115
    import warnings
 
116
    warnings.simplefilter("default")
 
117
 
90
118
# Try to find the value of SO_BINDTODEVICE:
91
119
try:
92
120
    # This is where SO_BINDTODEVICE is in Python 3.3 (or 3.4?) and
112
140
            # No value found
113
141
            SO_BINDTODEVICE = None
114
142
 
115
 
if sys.version_info.major == 2:
116
 
    str = unicode
 
143
if sys.version_info < (3, 2):
 
144
    configparser.Configparser = configparser.SafeConfigParser
117
145
 
118
 
version = "1.7.16"
 
146
version = "1.8.14"
119
147
stored_state_file = "clients.pickle"
120
148
 
121
149
logger = logging.getLogger()
 
150
logging.captureWarnings(True)   # Show warnings via the logging system
122
151
syslogger = None
123
152
 
124
153
try:
179
208
    pass
180
209
 
181
210
 
182
 
class PGPEngine(object):
 
211
class PGPEngine:
183
212
    """A simple class for OpenPGP symmetric encryption & decryption"""
184
213
 
185
214
    def __init__(self):
189
218
            output = subprocess.check_output(["gpgconf"])
190
219
            for line in output.splitlines():
191
220
                name, text, path = line.split(b":")
192
 
                if name == "gpg":
 
221
                if name == b"gpg":
193
222
                    self.gpg = path
194
223
                    break
195
224
        except OSError as e:
200
229
                          '--force-mdc',
201
230
                          '--quiet']
202
231
        # Only GPG version 1 has the --no-use-agent option.
203
 
        if self.gpg == "gpg" or self.gpg.endswith("/gpg"):
 
232
        if self.gpg == b"gpg" or self.gpg.endswith(b"/gpg"):
204
233
            self.gnupgargs.append("--no-use-agent")
205
234
 
206
235
    def __enter__(self):
275
304
 
276
305
 
277
306
# 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."""
 
307
class avahi:
 
308
    """This isn't so much a class as it is a module-like namespace."""
281
309
    IF_UNSPEC = -1               # avahi-common/address.h
282
310
    PROTO_UNSPEC = -1            # avahi-common/address.h
283
311
    PROTO_INET = 0               # avahi-common/address.h
287
315
    DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
288
316
    DBUS_PATH_SERVER = "/"
289
317
 
290
 
    def string_array_to_txt_array(self, t):
 
318
    @staticmethod
 
319
    def string_array_to_txt_array(t):
291
320
        return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
292
321
                           for s in t), signature="ay")
293
322
    ENTRY_GROUP_ESTABLISHED = 2  # avahi-common/defs.h
298
327
    SERVER_RUNNING = 2           # avahi-common/defs.h
299
328
    SERVER_COLLISION = 3         # avahi-common/defs.h
300
329
    SERVER_FAILURE = 4           # avahi-common/defs.h
301
 
avahi = Avahi()
302
330
 
303
331
 
304
332
class AvahiError(Exception):
316
344
    pass
317
345
 
318
346
 
319
 
class AvahiService(object):
 
347
class AvahiService:
320
348
    """An Avahi (Zeroconf) service.
321
349
 
322
350
    Attributes:
496
524
class AvahiServiceToSyslog(AvahiService):
497
525
    def rename(self, *args, **kwargs):
498
526
        """Add the new name to the syslog messages"""
499
 
        ret = super(AvahiServiceToSyslog, self).rename(self, *args,
 
527
        ret = super(AvahiServiceToSyslog, self).rename(*args,
500
528
                                                       **kwargs)
501
529
        syslogger.setFormatter(logging.Formatter(
502
530
            'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
505
533
 
506
534
 
507
535
# Pretend that we have a GnuTLS module
508
 
class GnuTLS(object):
509
 
    """This isn't so much a class as it is a module-like namespace.
510
 
    It is instantiated once, and simulates having a GnuTLS module."""
 
536
class gnutls:
 
537
    """This isn't so much a class as it is a module-like namespace."""
511
538
 
512
539
    library = ctypes.util.find_library("gnutls")
513
540
    if library is None:
514
541
        library = ctypes.util.find_library("gnutls-deb0")
515
542
    _library = ctypes.cdll.LoadLibrary(library)
516
543
    del library
517
 
    _need_version = b"3.3.0"
518
 
 
519
 
    def __init__(self):
520
 
        # Need to use "self" here, since this method is called before
521
 
        # the assignment to the "gnutls" global variable happens.
522
 
        if self.check_version(self._need_version) is None:
523
 
            raise self.Error("Needs GnuTLS {} or later"
524
 
                             .format(self._need_version))
525
544
 
526
545
    # Unless otherwise indicated, the constants and types below are
527
546
    # all from the gnutls/gnutls.h C header file.
531
550
    E_INTERRUPTED = -52
532
551
    E_AGAIN = -28
533
552
    CRT_OPENPGP = 2
 
553
    CRT_RAWPK = 3
534
554
    CLIENT = 2
535
555
    SHUT_RDWR = 0
536
556
    CRD_CERTIFICATE = 1
537
557
    E_NO_CERTIFICATE_FOUND = -49
 
558
    X509_FMT_DER = 0
 
559
    NO_TICKETS = 1<<10
 
560
    ENABLE_RAWPK = 1<<18
 
561
    CTYPE_PEERS = 3
 
562
    KEYID_USE_SHA256 = 1        # gnutls/x509.h
538
563
    OPENPGP_FMT_RAW = 0         # gnutls/openpgp.h
539
564
 
540
565
    # Types
563
588
 
564
589
    # Exceptions
565
590
    class Error(Exception):
566
 
        # We need to use the class name "GnuTLS" here, since this
567
 
        # exception might be raised from within GnuTLS.__init__,
568
 
        # which is called before the assignment to the "gnutls"
569
 
        # global variable has happened.
570
591
        def __init__(self, message=None, code=None, args=()):
571
592
            # Default usage is by a message string, but if a return
572
593
            # code is passed, convert it to a string with
573
594
            # gnutls.strerror()
574
595
            self.code = code
575
596
            if message is None and code is not None:
576
 
                message = GnuTLS.strerror(code)
577
 
            return super(GnuTLS.Error, self).__init__(
 
597
                message = gnutls.strerror(code)
 
598
            return super(gnutls.Error, self).__init__(
578
599
                message, *args)
579
600
 
580
601
    class CertificateSecurityError(Error):
581
602
        pass
582
603
 
583
604
    # Classes
584
 
    class Credentials(object):
 
605
    class Credentials:
585
606
        def __init__(self):
586
607
            self._c_object = gnutls.certificate_credentials_t()
587
608
            gnutls.certificate_allocate_credentials(
591
612
        def __del__(self):
592
613
            gnutls.certificate_free_credentials(self._c_object)
593
614
 
594
 
    class ClientSession(object):
 
615
    class ClientSession:
595
616
        def __init__(self, socket, credentials=None):
596
617
            self._c_object = gnutls.session_t()
597
 
            gnutls.init(ctypes.byref(self._c_object), gnutls.CLIENT)
 
618
            gnutls_flags = gnutls.CLIENT
 
619
            if gnutls.check_version(b"3.5.6"):
 
620
                gnutls_flags |= gnutls.NO_TICKETS
 
621
            if gnutls.has_rawpk:
 
622
                gnutls_flags |= gnutls.ENABLE_RAWPK
 
623
            gnutls.init(ctypes.byref(self._c_object), gnutls_flags)
 
624
            del gnutls_flags
598
625
            gnutls.set_default_priority(self._c_object)
599
626
            gnutls.transport_set_ptr(self._c_object, socket.fileno())
600
627
            gnutls.handshake_set_private_extensions(self._c_object,
732
759
    check_version.argtypes = [ctypes.c_char_p]
733
760
    check_version.restype = ctypes.c_char_p
734
761
 
735
 
    # All the function declarations below are from gnutls/openpgp.h
736
 
 
737
 
    openpgp_crt_init = _library.gnutls_openpgp_crt_init
738
 
    openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
739
 
    openpgp_crt_init.restype = _error_code
740
 
 
741
 
    openpgp_crt_import = _library.gnutls_openpgp_crt_import
742
 
    openpgp_crt_import.argtypes = [openpgp_crt_t,
743
 
                                   ctypes.POINTER(datum_t),
744
 
                                   openpgp_crt_fmt_t]
745
 
    openpgp_crt_import.restype = _error_code
746
 
 
747
 
    openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
748
 
    openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
749
 
                                        ctypes.POINTER(ctypes.c_uint)]
750
 
    openpgp_crt_verify_self.restype = _error_code
751
 
 
752
 
    openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
753
 
    openpgp_crt_deinit.argtypes = [openpgp_crt_t]
754
 
    openpgp_crt_deinit.restype = None
755
 
 
756
 
    openpgp_crt_get_fingerprint = (
757
 
        _library.gnutls_openpgp_crt_get_fingerprint)
758
 
    openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
759
 
                                            ctypes.c_void_p,
760
 
                                            ctypes.POINTER(
761
 
                                                ctypes.c_size_t)]
762
 
    openpgp_crt_get_fingerprint.restype = _error_code
 
762
    _need_version = b"3.3.0"
 
763
    if check_version(_need_version) is None:
 
764
        raise self.Error("Needs GnuTLS {} or later"
 
765
                         .format(_need_version))
 
766
 
 
767
    _tls_rawpk_version = b"3.6.6"
 
768
    has_rawpk = bool(check_version(_tls_rawpk_version))
 
769
 
 
770
    if has_rawpk:
 
771
        # Types
 
772
        class pubkey_st(ctypes.Structure):
 
773
            _fields = []
 
774
        pubkey_t = ctypes.POINTER(pubkey_st)
 
775
 
 
776
        x509_crt_fmt_t = ctypes.c_int
 
777
 
 
778
        # All the function declarations below are from
 
779
        # gnutls/abstract.h
 
780
        pubkey_init = _library.gnutls_pubkey_init
 
781
        pubkey_init.argtypes = [ctypes.POINTER(pubkey_t)]
 
782
        pubkey_init.restype = _error_code
 
783
 
 
784
        pubkey_import = _library.gnutls_pubkey_import
 
785
        pubkey_import.argtypes = [pubkey_t, ctypes.POINTER(datum_t),
 
786
                                  x509_crt_fmt_t]
 
787
        pubkey_import.restype = _error_code
 
788
 
 
789
        pubkey_get_key_id = _library.gnutls_pubkey_get_key_id
 
790
        pubkey_get_key_id.argtypes = [pubkey_t, ctypes.c_int,
 
791
                                      ctypes.POINTER(ctypes.c_ubyte),
 
792
                                      ctypes.POINTER(ctypes.c_size_t)]
 
793
        pubkey_get_key_id.restype = _error_code
 
794
 
 
795
        pubkey_deinit = _library.gnutls_pubkey_deinit
 
796
        pubkey_deinit.argtypes = [pubkey_t]
 
797
        pubkey_deinit.restype = None
 
798
    else:
 
799
        # All the function declarations below are from
 
800
        # gnutls/openpgp.h
 
801
 
 
802
        openpgp_crt_init = _library.gnutls_openpgp_crt_init
 
803
        openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
 
804
        openpgp_crt_init.restype = _error_code
 
805
 
 
806
        openpgp_crt_import = _library.gnutls_openpgp_crt_import
 
807
        openpgp_crt_import.argtypes = [openpgp_crt_t,
 
808
                                       ctypes.POINTER(datum_t),
 
809
                                       openpgp_crt_fmt_t]
 
810
        openpgp_crt_import.restype = _error_code
 
811
 
 
812
        openpgp_crt_verify_self = \
 
813
            _library.gnutls_openpgp_crt_verify_self
 
814
        openpgp_crt_verify_self.argtypes = [
 
815
            openpgp_crt_t,
 
816
            ctypes.c_uint,
 
817
            ctypes.POINTER(ctypes.c_uint),
 
818
        ]
 
819
        openpgp_crt_verify_self.restype = _error_code
 
820
 
 
821
        openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
 
822
        openpgp_crt_deinit.argtypes = [openpgp_crt_t]
 
823
        openpgp_crt_deinit.restype = None
 
824
 
 
825
        openpgp_crt_get_fingerprint = (
 
826
            _library.gnutls_openpgp_crt_get_fingerprint)
 
827
        openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
 
828
                                                ctypes.c_void_p,
 
829
                                                ctypes.POINTER(
 
830
                                                    ctypes.c_size_t)]
 
831
        openpgp_crt_get_fingerprint.restype = _error_code
 
832
 
 
833
    if check_version(b"3.6.4"):
 
834
        certificate_type_get2 = _library.gnutls_certificate_type_get2
 
835
        certificate_type_get2.argtypes = [session_t, ctypes.c_int]
 
836
        certificate_type_get2.restype = _error_code
763
837
 
764
838
    # Remove non-public functions
765
839
    del _error_code, _retry_on_error
766
 
# Create the global "gnutls" object, simulating a module
767
 
gnutls = GnuTLS()
768
840
 
769
841
 
770
842
def call_pipe(connection,       # : multiprocessing.Connection
778
850
    connection.close()
779
851
 
780
852
 
781
 
class Client(object):
 
853
class Client:
782
854
    """A representation of a client host served by this server.
783
855
 
784
856
    Attributes:
785
857
    approved:   bool(); 'None' if not yet approved/disapproved
786
858
    approval_delay: datetime.timedelta(); Time to wait for approval
787
859
    approval_duration: datetime.timedelta(); Duration of one approval
788
 
    checker:    subprocess.Popen(); a running checker process used
789
 
                                    to see if the client lives.
790
 
                                    'None' if no process is running.
 
860
    checker: multiprocessing.Process(); a running checker process used
 
861
             to see if the client lives. 'None' if no process is
 
862
             running.
791
863
    checker_callback_tag: a GLib event source tag, or None
792
864
    checker_command: string; External command which is run to check
793
865
                     if client lives.  %() expansions are done at
801
873
    disable_initiator_tag: a GLib event source tag, or None
802
874
    enabled:    bool()
803
875
    fingerprint: string (40 or 32 hexadecimal digits); used to
804
 
                 uniquely identify the client
 
876
                 uniquely identify an OpenPGP client
 
877
    key_id: string (64 hexadecimal digits); used to uniquely identify
 
878
            a client using raw public keys
805
879
    host:       string; available for use by the checker command
806
880
    interval:   datetime.timedelta(); How often to start a new checker
807
881
    last_approval_request: datetime.datetime(); (UTC) or None
825
899
    """
826
900
 
827
901
    runtime_expansions = ("approval_delay", "approval_duration",
828
 
                          "created", "enabled", "expires",
 
902
                          "created", "enabled", "expires", "key_id",
829
903
                          "fingerprint", "host", "interval",
830
904
                          "last_approval_request", "last_checked_ok",
831
905
                          "last_enabled", "name", "timeout")
861
935
            client["enabled"] = config.getboolean(client_name,
862
936
                                                  "enabled")
863
937
 
864
 
            # Uppercase and remove spaces from fingerprint for later
865
 
            # comparison purposes with return value from the
866
 
            # fingerprint() function
 
938
            # Uppercase and remove spaces from key_id and fingerprint
 
939
            # for later comparison purposes with return value from the
 
940
            # key_id() and fingerprint() functions
 
941
            client["key_id"] = (section.get("key_id", "").upper()
 
942
                                .replace(" ", ""))
867
943
            client["fingerprint"] = (section["fingerprint"].upper()
868
944
                                     .replace(" ", ""))
869
945
            if "secret" in section:
913
989
            self.expires = None
914
990
 
915
991
        logger.debug("Creating client %r", self.name)
 
992
        logger.debug("  Key ID: %s", self.key_id)
916
993
        logger.debug("  Fingerprint: %s", self.fingerprint)
917
994
        self.created = settings.get("created",
918
995
                                    datetime.datetime.utcnow())
982
1059
        if self.checker_initiator_tag is not None:
983
1060
            GLib.source_remove(self.checker_initiator_tag)
984
1061
        self.checker_initiator_tag = GLib.timeout_add(
985
 
            int(self.interval.total_seconds() * 1000),
 
1062
            random.randrange(int(self.interval.total_seconds() * 1000
 
1063
                                 + 1)),
986
1064
            self.start_checker)
987
1065
        # Schedule a disable() when 'timeout' has passed
988
1066
        if self.disable_initiator_tag is not None:
995
1073
    def checker_callback(self, source, condition, connection,
996
1074
                         command):
997
1075
        """The checker has completed, so take appropriate actions."""
998
 
        self.checker_callback_tag = None
999
 
        self.checker = None
1000
1076
        # Read return code from connection (see call_pipe)
1001
1077
        returncode = connection.recv()
1002
1078
        connection.close()
 
1079
        if self.checker is not None:
 
1080
            self.checker.join()
 
1081
        self.checker_callback_tag = None
 
1082
        self.checker = None
1003
1083
 
1004
1084
        if returncode >= 0:
1005
1085
            self.last_checker_status = returncode
1061
1141
        if self.checker is None:
1062
1142
            # Escape attributes for the shell
1063
1143
            escaped_attrs = {
1064
 
                attr: re.escape(str(getattr(self, attr)))
 
1144
                attr: shlex.quote(str(getattr(self, attr)))
1065
1145
                for attr in self.runtime_expansions}
1066
1146
            try:
1067
1147
                command = self.checker_command % escaped_attrs
1094
1174
                kwargs=popen_args)
1095
1175
            self.checker.start()
1096
1176
            self.checker_callback_tag = GLib.io_add_watch(
1097
 
                pipe[0].fileno(), GLib.IO_IN,
 
1177
                GLib.IOChannel.unix_new(pipe[0].fileno()),
 
1178
                GLib.PRIORITY_DEFAULT, GLib.IO_IN,
1098
1179
                self.checker_callback, pipe[0], command)
1099
1180
        # Re-run this periodically if run by GLib.timeout_add
1100
1181
        return True
1355
1436
                raise ValueError("Byte arrays not supported for non-"
1356
1437
                                 "'ay' signature {!r}"
1357
1438
                                 .format(prop._dbus_signature))
1358
 
            value = dbus.ByteArray(b''.join(chr(byte)
1359
 
                                            for byte in value))
 
1439
            value = dbus.ByteArray(bytes(value))
1360
1440
        prop(value)
1361
1441
 
1362
1442
    @dbus.service.method(dbus.PROPERTIES_IFACE,
2000
2080
    def Name_dbus_property(self):
2001
2081
        return dbus.String(self.name)
2002
2082
 
 
2083
    # KeyID - property
 
2084
    @dbus_annotations(
 
2085
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
 
2086
    @dbus_service_property(_interface, signature="s", access="read")
 
2087
    def KeyID_dbus_property(self):
 
2088
        return dbus.String(self.key_id)
 
2089
 
2003
2090
    # Fingerprint - property
2004
2091
    @dbus_annotations(
2005
2092
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
2160
2247
    del _interface
2161
2248
 
2162
2249
 
2163
 
class ProxyClient(object):
2164
 
    def __init__(self, child_pipe, fpr, address):
 
2250
class ProxyClient:
 
2251
    def __init__(self, child_pipe, key_id, fpr, address):
2165
2252
        self._pipe = child_pipe
2166
 
        self._pipe.send(('init', fpr, address))
 
2253
        self._pipe.send(('init', key_id, fpr, address))
2167
2254
        if not self._pipe.recv():
2168
 
            raise KeyError(fpr)
 
2255
            raise KeyError(key_id or fpr)
2169
2256
 
2170
2257
    def __getattribute__(self, name):
2171
2258
        if name == '_pipe':
2238
2325
 
2239
2326
            approval_required = False
2240
2327
            try:
2241
 
                try:
2242
 
                    fpr = self.fingerprint(
2243
 
                        self.peer_certificate(session))
2244
 
                except (TypeError, gnutls.Error) as error:
2245
 
                    logger.warning("Bad certificate: %s", error)
2246
 
                    return
2247
 
                logger.debug("Fingerprint: %s", fpr)
2248
 
 
2249
 
                try:
2250
 
                    client = ProxyClient(child_pipe, fpr,
 
2328
                if gnutls.has_rawpk:
 
2329
                    fpr = b""
 
2330
                    try:
 
2331
                        key_id = self.key_id(
 
2332
                            self.peer_certificate(session))
 
2333
                    except (TypeError, gnutls.Error) as error:
 
2334
                        logger.warning("Bad certificate: %s", error)
 
2335
                        return
 
2336
                    logger.debug("Key ID: %s", key_id)
 
2337
 
 
2338
                else:
 
2339
                    key_id = b""
 
2340
                    try:
 
2341
                        fpr = self.fingerprint(
 
2342
                            self.peer_certificate(session))
 
2343
                    except (TypeError, gnutls.Error) as error:
 
2344
                        logger.warning("Bad certificate: %s", error)
 
2345
                        return
 
2346
                    logger.debug("Fingerprint: %s", fpr)
 
2347
 
 
2348
                try:
 
2349
                    client = ProxyClient(child_pipe, key_id, fpr,
2251
2350
                                         self.client_address)
2252
2351
                except KeyError:
2253
2352
                    return
2330
2429
 
2331
2430
    @staticmethod
2332
2431
    def peer_certificate(session):
2333
 
        "Return the peer's OpenPGP certificate as a bytestring"
2334
 
        # If not an OpenPGP certificate...
2335
 
        if (gnutls.certificate_type_get(session._c_object)
2336
 
            != gnutls.CRT_OPENPGP):
 
2432
        "Return the peer's certificate as a bytestring"
 
2433
        try:
 
2434
            cert_type = gnutls.certificate_type_get2(session._c_object,
 
2435
                                                     gnutls.CTYPE_PEERS)
 
2436
        except AttributeError:
 
2437
            cert_type = gnutls.certificate_type_get(session._c_object)
 
2438
        if gnutls.has_rawpk:
 
2439
            valid_cert_types = frozenset((gnutls.CRT_RAWPK,))
 
2440
        else:
 
2441
            valid_cert_types = frozenset((gnutls.CRT_OPENPGP,))
 
2442
        # If not a valid certificate type...
 
2443
        if cert_type not in valid_cert_types:
 
2444
            logger.info("Cert type %r not in %r", cert_type,
 
2445
                        valid_cert_types)
2337
2446
            # ...return invalid data
2338
2447
            return b""
2339
2448
        list_size = ctypes.c_uint(1)
2347
2456
        return ctypes.string_at(cert.data, cert.size)
2348
2457
 
2349
2458
    @staticmethod
 
2459
    def key_id(certificate):
 
2460
        "Convert a certificate bytestring to a hexdigit key ID"
 
2461
        # New GnuTLS "datum" with the public key
 
2462
        datum = gnutls.datum_t(
 
2463
            ctypes.cast(ctypes.c_char_p(certificate),
 
2464
                        ctypes.POINTER(ctypes.c_ubyte)),
 
2465
            ctypes.c_uint(len(certificate)))
 
2466
        # XXX all these need to be created in the gnutls "module"
 
2467
        # New empty GnuTLS certificate
 
2468
        pubkey = gnutls.pubkey_t()
 
2469
        gnutls.pubkey_init(ctypes.byref(pubkey))
 
2470
        # Import the raw public key into the certificate
 
2471
        gnutls.pubkey_import(pubkey,
 
2472
                             ctypes.byref(datum),
 
2473
                             gnutls.X509_FMT_DER)
 
2474
        # New buffer for the key ID
 
2475
        buf = ctypes.create_string_buffer(32)
 
2476
        buf_len = ctypes.c_size_t(len(buf))
 
2477
        # Get the key ID from the raw public key into the buffer
 
2478
        gnutls.pubkey_get_key_id(
 
2479
            pubkey,
 
2480
            gnutls.KEYID_USE_SHA256,
 
2481
            ctypes.cast(ctypes.byref(buf),
 
2482
                        ctypes.POINTER(ctypes.c_ubyte)),
 
2483
            ctypes.byref(buf_len))
 
2484
        # Deinit the certificate
 
2485
        gnutls.pubkey_deinit(pubkey)
 
2486
 
 
2487
        # Convert the buffer to a Python bytestring
 
2488
        key_id = ctypes.string_at(buf, buf_len.value)
 
2489
        # Convert the bytestring to hexadecimal notation
 
2490
        hex_key_id = binascii.hexlify(key_id).upper()
 
2491
        return hex_key_id
 
2492
 
 
2493
    @staticmethod
2350
2494
    def fingerprint(openpgp):
2351
2495
        "Convert an OpenPGP bytestring to a hexdigit fingerprint"
2352
2496
        # New GnuTLS "datum" with the OpenPGP public key
2366
2510
                                       ctypes.byref(crtverify))
2367
2511
        if crtverify.value != 0:
2368
2512
            gnutls.openpgp_crt_deinit(crt)
2369
 
            raise gnutls.CertificateSecurityError("Verify failed")
 
2513
            raise gnutls.CertificateSecurityError(code
 
2514
                                                  =crtverify.value)
2370
2515
        # New buffer for the fingerprint
2371
2516
        buf = ctypes.create_string_buffer(20)
2372
2517
        buf_len = ctypes.c_size_t()
2382
2527
        return hex_fpr
2383
2528
 
2384
2529
 
2385
 
class MultiprocessingMixIn(object):
 
2530
class MultiprocessingMixIn:
2386
2531
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
2387
2532
 
2388
2533
    def sub_process_main(self, request, address):
2400
2545
        return proc
2401
2546
 
2402
2547
 
2403
 
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
 
2548
class MultiprocessingMixInWithPipe(MultiprocessingMixIn):
2404
2549
    """ adds a pipe to the MixIn """
2405
2550
 
2406
2551
    def process_request(self, request, client_address):
2421
2566
 
2422
2567
 
2423
2568
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
2424
 
                     socketserver.TCPServer, object):
 
2569
                     socketserver.TCPServer):
2425
2570
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
2426
2571
 
2427
2572
    Attributes:
2500
2645
                    raise
2501
2646
        # Only bind(2) the socket if we really need to.
2502
2647
        if self.server_address[0] or self.server_address[1]:
 
2648
            if self.server_address[1]:
 
2649
                self.allow_reuse_address = True
2503
2650
            if not self.server_address[0]:
2504
2651
                if self.address_family == socket.AF_INET6:
2505
2652
                    any_address = "::"  # in6addr_any
2558
2705
    def add_pipe(self, parent_pipe, proc):
2559
2706
        # Call "handle_ipc" for both data and EOF events
2560
2707
        GLib.io_add_watch(
2561
 
            parent_pipe.fileno(),
2562
 
            GLib.IO_IN | GLib.IO_HUP,
 
2708
            GLib.IOChannel.unix_new(parent_pipe.fileno()),
 
2709
            GLib.PRIORITY_DEFAULT, GLib.IO_IN | GLib.IO_HUP,
2563
2710
            functools.partial(self.handle_ipc,
2564
2711
                              parent_pipe=parent_pipe,
2565
2712
                              proc=proc))
2579
2726
        command = request[0]
2580
2727
 
2581
2728
        if command == 'init':
2582
 
            fpr = request[1].decode("ascii")
2583
 
            address = request[2]
 
2729
            key_id = request[1].decode("ascii")
 
2730
            fpr = request[2].decode("ascii")
 
2731
            address = request[3]
2584
2732
 
2585
2733
            for c in self.clients.values():
2586
 
                if c.fingerprint == fpr:
 
2734
                if key_id == ("E3B0C44298FC1C149AFBF4C8996FB924"
 
2735
                              "27AE41E4649B934CA495991B7852B855"):
 
2736
                    continue
 
2737
                if key_id and c.key_id == key_id:
 
2738
                    client = c
 
2739
                    break
 
2740
                if fpr and c.fingerprint == fpr:
2587
2741
                    client = c
2588
2742
                    break
2589
2743
            else:
2590
 
                logger.info("Client not found for fingerprint: %s, ad"
2591
 
                            "dress: %s", fpr, address)
 
2744
                logger.info("Client not found for key ID: %s, address"
 
2745
                            ": %s", key_id or fpr, address)
2592
2746
                if self.use_dbus:
2593
2747
                    # Emit D-Bus signal
2594
 
                    mandos_dbus_service.ClientNotFound(fpr,
 
2748
                    mandos_dbus_service.ClientNotFound(key_id or fpr,
2595
2749
                                                       address[0])
2596
2750
                parent_pipe.send(False)
2597
2751
                return False
2598
2752
 
2599
2753
            GLib.io_add_watch(
2600
 
                parent_pipe.fileno(),
2601
 
                GLib.IO_IN | GLib.IO_HUP,
 
2754
                GLib.IOChannel.unix_new(parent_pipe.fileno()),
 
2755
                GLib.PRIORITY_DEFAULT, GLib.IO_IN | GLib.IO_HUP,
2602
2756
                functools.partial(self.handle_ipc,
2603
2757
                                  parent_pipe=parent_pipe,
2604
2758
                                  proc=proc,
2619
2773
        if command == 'getattr':
2620
2774
            attrname = request[1]
2621
2775
            if isinstance(client_object.__getattribute__(attrname),
2622
 
                          collections.Callable):
 
2776
                          collections.abc.Callable):
2623
2777
                parent_pipe.send(('function', ))
2624
2778
            else:
2625
2779
                parent_pipe.send((
2636
2790
def rfc3339_duration_to_delta(duration):
2637
2791
    """Parse an RFC 3339 "duration" and return a datetime.timedelta
2638
2792
 
2639
 
    >>> rfc3339_duration_to_delta("P7D")
2640
 
    datetime.timedelta(7)
2641
 
    >>> rfc3339_duration_to_delta("PT60S")
2642
 
    datetime.timedelta(0, 60)
2643
 
    >>> rfc3339_duration_to_delta("PT60M")
2644
 
    datetime.timedelta(0, 3600)
2645
 
    >>> rfc3339_duration_to_delta("PT24H")
2646
 
    datetime.timedelta(1)
2647
 
    >>> rfc3339_duration_to_delta("P1W")
2648
 
    datetime.timedelta(7)
2649
 
    >>> rfc3339_duration_to_delta("PT5M30S")
2650
 
    datetime.timedelta(0, 330)
2651
 
    >>> rfc3339_duration_to_delta("P1DT3M20S")
2652
 
    datetime.timedelta(1, 200)
 
2793
    >>> timedelta = datetime.timedelta
 
2794
    >>> rfc3339_duration_to_delta("P7D") == timedelta(7)
 
2795
    True
 
2796
    >>> rfc3339_duration_to_delta("PT60S") == timedelta(0, 60)
 
2797
    True
 
2798
    >>> rfc3339_duration_to_delta("PT60M") == timedelta(0, 3600)
 
2799
    True
 
2800
    >>> rfc3339_duration_to_delta("PT24H") == timedelta(1)
 
2801
    True
 
2802
    >>> rfc3339_duration_to_delta("P1W") == timedelta(7)
 
2803
    True
 
2804
    >>> rfc3339_duration_to_delta("PT5M30S") == timedelta(0, 330)
 
2805
    True
 
2806
    >>> rfc3339_duration_to_delta("P1DT3M20S") == timedelta(1, 200)
 
2807
    True
 
2808
    >>> del timedelta
2653
2809
    """
2654
2810
 
2655
2811
    # Parsing an RFC 3339 duration with regular expressions is not
2735
2891
def string_to_delta(interval):
2736
2892
    """Parse a string and return a datetime.timedelta
2737
2893
 
2738
 
    >>> string_to_delta('7d')
2739
 
    datetime.timedelta(7)
2740
 
    >>> string_to_delta('60s')
2741
 
    datetime.timedelta(0, 60)
2742
 
    >>> string_to_delta('60m')
2743
 
    datetime.timedelta(0, 3600)
2744
 
    >>> string_to_delta('24h')
2745
 
    datetime.timedelta(1)
2746
 
    >>> string_to_delta('1w')
2747
 
    datetime.timedelta(7)
2748
 
    >>> string_to_delta('5m 30s')
2749
 
    datetime.timedelta(0, 330)
 
2894
    >>> string_to_delta('7d') == datetime.timedelta(7)
 
2895
    True
 
2896
    >>> string_to_delta('60s') == datetime.timedelta(0, 60)
 
2897
    True
 
2898
    >>> string_to_delta('60m') == datetime.timedelta(0, 3600)
 
2899
    True
 
2900
    >>> string_to_delta('24h') == datetime.timedelta(1)
 
2901
    True
 
2902
    >>> string_to_delta('1w') == datetime.timedelta(7)
 
2903
    True
 
2904
    >>> string_to_delta('5m 30s') == datetime.timedelta(0, 330)
 
2905
    True
2750
2906
    """
2751
2907
 
2752
2908
    try:
2854
3010
 
2855
3011
    options = parser.parse_args()
2856
3012
 
2857
 
    if options.check:
2858
 
        import doctest
2859
 
        fail_count, test_count = doctest.testmod()
2860
 
        sys.exit(os.EX_OK if fail_count == 0 else 1)
2861
 
 
2862
3013
    # Default values for config file for server-global settings
 
3014
    if gnutls.has_rawpk:
 
3015
        priority = ("SECURE128:!CTYPE-X.509:+CTYPE-RAWPK:!RSA"
 
3016
                    ":!VERS-ALL:+VERS-TLS1.3:%PROFILE_ULTRA")
 
3017
    else:
 
3018
        priority = ("SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
 
3019
                    ":+SIGN-DSA-SHA256")
2863
3020
    server_defaults = {"interface": "",
2864
3021
                       "address": "",
2865
3022
                       "port": "",
2866
3023
                       "debug": "False",
2867
 
                       "priority":
2868
 
                       "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
2869
 
                       ":+SIGN-DSA-SHA256",
 
3024
                       "priority": priority,
2870
3025
                       "servicename": "Mandos",
2871
3026
                       "use_dbus": "True",
2872
3027
                       "use_ipv6": "True",
2877
3032
                       "foreground": "False",
2878
3033
                       "zeroconf": "True",
2879
3034
                       }
 
3035
    del priority
2880
3036
 
2881
3037
    # Parse config file for server-global settings
2882
 
    server_config = configparser.SafeConfigParser(server_defaults)
 
3038
    server_config = configparser.ConfigParser(server_defaults)
2883
3039
    del server_defaults
2884
3040
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
2885
 
    # Convert the SafeConfigParser object to a dict
 
3041
    # Convert the ConfigParser object to a dict
2886
3042
    server_settings = server_config.defaults()
2887
3043
    # Use the appropriate methods on the non-string config options
2888
3044
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
2960
3116
                                  server_settings["servicename"])))
2961
3117
 
2962
3118
    # Parse config file with clients
2963
 
    client_config = configparser.SafeConfigParser(Client
2964
 
                                                  .client_defaults)
 
3119
    client_config = configparser.ConfigParser(Client.client_defaults)
2965
3120
    client_config.read(os.path.join(server_settings["configdir"],
2966
3121
                                    "clients.conf"))
2967
3122
 
3038
3193
        # Close all input and output, do double fork, etc.
3039
3194
        daemon()
3040
3195
 
3041
 
    # multiprocessing will use threads, so before we use GLib we need
3042
 
    # to inform GLib that threads will be used.
3043
 
    GLib.threads_init()
 
3196
    if gi.version_info < (3, 10, 2):
 
3197
        # multiprocessing will use threads, so before we use GLib we
 
3198
        # need to inform GLib that threads will be used.
 
3199
        GLib.threads_init()
3044
3200
 
3045
3201
    global main_loop
3046
3202
    # From the Avahi example code
3122
3278
                             if isinstance(s, bytes)
3123
3279
                             else s) for s in
3124
3280
                            value["client_structure"]]
3125
 
                        # .name & .host
3126
 
                        for k in ("name", "host"):
 
3281
                        # .name, .host, and .checker_command
 
3282
                        for k in ("name", "host", "checker_command"):
3127
3283
                            if isinstance(value[k], bytes):
3128
3284
                                value[k] = value[k].decode("utf-8")
 
3285
                        if "key_id" not in value:
 
3286
                            value["key_id"] = ""
 
3287
                        elif "fingerprint" not in value:
 
3288
                            value["fingerprint"] = ""
3129
3289
                    #  old_client_settings
3130
3290
                    # .keys()
3131
3291
                    old_client_settings = {
3135
3295
                        for key, value in
3136
3296
                        bytes_old_client_settings.items()}
3137
3297
                    del bytes_old_client_settings
3138
 
                    # .host
 
3298
                    # .host and .checker_command
3139
3299
                    for value in old_client_settings.values():
3140
 
                        if isinstance(value["host"], bytes):
3141
 
                            value["host"] = (value["host"]
3142
 
                                             .decode("utf-8"))
 
3300
                        for attribute in ("host", "checker_command"):
 
3301
                            if isinstance(value[attribute], bytes):
 
3302
                                value[attribute] = (value[attribute]
 
3303
                                                    .decode("utf-8"))
3143
3304
            os.remove(stored_state_path)
3144
3305
        except IOError as e:
3145
3306
            if e.errno == errno.ENOENT:
3268
3429
                pass
3269
3430
 
3270
3431
            @dbus.service.signal(_interface, signature="ss")
3271
 
            def ClientNotFound(self, fingerprint, address):
 
3432
            def ClientNotFound(self, key_id, address):
3272
3433
                "D-Bus signal"
3273
3434
                pass
3274
3435
 
3470
3631
                sys.exit(1)
3471
3632
            # End of Avahi example code
3472
3633
 
3473
 
        GLib.io_add_watch(tcp_server.fileno(), GLib.IO_IN,
3474
 
                          lambda *args, **kwargs:
3475
 
                          (tcp_server.handle_request
3476
 
                           (*args[2:], **kwargs) or True))
 
3634
        GLib.io_add_watch(
 
3635
            GLib.IOChannel.unix_new(tcp_server.fileno()),
 
3636
            GLib.PRIORITY_DEFAULT, GLib.IO_IN,
 
3637
            lambda *args, **kwargs: (tcp_server.handle_request
 
3638
                                     (*args[2:], **kwargs) or True))
3477
3639
 
3478
3640
        logger.debug("Starting main loop")
3479
3641
        main_loop.run()
3489
3651
    # Must run before the D-Bus bus name gets deregistered
3490
3652
    cleanup()
3491
3653
 
 
3654
 
 
3655
def should_only_run_tests():
 
3656
    parser = argparse.ArgumentParser(add_help=False)
 
3657
    parser.add_argument("--check", action='store_true')
 
3658
    args, unknown_args = parser.parse_known_args()
 
3659
    run_tests = args.check
 
3660
    if run_tests:
 
3661
        # Remove --check argument from sys.argv
 
3662
        sys.argv[1:] = unknown_args
 
3663
    return run_tests
 
3664
 
 
3665
# Add all tests from doctest strings
 
3666
def load_tests(loader, tests, none):
 
3667
    import doctest
 
3668
    tests.addTests(doctest.DocTestSuite())
 
3669
    return tests
3492
3670
 
3493
3671
if __name__ == '__main__':
3494
 
    main()
 
3672
    try:
 
3673
        if should_only_run_tests():
 
3674
            # Call using ./mandos --check [--verbose]
 
3675
            unittest.main()
 
3676
        else:
 
3677
            main()
 
3678
    finally:
 
3679
        logging.shutdown()