/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: 2018-02-06 20:03:50 UTC
  • Revision ID: teddy@recompile.se-20180206200350-jzvorueb731xkph3
Update Debian Debhelper compatibility version.

* (debian/compat): Change to "10".

Show diffs side-by-side

added added

removed removed

Lines of Context:
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 -*-
 
1
#!/usr/bin/python
 
2
# -*- mode: python; 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-2019 Teddy Hogeborn
15
 
# Copyright © 2008-2019 Björn Påhlsson
 
14
# Copyright © 2008-2017 Teddy Hogeborn
 
15
# Copyright © 2008-2017 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
83
80
 
84
81
import dbus
85
82
import dbus.service
86
 
import gi
87
83
from gi.repository import GLib
88
84
from dbus.mainloop.glib import DBusGMainLoop
89
85
import ctypes
91
87
import xml.dom.minidom
92
88
import inspect
93
89
 
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
 
 
118
90
# Try to find the value of SO_BINDTODEVICE:
119
91
try:
120
92
    # This is where SO_BINDTODEVICE is in Python 3.3 (or 3.4?) and
140
112
            # No value found
141
113
            SO_BINDTODEVICE = None
142
114
 
143
 
if sys.version_info < (3, 2):
144
 
    configparser.Configparser = configparser.SafeConfigParser
 
115
if sys.version_info.major == 2:
 
116
    str = unicode
145
117
 
146
 
version = "1.8.11"
 
118
version = "1.7.16"
147
119
stored_state_file = "clients.pickle"
148
120
 
149
121
logger = logging.getLogger()
150
 
logging.captureWarnings(True)   # Show warnings via the logging system
151
122
syslogger = None
152
123
 
153
124
try:
208
179
    pass
209
180
 
210
181
 
211
 
class PGPEngine:
 
182
class PGPEngine(object):
212
183
    """A simple class for OpenPGP symmetric encryption & decryption"""
213
184
 
214
185
    def __init__(self):
218
189
            output = subprocess.check_output(["gpgconf"])
219
190
            for line in output.splitlines():
220
191
                name, text, path = line.split(b":")
221
 
                if name == b"gpg":
 
192
                if name == "gpg":
222
193
                    self.gpg = path
223
194
                    break
224
195
        except OSError as e:
229
200
                          '--force-mdc',
230
201
                          '--quiet']
231
202
        # Only GPG version 1 has the --no-use-agent option.
232
 
        if self.gpg == b"gpg" or self.gpg.endswith(b"/gpg"):
 
203
        if self.gpg == "gpg" or self.gpg.endswith("/gpg"):
233
204
            self.gnupgargs.append("--no-use-agent")
234
205
 
235
206
    def __enter__(self):
304
275
 
305
276
 
306
277
# Pretend that we have an Avahi module
307
 
class avahi:
308
 
    """This isn't so much a class as it is a module-like namespace."""
 
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."""
309
281
    IF_UNSPEC = -1               # avahi-common/address.h
310
282
    PROTO_UNSPEC = -1            # avahi-common/address.h
311
283
    PROTO_INET = 0               # avahi-common/address.h
315
287
    DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
316
288
    DBUS_PATH_SERVER = "/"
317
289
 
318
 
    @staticmethod
319
 
    def string_array_to_txt_array(t):
 
290
    def string_array_to_txt_array(self, t):
320
291
        return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
321
292
                           for s in t), signature="ay")
322
293
    ENTRY_GROUP_ESTABLISHED = 2  # avahi-common/defs.h
327
298
    SERVER_RUNNING = 2           # avahi-common/defs.h
328
299
    SERVER_COLLISION = 3         # avahi-common/defs.h
329
300
    SERVER_FAILURE = 4           # avahi-common/defs.h
 
301
avahi = Avahi()
330
302
 
331
303
 
332
304
class AvahiError(Exception):
344
316
    pass
345
317
 
346
318
 
347
 
class AvahiService:
 
319
class AvahiService(object):
348
320
    """An Avahi (Zeroconf) service.
349
321
 
350
322
    Attributes:
524
496
class AvahiServiceToSyslog(AvahiService):
525
497
    def rename(self, *args, **kwargs):
526
498
        """Add the new name to the syslog messages"""
527
 
        ret = super(AvahiServiceToSyslog, self).rename(*args, **kwargs)
 
499
        ret = super(AvahiServiceToSyslog, self).rename(self, *args,
 
500
                                                       **kwargs)
528
501
        syslogger.setFormatter(logging.Formatter(
529
502
            'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
530
503
            .format(self.name)))
532
505
 
533
506
 
534
507
# Pretend that we have a GnuTLS module
535
 
class gnutls:
536
 
    """This isn't so much a class as it is a module-like namespace."""
 
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."""
537
511
 
538
512
    library = ctypes.util.find_library("gnutls")
539
513
    if library is None:
540
514
        library = ctypes.util.find_library("gnutls-deb0")
541
515
    _library = ctypes.cdll.LoadLibrary(library)
542
516
    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))
543
525
 
544
526
    # Unless otherwise indicated, the constants and types below are
545
527
    # all from the gnutls/gnutls.h C header file.
549
531
    E_INTERRUPTED = -52
550
532
    E_AGAIN = -28
551
533
    CRT_OPENPGP = 2
552
 
    CRT_RAWPK = 3
553
534
    CLIENT = 2
554
535
    SHUT_RDWR = 0
555
536
    CRD_CERTIFICATE = 1
556
537
    E_NO_CERTIFICATE_FOUND = -49
557
 
    X509_FMT_DER = 0
558
 
    NO_TICKETS = 1<<10
559
 
    ENABLE_RAWPK = 1<<18
560
 
    CTYPE_PEERS = 3
561
 
    KEYID_USE_SHA256 = 1        # gnutls/x509.h
562
538
    OPENPGP_FMT_RAW = 0         # gnutls/openpgp.h
563
539
 
564
540
    # Types
587
563
 
588
564
    # Exceptions
589
565
    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.
590
570
        def __init__(self, message=None, code=None, args=()):
591
571
            # Default usage is by a message string, but if a return
592
572
            # code is passed, convert it to a string with
593
573
            # gnutls.strerror()
594
574
            self.code = code
595
575
            if message is None and code is not None:
596
 
                message = gnutls.strerror(code)
597
 
            return super(gnutls.Error, self).__init__(
 
576
                message = GnuTLS.strerror(code)
 
577
            return super(GnuTLS.Error, self).__init__(
598
578
                message, *args)
599
579
 
600
580
    class CertificateSecurityError(Error):
601
581
        pass
602
582
 
603
583
    # Classes
604
 
    class Credentials:
 
584
    class Credentials(object):
605
585
        def __init__(self):
606
586
            self._c_object = gnutls.certificate_credentials_t()
607
587
            gnutls.certificate_allocate_credentials(
611
591
        def __del__(self):
612
592
            gnutls.certificate_free_credentials(self._c_object)
613
593
 
614
 
    class ClientSession:
 
594
    class ClientSession(object):
615
595
        def __init__(self, socket, credentials=None):
616
596
            self._c_object = gnutls.session_t()
617
 
            gnutls_flags = gnutls.CLIENT
618
 
            if gnutls.check_version(b"3.5.6"):
619
 
                gnutls_flags |= gnutls.NO_TICKETS
620
 
            if gnutls.has_rawpk:
621
 
                gnutls_flags |= gnutls.ENABLE_RAWPK
622
 
            gnutls.init(ctypes.byref(self._c_object), gnutls_flags)
623
 
            del gnutls_flags
 
597
            gnutls.init(ctypes.byref(self._c_object), gnutls.CLIENT)
624
598
            gnutls.set_default_priority(self._c_object)
625
599
            gnutls.transport_set_ptr(self._c_object, socket.fileno())
626
600
            gnutls.handshake_set_private_extensions(self._c_object,
758
732
    check_version.argtypes = [ctypes.c_char_p]
759
733
    check_version.restype = ctypes.c_char_p
760
734
 
761
 
    _need_version = b"3.3.0"
762
 
    if check_version(_need_version) is None:
763
 
        raise self.Error("Needs GnuTLS {} or later"
764
 
                         .format(_need_version))
765
 
 
766
 
    _tls_rawpk_version = b"3.6.6"
767
 
    has_rawpk = bool(check_version(_tls_rawpk_version))
768
 
 
769
 
    if has_rawpk:
770
 
        # Types
771
 
        class pubkey_st(ctypes.Structure):
772
 
            _fields = []
773
 
        pubkey_t = ctypes.POINTER(pubkey_st)
774
 
 
775
 
        x509_crt_fmt_t = ctypes.c_int
776
 
 
777
 
        # All the function declarations below are from gnutls/abstract.h
778
 
        pubkey_init = _library.gnutls_pubkey_init
779
 
        pubkey_init.argtypes = [ctypes.POINTER(pubkey_t)]
780
 
        pubkey_init.restype = _error_code
781
 
 
782
 
        pubkey_import = _library.gnutls_pubkey_import
783
 
        pubkey_import.argtypes = [pubkey_t, ctypes.POINTER(datum_t),
784
 
                                  x509_crt_fmt_t]
785
 
        pubkey_import.restype = _error_code
786
 
 
787
 
        pubkey_get_key_id = _library.gnutls_pubkey_get_key_id
788
 
        pubkey_get_key_id.argtypes = [pubkey_t, ctypes.c_int,
789
 
                                      ctypes.POINTER(ctypes.c_ubyte),
790
 
                                      ctypes.POINTER(ctypes.c_size_t)]
791
 
        pubkey_get_key_id.restype = _error_code
792
 
 
793
 
        pubkey_deinit = _library.gnutls_pubkey_deinit
794
 
        pubkey_deinit.argtypes = [pubkey_t]
795
 
        pubkey_deinit.restype = None
796
 
    else:
797
 
        # All the function declarations below are from gnutls/openpgp.h
798
 
 
799
 
        openpgp_crt_init = _library.gnutls_openpgp_crt_init
800
 
        openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
801
 
        openpgp_crt_init.restype = _error_code
802
 
 
803
 
        openpgp_crt_import = _library.gnutls_openpgp_crt_import
804
 
        openpgp_crt_import.argtypes = [openpgp_crt_t,
805
 
                                       ctypes.POINTER(datum_t),
806
 
                                       openpgp_crt_fmt_t]
807
 
        openpgp_crt_import.restype = _error_code
808
 
 
809
 
        openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
810
 
        openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
811
 
                                            ctypes.POINTER(ctypes.c_uint)]
812
 
        openpgp_crt_verify_self.restype = _error_code
813
 
 
814
 
        openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
815
 
        openpgp_crt_deinit.argtypes = [openpgp_crt_t]
816
 
        openpgp_crt_deinit.restype = None
817
 
 
818
 
        openpgp_crt_get_fingerprint = (
819
 
            _library.gnutls_openpgp_crt_get_fingerprint)
820
 
        openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
821
 
                                                ctypes.c_void_p,
822
 
                                                ctypes.POINTER(
823
 
                                                    ctypes.c_size_t)]
824
 
        openpgp_crt_get_fingerprint.restype = _error_code
825
 
 
826
 
    if check_version(b"3.6.4"):
827
 
        certificate_type_get2 = _library.gnutls_certificate_type_get2
828
 
        certificate_type_get2.argtypes = [session_t, ctypes.c_int]
829
 
        certificate_type_get2.restype = _error_code
 
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
830
763
 
831
764
    # Remove non-public functions
832
765
    del _error_code, _retry_on_error
 
766
# Create the global "gnutls" object, simulating a module
 
767
gnutls = GnuTLS()
833
768
 
834
769
 
835
770
def call_pipe(connection,       # : multiprocessing.Connection
843
778
    connection.close()
844
779
 
845
780
 
846
 
class Client:
 
781
class Client(object):
847
782
    """A representation of a client host served by this server.
848
783
 
849
784
    Attributes:
850
785
    approved:   bool(); 'None' if not yet approved/disapproved
851
786
    approval_delay: datetime.timedelta(); Time to wait for approval
852
787
    approval_duration: datetime.timedelta(); Duration of one approval
853
 
    checker: multiprocessing.Process(); a running checker process used
854
 
             to see if the client lives. 'None' if no process is
855
 
             running.
 
788
    checker:    subprocess.Popen(); a running checker process used
 
789
                                    to see if the client lives.
 
790
                                    'None' if no process is running.
856
791
    checker_callback_tag: a GLib event source tag, or None
857
792
    checker_command: string; External command which is run to check
858
793
                     if client lives.  %() expansions are done at
866
801
    disable_initiator_tag: a GLib event source tag, or None
867
802
    enabled:    bool()
868
803
    fingerprint: string (40 or 32 hexadecimal digits); used to
869
 
                 uniquely identify an OpenPGP client
870
 
    key_id: string (64 hexadecimal digits); used to uniquely identify
871
 
            a client using raw public keys
 
804
                 uniquely identify the client
872
805
    host:       string; available for use by the checker command
873
806
    interval:   datetime.timedelta(); How often to start a new checker
874
807
    last_approval_request: datetime.datetime(); (UTC) or None
892
825
    """
893
826
 
894
827
    runtime_expansions = ("approval_delay", "approval_duration",
895
 
                          "created", "enabled", "expires", "key_id",
 
828
                          "created", "enabled", "expires",
896
829
                          "fingerprint", "host", "interval",
897
830
                          "last_approval_request", "last_checked_ok",
898
831
                          "last_enabled", "name", "timeout")
928
861
            client["enabled"] = config.getboolean(client_name,
929
862
                                                  "enabled")
930
863
 
931
 
            # Uppercase and remove spaces from key_id and fingerprint
932
 
            # for later comparison purposes with return value from the
933
 
            # key_id() and fingerprint() functions
934
 
            client["key_id"] = (section.get("key_id", "").upper()
935
 
                                .replace(" ", ""))
 
864
            # Uppercase and remove spaces from fingerprint for later
 
865
            # comparison purposes with return value from the
 
866
            # fingerprint() function
936
867
            client["fingerprint"] = (section["fingerprint"].upper()
937
868
                                     .replace(" ", ""))
938
869
            if "secret" in section:
982
913
            self.expires = None
983
914
 
984
915
        logger.debug("Creating client %r", self.name)
985
 
        logger.debug("  Key ID: %s", self.key_id)
986
916
        logger.debug("  Fingerprint: %s", self.fingerprint)
987
917
        self.created = settings.get("created",
988
918
                                    datetime.datetime.utcnow())
1052
982
        if self.checker_initiator_tag is not None:
1053
983
            GLib.source_remove(self.checker_initiator_tag)
1054
984
        self.checker_initiator_tag = GLib.timeout_add(
1055
 
            random.randrange(int(self.interval.total_seconds() * 1000
1056
 
                                 + 1)),
 
985
            int(self.interval.total_seconds() * 1000),
1057
986
            self.start_checker)
1058
987
        # Schedule a disable() when 'timeout' has passed
1059
988
        if self.disable_initiator_tag is not None:
1066
995
    def checker_callback(self, source, condition, connection,
1067
996
                         command):
1068
997
        """The checker has completed, so take appropriate actions."""
 
998
        self.checker_callback_tag = None
 
999
        self.checker = None
1069
1000
        # Read return code from connection (see call_pipe)
1070
1001
        returncode = connection.recv()
1071
1002
        connection.close()
1072
 
        if self.checker is not None:
1073
 
            self.checker.join()
1074
 
        self.checker_callback_tag = None
1075
 
        self.checker = None
1076
1003
 
1077
1004
        if returncode >= 0:
1078
1005
            self.last_checker_status = returncode
1134
1061
        if self.checker is None:
1135
1062
            # Escape attributes for the shell
1136
1063
            escaped_attrs = {
1137
 
                attr: shlex.quote(str(getattr(self, attr)))
 
1064
                attr: re.escape(str(getattr(self, attr)))
1138
1065
                for attr in self.runtime_expansions}
1139
1066
            try:
1140
1067
                command = self.checker_command % escaped_attrs
1167
1094
                kwargs=popen_args)
1168
1095
            self.checker.start()
1169
1096
            self.checker_callback_tag = GLib.io_add_watch(
1170
 
                GLib.IOChannel.unix_new(pipe[0].fileno()),
1171
 
                GLib.PRIORITY_DEFAULT, GLib.IO_IN,
 
1097
                pipe[0].fileno(), GLib.IO_IN,
1172
1098
                self.checker_callback, pipe[0], command)
1173
1099
        # Re-run this periodically if run by GLib.timeout_add
1174
1100
        return True
1429
1355
                raise ValueError("Byte arrays not supported for non-"
1430
1356
                                 "'ay' signature {!r}"
1431
1357
                                 .format(prop._dbus_signature))
1432
 
            value = dbus.ByteArray(bytes(value))
 
1358
            value = dbus.ByteArray(b''.join(chr(byte)
 
1359
                                            for byte in value))
1433
1360
        prop(value)
1434
1361
 
1435
1362
    @dbus.service.method(dbus.PROPERTIES_IFACE,
2073
2000
    def Name_dbus_property(self):
2074
2001
        return dbus.String(self.name)
2075
2002
 
2076
 
    # KeyID - property
2077
 
    @dbus_annotations(
2078
 
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
2079
 
    @dbus_service_property(_interface, signature="s", access="read")
2080
 
    def KeyID_dbus_property(self):
2081
 
        return dbus.String(self.key_id)
2082
 
 
2083
2003
    # Fingerprint - property
2084
2004
    @dbus_annotations(
2085
2005
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
2240
2160
    del _interface
2241
2161
 
2242
2162
 
2243
 
class ProxyClient:
2244
 
    def __init__(self, child_pipe, key_id, fpr, address):
 
2163
class ProxyClient(object):
 
2164
    def __init__(self, child_pipe, fpr, address):
2245
2165
        self._pipe = child_pipe
2246
 
        self._pipe.send(('init', key_id, fpr, address))
 
2166
        self._pipe.send(('init', fpr, address))
2247
2167
        if not self._pipe.recv():
2248
 
            raise KeyError(key_id or fpr)
 
2168
            raise KeyError(fpr)
2249
2169
 
2250
2170
    def __getattribute__(self, name):
2251
2171
        if name == '_pipe':
2318
2238
 
2319
2239
            approval_required = False
2320
2240
            try:
2321
 
                if gnutls.has_rawpk:
2322
 
                    fpr = b""
2323
 
                    try:
2324
 
                        key_id = self.key_id(
2325
 
                            self.peer_certificate(session))
2326
 
                    except (TypeError, gnutls.Error) as error:
2327
 
                        logger.warning("Bad certificate: %s", error)
2328
 
                        return
2329
 
                    logger.debug("Key ID: %s", key_id)
2330
 
 
2331
 
                else:
2332
 
                    key_id = b""
2333
 
                    try:
2334
 
                        fpr = self.fingerprint(
2335
 
                            self.peer_certificate(session))
2336
 
                    except (TypeError, gnutls.Error) as error:
2337
 
                        logger.warning("Bad certificate: %s", error)
2338
 
                        return
2339
 
                    logger.debug("Fingerprint: %s", fpr)
2340
 
 
2341
 
                try:
2342
 
                    client = ProxyClient(child_pipe, key_id, fpr,
 
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,
2343
2251
                                         self.client_address)
2344
2252
                except KeyError:
2345
2253
                    return
2422
2330
 
2423
2331
    @staticmethod
2424
2332
    def peer_certificate(session):
2425
 
        "Return the peer's certificate as a bytestring"
2426
 
        try:
2427
 
            cert_type = gnutls.certificate_type_get2(session._c_object,
2428
 
                                                     gnutls.CTYPE_PEERS)
2429
 
        except AttributeError:
2430
 
            cert_type = gnutls.certificate_type_get(session._c_object)
2431
 
        if gnutls.has_rawpk:
2432
 
            valid_cert_types = frozenset((gnutls.CRT_RAWPK,))
2433
 
        else:
2434
 
            valid_cert_types = frozenset((gnutls.CRT_OPENPGP,))
2435
 
        # If not a valid certificate type...
2436
 
        if cert_type not in valid_cert_types:
2437
 
            logger.info("Cert type %r not in %r", cert_type,
2438
 
                        valid_cert_types)
 
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):
2439
2337
            # ...return invalid data
2440
2338
            return b""
2441
2339
        list_size = ctypes.c_uint(1)
2449
2347
        return ctypes.string_at(cert.data, cert.size)
2450
2348
 
2451
2349
    @staticmethod
2452
 
    def key_id(certificate):
2453
 
        "Convert a certificate bytestring to a hexdigit key ID"
2454
 
        # New GnuTLS "datum" with the public key
2455
 
        datum = gnutls.datum_t(
2456
 
            ctypes.cast(ctypes.c_char_p(certificate),
2457
 
                        ctypes.POINTER(ctypes.c_ubyte)),
2458
 
            ctypes.c_uint(len(certificate)))
2459
 
        # XXX all these need to be created in the gnutls "module"
2460
 
        # New empty GnuTLS certificate
2461
 
        pubkey = gnutls.pubkey_t()
2462
 
        gnutls.pubkey_init(ctypes.byref(pubkey))
2463
 
        # Import the raw public key into the certificate
2464
 
        gnutls.pubkey_import(pubkey,
2465
 
                             ctypes.byref(datum),
2466
 
                             gnutls.X509_FMT_DER)
2467
 
        # New buffer for the key ID
2468
 
        buf = ctypes.create_string_buffer(32)
2469
 
        buf_len = ctypes.c_size_t(len(buf))
2470
 
        # Get the key ID from the raw public key into the buffer
2471
 
        gnutls.pubkey_get_key_id(pubkey,
2472
 
                                 gnutls.KEYID_USE_SHA256,
2473
 
                                 ctypes.cast(ctypes.byref(buf),
2474
 
                                             ctypes.POINTER(ctypes.c_ubyte)),
2475
 
                                 ctypes.byref(buf_len))
2476
 
        # Deinit the certificate
2477
 
        gnutls.pubkey_deinit(pubkey)
2478
 
 
2479
 
        # Convert the buffer to a Python bytestring
2480
 
        key_id = ctypes.string_at(buf, buf_len.value)
2481
 
        # Convert the bytestring to hexadecimal notation
2482
 
        hex_key_id = binascii.hexlify(key_id).upper()
2483
 
        return hex_key_id
2484
 
 
2485
 
    @staticmethod
2486
2350
    def fingerprint(openpgp):
2487
2351
        "Convert an OpenPGP bytestring to a hexdigit fingerprint"
2488
2352
        # New GnuTLS "datum" with the OpenPGP public key
2502
2366
                                       ctypes.byref(crtverify))
2503
2367
        if crtverify.value != 0:
2504
2368
            gnutls.openpgp_crt_deinit(crt)
2505
 
            raise gnutls.CertificateSecurityError(code
2506
 
                                                  =crtverify.value)
 
2369
            raise gnutls.CertificateSecurityError("Verify failed")
2507
2370
        # New buffer for the fingerprint
2508
2371
        buf = ctypes.create_string_buffer(20)
2509
2372
        buf_len = ctypes.c_size_t()
2519
2382
        return hex_fpr
2520
2383
 
2521
2384
 
2522
 
class MultiprocessingMixIn:
 
2385
class MultiprocessingMixIn(object):
2523
2386
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
2524
2387
 
2525
2388
    def sub_process_main(self, request, address):
2537
2400
        return proc
2538
2401
 
2539
2402
 
2540
 
class MultiprocessingMixInWithPipe(MultiprocessingMixIn):
 
2403
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
2541
2404
    """ adds a pipe to the MixIn """
2542
2405
 
2543
2406
    def process_request(self, request, client_address):
2558
2421
 
2559
2422
 
2560
2423
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
2561
 
                     socketserver.TCPServer):
 
2424
                     socketserver.TCPServer, object):
2562
2425
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
2563
2426
 
2564
2427
    Attributes:
2637
2500
                    raise
2638
2501
        # Only bind(2) the socket if we really need to.
2639
2502
        if self.server_address[0] or self.server_address[1]:
2640
 
            if self.server_address[1]:
2641
 
                self.allow_reuse_address = True
2642
2503
            if not self.server_address[0]:
2643
2504
                if self.address_family == socket.AF_INET6:
2644
2505
                    any_address = "::"  # in6addr_any
2697
2558
    def add_pipe(self, parent_pipe, proc):
2698
2559
        # Call "handle_ipc" for both data and EOF events
2699
2560
        GLib.io_add_watch(
2700
 
            GLib.IOChannel.unix_new(parent_pipe.fileno()),
2701
 
            GLib.PRIORITY_DEFAULT, GLib.IO_IN | GLib.IO_HUP,
 
2561
            parent_pipe.fileno(),
 
2562
            GLib.IO_IN | GLib.IO_HUP,
2702
2563
            functools.partial(self.handle_ipc,
2703
2564
                              parent_pipe=parent_pipe,
2704
2565
                              proc=proc))
2718
2579
        command = request[0]
2719
2580
 
2720
2581
        if command == 'init':
2721
 
            key_id = request[1].decode("ascii")
2722
 
            fpr = request[2].decode("ascii")
2723
 
            address = request[3]
 
2582
            fpr = request[1].decode("ascii")
 
2583
            address = request[2]
2724
2584
 
2725
2585
            for c in self.clients.values():
2726
 
                if key_id == "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855":
2727
 
                    continue
2728
 
                if key_id and c.key_id == key_id:
2729
 
                    client = c
2730
 
                    break
2731
 
                if fpr and c.fingerprint == fpr:
 
2586
                if c.fingerprint == fpr:
2732
2587
                    client = c
2733
2588
                    break
2734
2589
            else:
2735
 
                logger.info("Client not found for key ID: %s, address"
2736
 
                            ": %s", key_id or fpr, address)
 
2590
                logger.info("Client not found for fingerprint: %s, ad"
 
2591
                            "dress: %s", fpr, address)
2737
2592
                if self.use_dbus:
2738
2593
                    # Emit D-Bus signal
2739
 
                    mandos_dbus_service.ClientNotFound(key_id or fpr,
 
2594
                    mandos_dbus_service.ClientNotFound(fpr,
2740
2595
                                                       address[0])
2741
2596
                parent_pipe.send(False)
2742
2597
                return False
2743
2598
 
2744
2599
            GLib.io_add_watch(
2745
 
                GLib.IOChannel.unix_new(parent_pipe.fileno()),
2746
 
                GLib.PRIORITY_DEFAULT, GLib.IO_IN | GLib.IO_HUP,
 
2600
                parent_pipe.fileno(),
 
2601
                GLib.IO_IN | GLib.IO_HUP,
2747
2602
                functools.partial(self.handle_ipc,
2748
2603
                                  parent_pipe=parent_pipe,
2749
2604
                                  proc=proc,
2764
2619
        if command == 'getattr':
2765
2620
            attrname = request[1]
2766
2621
            if isinstance(client_object.__getattribute__(attrname),
2767
 
                          collections.abc.Callable):
 
2622
                          collections.Callable):
2768
2623
                parent_pipe.send(('function', ))
2769
2624
            else:
2770
2625
                parent_pipe.send((
2781
2636
def rfc3339_duration_to_delta(duration):
2782
2637
    """Parse an RFC 3339 "duration" and return a datetime.timedelta
2783
2638
 
2784
 
    >>> rfc3339_duration_to_delta("P7D") == datetime.timedelta(7)
2785
 
    True
2786
 
    >>> rfc3339_duration_to_delta("PT60S") == datetime.timedelta(0, 60)
2787
 
    True
2788
 
    >>> rfc3339_duration_to_delta("PT60M") == datetime.timedelta(0, 3600)
2789
 
    True
2790
 
    >>> rfc3339_duration_to_delta("PT24H") == datetime.timedelta(1)
2791
 
    True
2792
 
    >>> rfc3339_duration_to_delta("P1W") == datetime.timedelta(7)
2793
 
    True
2794
 
    >>> rfc3339_duration_to_delta("PT5M30S") == datetime.timedelta(0, 330)
2795
 
    True
2796
 
    >>> rfc3339_duration_to_delta("P1DT3M20S") == datetime.timedelta(1, 200)
2797
 
    True
 
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)
2798
2653
    """
2799
2654
 
2800
2655
    # Parsing an RFC 3339 duration with regular expressions is not
2880
2735
def string_to_delta(interval):
2881
2736
    """Parse a string and return a datetime.timedelta
2882
2737
 
2883
 
    >>> string_to_delta('7d') == datetime.timedelta(7)
2884
 
    True
2885
 
    >>> string_to_delta('60s') == datetime.timedelta(0, 60)
2886
 
    True
2887
 
    >>> string_to_delta('60m') == datetime.timedelta(0, 3600)
2888
 
    True
2889
 
    >>> string_to_delta('24h') == datetime.timedelta(1)
2890
 
    True
2891
 
    >>> string_to_delta('1w') == datetime.timedelta(7)
2892
 
    True
2893
 
    >>> string_to_delta('5m 30s') == datetime.timedelta(0, 330)
2894
 
    True
 
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)
2895
2750
    """
2896
2751
 
2897
2752
    try:
2999
2854
 
3000
2855
    options = parser.parse_args()
3001
2856
 
 
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
 
3002
2862
    # Default values for config file for server-global settings
3003
 
    if gnutls.has_rawpk:
3004
 
        priority = ("SECURE128:!CTYPE-X.509:+CTYPE-RAWPK:!RSA"
3005
 
                    ":!VERS-ALL:+VERS-TLS1.3:%PROFILE_ULTRA")
3006
 
    else:
3007
 
        priority = ("SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
3008
 
                    ":+SIGN-DSA-SHA256")
3009
2863
    server_defaults = {"interface": "",
3010
2864
                       "address": "",
3011
2865
                       "port": "",
3012
2866
                       "debug": "False",
3013
 
                       "priority": priority,
 
2867
                       "priority":
 
2868
                       "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
 
2869
                       ":+SIGN-DSA-SHA256",
3014
2870
                       "servicename": "Mandos",
3015
2871
                       "use_dbus": "True",
3016
2872
                       "use_ipv6": "True",
3021
2877
                       "foreground": "False",
3022
2878
                       "zeroconf": "True",
3023
2879
                       }
3024
 
    del priority
3025
2880
 
3026
2881
    # Parse config file for server-global settings
3027
 
    server_config = configparser.ConfigParser(server_defaults)
 
2882
    server_config = configparser.SafeConfigParser(server_defaults)
3028
2883
    del server_defaults
3029
2884
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
3030
 
    # Convert the ConfigParser object to a dict
 
2885
    # Convert the SafeConfigParser object to a dict
3031
2886
    server_settings = server_config.defaults()
3032
2887
    # Use the appropriate methods on the non-string config options
3033
2888
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
3105
2960
                                  server_settings["servicename"])))
3106
2961
 
3107
2962
    # Parse config file with clients
3108
 
    client_config = configparser.ConfigParser(Client.client_defaults)
 
2963
    client_config = configparser.SafeConfigParser(Client
 
2964
                                                  .client_defaults)
3109
2965
    client_config.read(os.path.join(server_settings["configdir"],
3110
2966
                                    "clients.conf"))
3111
2967
 
3182
3038
        # Close all input and output, do double fork, etc.
3183
3039
        daemon()
3184
3040
 
3185
 
    if gi.version_info < (3, 10, 2):
3186
 
        # multiprocessing will use threads, so before we use GLib we
3187
 
        # need to inform GLib that threads will be used.
3188
 
        GLib.threads_init()
 
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()
3189
3044
 
3190
3045
    global main_loop
3191
3046
    # From the Avahi example code
3267
3122
                             if isinstance(s, bytes)
3268
3123
                             else s) for s in
3269
3124
                            value["client_structure"]]
3270
 
                        # .name, .host, and .checker_command
3271
 
                        for k in ("name", "host", "checker_command"):
 
3125
                        # .name & .host
 
3126
                        for k in ("name", "host"):
3272
3127
                            if isinstance(value[k], bytes):
3273
3128
                                value[k] = value[k].decode("utf-8")
3274
 
                        if "key_id" not in value:
3275
 
                            value["key_id"] = ""
3276
 
                        elif "fingerprint" not in value:
3277
 
                            value["fingerprint"] = ""
3278
3129
                    #  old_client_settings
3279
3130
                    # .keys()
3280
3131
                    old_client_settings = {
3284
3135
                        for key, value in
3285
3136
                        bytes_old_client_settings.items()}
3286
3137
                    del bytes_old_client_settings
3287
 
                    # .host and .checker_command
 
3138
                    # .host
3288
3139
                    for value in old_client_settings.values():
3289
 
                        for attribute in ("host", "checker_command"):
3290
 
                            if isinstance(value[attribute], bytes):
3291
 
                                value[attribute] = (value[attribute]
3292
 
                                                    .decode("utf-8"))
 
3140
                        if isinstance(value["host"], bytes):
 
3141
                            value["host"] = (value["host"]
 
3142
                                             .decode("utf-8"))
3293
3143
            os.remove(stored_state_path)
3294
3144
        except IOError as e:
3295
3145
            if e.errno == errno.ENOENT:
3418
3268
                pass
3419
3269
 
3420
3270
            @dbus.service.signal(_interface, signature="ss")
3421
 
            def ClientNotFound(self, key_id, address):
 
3271
            def ClientNotFound(self, fingerprint, address):
3422
3272
                "D-Bus signal"
3423
3273
                pass
3424
3274
 
3620
3470
                sys.exit(1)
3621
3471
            # End of Avahi example code
3622
3472
 
3623
 
        GLib.io_add_watch(
3624
 
            GLib.IOChannel.unix_new(tcp_server.fileno()),
3625
 
            GLib.PRIORITY_DEFAULT, GLib.IO_IN,
3626
 
            lambda *args, **kwargs: (tcp_server.handle_request
3627
 
                                     (*args[2:], **kwargs) or True))
 
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))
3628
3477
 
3629
3478
        logger.debug("Starting main loop")
3630
3479
        main_loop.run()
3640
3489
    # Must run before the D-Bus bus name gets deregistered
3641
3490
    cleanup()
3642
3491
 
3643
 
 
3644
 
def should_only_run_tests():
3645
 
    parser = argparse.ArgumentParser(add_help=False)
3646
 
    parser.add_argument("--check", action='store_true')
3647
 
    args, unknown_args = parser.parse_known_args()
3648
 
    run_tests = args.check
3649
 
    if run_tests:
3650
 
        # Remove --check argument from sys.argv
3651
 
        sys.argv[1:] = unknown_args
3652
 
    return run_tests
3653
 
 
3654
 
# Add all tests from doctest strings
3655
 
def load_tests(loader, tests, none):
3656
 
    import doctest
3657
 
    tests.addTests(doctest.DocTestSuite())
3658
 
    return tests
3659
3492
 
3660
3493
if __name__ == '__main__':
3661
 
    try:
3662
 
        if should_only_run_tests():
3663
 
            # Call using ./mandos --check [--verbose]
3664
 
            unittest.main()
3665
 
        else:
3666
 
            main()
3667
 
    finally:
3668
 
        logging.shutdown()
 
3494
    main()