/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2016-08-25 17:37:05 UTC
  • Revision ID: teddy@recompile.se-20160825173705-q2v6lban8ph9uw75
PEP8 compliance: mandos

* mandos: Add PEP8 compliance (as per the "pycodestyle" tool).

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-2020 Teddy Hogeborn
15
 
# Copyright © 2008-2020 Björn Påhlsson
16
 
#
17
 
# This file is part of Mandos.
18
 
#
19
 
# Mandos is free software: you can redistribute it and/or modify it
20
 
# under the terms of the GNU General Public License as published by
 
14
# Copyright © 2008-2016 Teddy Hogeborn
 
15
# Copyright © 2008-2016 Björn Påhlsson
 
16
#
 
17
# This program is free software: you can redistribute it and/or modify
 
18
# it under the terms of the GNU General Public License as published by
21
19
# the Free Software Foundation, either version 3 of the License, or
22
20
# (at your option) any later version.
23
21
#
24
 
#     Mandos is distributed in the hope that it will be useful, but
25
 
#     WITHOUT ANY WARRANTY; without even the implied warranty of
 
22
#     This program is distributed in the hope that it will be useful,
 
23
#     but WITHOUT ANY WARRANTY; without even the implied warranty of
26
24
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27
25
#     GNU General Public License for more details.
28
26
#
29
27
# You should have received a copy of the GNU General Public License
30
 
# along with Mandos.  If not, see <http://www.gnu.org/licenses/>.
 
28
# along with this program.  If not, see
 
29
# <http://www.gnu.org/licenses/>.
31
30
#
32
31
# Contact the authors at <mandos@recompile.se>.
33
32
#
77
76
import itertools
78
77
import collections
79
78
import codecs
80
 
import unittest
81
 
import random
82
 
import shlex
83
79
 
84
80
import dbus
85
81
import dbus.service
86
 
import gi
87
82
from gi.repository import GLib
88
83
from dbus.mainloop.glib import DBusGMainLoop
89
84
import ctypes
91
86
import xml.dom.minidom
92
87
import inspect
93
88
 
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
89
# Try to find the value of SO_BINDTODEVICE:
119
90
try:
120
91
    # This is where SO_BINDTODEVICE is in Python 3.3 (or 3.4?) and
140
111
            # No value found
141
112
            SO_BINDTODEVICE = None
142
113
 
143
 
if sys.version_info < (3, 2):
144
 
    configparser.Configparser = configparser.SafeConfigParser
 
114
if sys.version_info.major == 2:
 
115
    str = unicode
145
116
 
146
 
version = "1.8.12"
 
117
version = "1.7.10"
147
118
stored_state_file = "clients.pickle"
148
119
 
149
120
logger = logging.getLogger()
150
 
logging.captureWarnings(True)   # Show warnings via the logging system
151
121
syslogger = None
152
122
 
153
123
try:
208
178
    pass
209
179
 
210
180
 
211
 
class PGPEngine:
 
181
class PGPEngine(object):
212
182
    """A simple class for OpenPGP symmetric encryption & decryption"""
213
183
 
214
184
    def __init__(self):
218
188
            output = subprocess.check_output(["gpgconf"])
219
189
            for line in output.splitlines():
220
190
                name, text, path = line.split(b":")
221
 
                if name == b"gpg":
 
191
                if name == "gpg":
222
192
                    self.gpg = path
223
193
                    break
224
194
        except OSError as e:
229
199
                          '--force-mdc',
230
200
                          '--quiet']
231
201
        # Only GPG version 1 has the --no-use-agent option.
232
 
        if self.gpg == b"gpg" or self.gpg.endswith(b"/gpg"):
 
202
        if self.gpg == "gpg" or self.gpg.endswith("/gpg"):
233
203
            self.gnupgargs.append("--no-use-agent")
234
204
 
235
205
    def __enter__(self):
304
274
 
305
275
 
306
276
# 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."""
 
277
class Avahi(object):
 
278
    """This isn't so much a class as it is a module-like namespace.
 
279
    It is instantiated once, and simulates having an Avahi module."""
309
280
    IF_UNSPEC = -1               # avahi-common/address.h
310
281
    PROTO_UNSPEC = -1            # avahi-common/address.h
311
282
    PROTO_INET = 0               # avahi-common/address.h
315
286
    DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
316
287
    DBUS_PATH_SERVER = "/"
317
288
 
318
 
    @staticmethod
319
 
    def string_array_to_txt_array(t):
 
289
    def string_array_to_txt_array(self, t):
320
290
        return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
321
291
                           for s in t), signature="ay")
322
292
    ENTRY_GROUP_ESTABLISHED = 2  # avahi-common/defs.h
327
297
    SERVER_RUNNING = 2           # avahi-common/defs.h
328
298
    SERVER_COLLISION = 3         # avahi-common/defs.h
329
299
    SERVER_FAILURE = 4           # avahi-common/defs.h
 
300
avahi = Avahi()
330
301
 
331
302
 
332
303
class AvahiError(Exception):
344
315
    pass
345
316
 
346
317
 
347
 
class AvahiService:
 
318
class AvahiService(object):
348
319
    """An Avahi (Zeroconf) service.
349
320
 
350
321
    Attributes:
524
495
class AvahiServiceToSyslog(AvahiService):
525
496
    def rename(self, *args, **kwargs):
526
497
        """Add the new name to the syslog messages"""
527
 
        ret = super(AvahiServiceToSyslog, self).rename(*args, **kwargs)
 
498
        ret = AvahiService.rename(self, *args, **kwargs)
528
499
        syslogger.setFormatter(logging.Formatter(
529
500
            'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
530
501
            .format(self.name)))
532
503
 
533
504
 
534
505
# 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."""
537
 
 
538
 
    library = ctypes.util.find_library("gnutls")
539
 
    if library is None:
540
 
        library = ctypes.util.find_library("gnutls-deb0")
541
 
    _library = ctypes.cdll.LoadLibrary(library)
542
 
    del library
 
506
class GnuTLS(object):
 
507
    """This isn't so much a class as it is a module-like namespace.
 
508
    It is instantiated once, and simulates having a GnuTLS module."""
 
509
 
 
510
    _library = ctypes.cdll.LoadLibrary(
 
511
        ctypes.util.find_library("gnutls"))
 
512
    _need_version = b"3.3.0"
 
513
 
 
514
    def __init__(self):
 
515
        # Need to use class name "GnuTLS" here, since this method is
 
516
        # called before the assignment to the "gnutls" global variable
 
517
        # happens.
 
518
        if GnuTLS.check_version(self._need_version) is None:
 
519
            raise GnuTLS.Error("Needs GnuTLS {} or later"
 
520
                               .format(self._need_version))
543
521
 
544
522
    # Unless otherwise indicated, the constants and types below are
545
523
    # all from the gnutls/gnutls.h C header file.
549
527
    E_INTERRUPTED = -52
550
528
    E_AGAIN = -28
551
529
    CRT_OPENPGP = 2
552
 
    CRT_RAWPK = 3
553
530
    CLIENT = 2
554
531
    SHUT_RDWR = 0
555
532
    CRD_CERTIFICATE = 1
556
533
    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
534
    OPENPGP_FMT_RAW = 0         # gnutls/openpgp.h
563
535
 
564
536
    # Types
587
559
 
588
560
    # Exceptions
589
561
    class Error(Exception):
 
562
        # We need to use the class name "GnuTLS" here, since this
 
563
        # exception might be raised from within GnuTLS.__init__,
 
564
        # which is called before the assignment to the "gnutls"
 
565
        # global variable has happened.
590
566
        def __init__(self, message=None, code=None, args=()):
591
567
            # Default usage is by a message string, but if a return
592
568
            # code is passed, convert it to a string with
593
569
            # gnutls.strerror()
594
570
            self.code = code
595
571
            if message is None and code is not None:
596
 
                message = gnutls.strerror(code)
597
 
            return super(gnutls.Error, self).__init__(
 
572
                message = GnuTLS.strerror(code)
 
573
            return super(GnuTLS.Error, self).__init__(
598
574
                message, *args)
599
575
 
600
576
    class CertificateSecurityError(Error):
601
577
        pass
602
578
 
603
579
    # Classes
604
 
    class Credentials:
 
580
    class Credentials(object):
605
581
        def __init__(self):
606
582
            self._c_object = gnutls.certificate_credentials_t()
607
583
            gnutls.certificate_allocate_credentials(
611
587
        def __del__(self):
612
588
            gnutls.certificate_free_credentials(self._c_object)
613
589
 
614
 
    class ClientSession:
 
590
    class ClientSession(object):
615
591
        def __init__(self, socket, credentials=None):
616
592
            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
 
593
            gnutls.init(ctypes.byref(self._c_object), gnutls.CLIENT)
624
594
            gnutls.set_default_priority(self._c_object)
625
595
            gnutls.transport_set_ptr(self._c_object, socket.fileno())
626
596
            gnutls.handshake_set_private_extensions(self._c_object,
758
728
    check_version.argtypes = [ctypes.c_char_p]
759
729
    check_version.restype = ctypes.c_char_p
760
730
 
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
 
731
    # All the function declarations below are from gnutls/openpgp.h
 
732
 
 
733
    openpgp_crt_init = _library.gnutls_openpgp_crt_init
 
734
    openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
 
735
    openpgp_crt_init.restype = _error_code
 
736
 
 
737
    openpgp_crt_import = _library.gnutls_openpgp_crt_import
 
738
    openpgp_crt_import.argtypes = [openpgp_crt_t,
 
739
                                   ctypes.POINTER(datum_t),
 
740
                                   openpgp_crt_fmt_t]
 
741
    openpgp_crt_import.restype = _error_code
 
742
 
 
743
    openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
 
744
    openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
 
745
                                        ctypes.POINTER(ctypes.c_uint)]
 
746
    openpgp_crt_verify_self.restype = _error_code
 
747
 
 
748
    openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
 
749
    openpgp_crt_deinit.argtypes = [openpgp_crt_t]
 
750
    openpgp_crt_deinit.restype = None
 
751
 
 
752
    openpgp_crt_get_fingerprint = (
 
753
        _library.gnutls_openpgp_crt_get_fingerprint)
 
754
    openpgp_crt_get_fingerprint.argtypes = [openpgp_crt_t,
 
755
                                            ctypes.c_void_p,
 
756
                                            ctypes.POINTER(
 
757
                                                ctypes.c_size_t)]
 
758
    openpgp_crt_get_fingerprint.restype = _error_code
830
759
 
831
760
    # Remove non-public functions
832
761
    del _error_code, _retry_on_error
 
762
# Create the global "gnutls" object, simulating a module
 
763
gnutls = GnuTLS()
833
764
 
834
765
 
835
766
def call_pipe(connection,       # : multiprocessing.Connection
843
774
    connection.close()
844
775
 
845
776
 
846
 
class Client:
 
777
class Client(object):
847
778
    """A representation of a client host served by this server.
848
779
 
849
780
    Attributes:
850
781
    approved:   bool(); 'None' if not yet approved/disapproved
851
782
    approval_delay: datetime.timedelta(); Time to wait for approval
852
783
    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.
 
784
    checker:    subprocess.Popen(); a running checker process used
 
785
                                    to see if the client lives.
 
786
                                    'None' if no process is running.
856
787
    checker_callback_tag: a GLib event source tag, or None
857
788
    checker_command: string; External command which is run to check
858
789
                     if client lives.  %() expansions are done at
866
797
    disable_initiator_tag: a GLib event source tag, or None
867
798
    enabled:    bool()
868
799
    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
 
800
                 uniquely identify the client
872
801
    host:       string; available for use by the checker command
873
802
    interval:   datetime.timedelta(); How often to start a new checker
874
803
    last_approval_request: datetime.datetime(); (UTC) or None
892
821
    """
893
822
 
894
823
    runtime_expansions = ("approval_delay", "approval_duration",
895
 
                          "created", "enabled", "expires", "key_id",
 
824
                          "created", "enabled", "expires",
896
825
                          "fingerprint", "host", "interval",
897
826
                          "last_approval_request", "last_checked_ok",
898
827
                          "last_enabled", "name", "timeout")
928
857
            client["enabled"] = config.getboolean(client_name,
929
858
                                                  "enabled")
930
859
 
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(" ", ""))
 
860
            # Uppercase and remove spaces from fingerprint for later
 
861
            # comparison purposes with return value from the
 
862
            # fingerprint() function
936
863
            client["fingerprint"] = (section["fingerprint"].upper()
937
864
                                     .replace(" ", ""))
938
865
            if "secret" in section:
982
909
            self.expires = None
983
910
 
984
911
        logger.debug("Creating client %r", self.name)
985
 
        logger.debug("  Key ID: %s", self.key_id)
986
912
        logger.debug("  Fingerprint: %s", self.fingerprint)
987
913
        self.created = settings.get("created",
988
914
                                    datetime.datetime.utcnow())
1052
978
        if self.checker_initiator_tag is not None:
1053
979
            GLib.source_remove(self.checker_initiator_tag)
1054
980
        self.checker_initiator_tag = GLib.timeout_add(
1055
 
            random.randrange(int(self.interval.total_seconds() * 1000
1056
 
                                 + 1)),
 
981
            int(self.interval.total_seconds() * 1000),
1057
982
            self.start_checker)
1058
983
        # Schedule a disable() when 'timeout' has passed
1059
984
        if self.disable_initiator_tag is not None:
1066
991
    def checker_callback(self, source, condition, connection,
1067
992
                         command):
1068
993
        """The checker has completed, so take appropriate actions."""
 
994
        self.checker_callback_tag = None
 
995
        self.checker = None
1069
996
        # Read return code from connection (see call_pipe)
1070
997
        returncode = connection.recv()
1071
998
        connection.close()
1072
 
        if self.checker is not None:
1073
 
            self.checker.join()
1074
 
        self.checker_callback_tag = None
1075
 
        self.checker = None
1076
999
 
1077
1000
        if returncode >= 0:
1078
1001
            self.last_checker_status = returncode
1134
1057
        if self.checker is None:
1135
1058
            # Escape attributes for the shell
1136
1059
            escaped_attrs = {
1137
 
                attr: shlex.quote(str(getattr(self, attr)))
 
1060
                attr: re.escape(str(getattr(self, attr)))
1138
1061
                for attr in self.runtime_expansions}
1139
1062
            try:
1140
1063
                command = self.checker_command % escaped_attrs
1167
1090
                kwargs=popen_args)
1168
1091
            self.checker.start()
1169
1092
            self.checker_callback_tag = GLib.io_add_watch(
1170
 
                GLib.IOChannel.unix_new(pipe[0].fileno()),
1171
 
                GLib.PRIORITY_DEFAULT, GLib.IO_IN,
 
1093
                pipe[0].fileno(), GLib.IO_IN,
1172
1094
                self.checker_callback, pipe[0], command)
1173
1095
        # Re-run this periodically if run by GLib.timeout_add
1174
1096
        return True
1429
1351
                raise ValueError("Byte arrays not supported for non-"
1430
1352
                                 "'ay' signature {!r}"
1431
1353
                                 .format(prop._dbus_signature))
1432
 
            value = dbus.ByteArray(bytes(value))
 
1354
            value = dbus.ByteArray(b''.join(chr(byte)
 
1355
                                            for byte in value))
1433
1356
        prop(value)
1434
1357
 
1435
1358
    @dbus.service.method(dbus.PROPERTIES_IFACE,
1535
1458
                         exc_info=error)
1536
1459
        return xmlstring
1537
1460
 
1538
 
 
1539
1461
try:
1540
1462
    dbus.OBJECT_MANAGER_IFACE
1541
1463
except AttributeError:
2073
1995
    def Name_dbus_property(self):
2074
1996
        return dbus.String(self.name)
2075
1997
 
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
1998
    # Fingerprint - property
2084
1999
    @dbus_annotations(
2085
2000
        {"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
2240
2155
    del _interface
2241
2156
 
2242
2157
 
2243
 
class ProxyClient:
2244
 
    def __init__(self, child_pipe, key_id, fpr, address):
 
2158
class ProxyClient(object):
 
2159
    def __init__(self, child_pipe, fpr, address):
2245
2160
        self._pipe = child_pipe
2246
 
        self._pipe.send(('init', key_id, fpr, address))
 
2161
        self._pipe.send(('init', fpr, address))
2247
2162
        if not self._pipe.recv():
2248
 
            raise KeyError(key_id or fpr)
 
2163
            raise KeyError(fpr)
2249
2164
 
2250
2165
    def __getattribute__(self, name):
2251
2166
        if name == '_pipe':
2318
2233
 
2319
2234
            approval_required = False
2320
2235
            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,
 
2236
                try:
 
2237
                    fpr = self.fingerprint(
 
2238
                        self.peer_certificate(session))
 
2239
                except (TypeError, gnutls.Error) as error:
 
2240
                    logger.warning("Bad certificate: %s", error)
 
2241
                    return
 
2242
                logger.debug("Fingerprint: %s", fpr)
 
2243
 
 
2244
                try:
 
2245
                    client = ProxyClient(child_pipe, fpr,
2343
2246
                                         self.client_address)
2344
2247
                except KeyError:
2345
2248
                    return
2422
2325
 
2423
2326
    @staticmethod
2424
2327
    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)
 
2328
        "Return the peer's OpenPGP certificate as a bytestring"
 
2329
        # If not an OpenPGP certificate...
 
2330
        if (gnutls.certificate_type_get(session._c_object)
 
2331
            != gnutls.CRT_OPENPGP):
2439
2332
            # ...return invalid data
2440
2333
            return b""
2441
2334
        list_size = ctypes.c_uint(1)
2449
2342
        return ctypes.string_at(cert.data, cert.size)
2450
2343
 
2451
2344
    @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
2345
    def fingerprint(openpgp):
2487
2346
        "Convert an OpenPGP bytestring to a hexdigit fingerprint"
2488
2347
        # New GnuTLS "datum" with the OpenPGP public key
2502
2361
                                       ctypes.byref(crtverify))
2503
2362
        if crtverify.value != 0:
2504
2363
            gnutls.openpgp_crt_deinit(crt)
2505
 
            raise gnutls.CertificateSecurityError(code
2506
 
                                                  =crtverify.value)
 
2364
            raise gnutls.CertificateSecurityError("Verify failed")
2507
2365
        # New buffer for the fingerprint
2508
2366
        buf = ctypes.create_string_buffer(20)
2509
2367
        buf_len = ctypes.c_size_t()
2519
2377
        return hex_fpr
2520
2378
 
2521
2379
 
2522
 
class MultiprocessingMixIn:
 
2380
class MultiprocessingMixIn(object):
2523
2381
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
2524
2382
 
2525
2383
    def sub_process_main(self, request, address):
2537
2395
        return proc
2538
2396
 
2539
2397
 
2540
 
class MultiprocessingMixInWithPipe(MultiprocessingMixIn):
 
2398
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
2541
2399
    """ adds a pipe to the MixIn """
2542
2400
 
2543
2401
    def process_request(self, request, client_address):
2558
2416
 
2559
2417
 
2560
2418
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
2561
 
                     socketserver.TCPServer):
 
2419
                     socketserver.TCPServer, object):
2562
2420
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
2563
2421
 
2564
2422
    Attributes:
2637
2495
                    raise
2638
2496
        # Only bind(2) the socket if we really need to.
2639
2497
        if self.server_address[0] or self.server_address[1]:
2640
 
            if self.server_address[1]:
2641
 
                self.allow_reuse_address = True
2642
2498
            if not self.server_address[0]:
2643
2499
                if self.address_family == socket.AF_INET6:
2644
2500
                    any_address = "::"  # in6addr_any
2697
2553
    def add_pipe(self, parent_pipe, proc):
2698
2554
        # Call "handle_ipc" for both data and EOF events
2699
2555
        GLib.io_add_watch(
2700
 
            GLib.IOChannel.unix_new(parent_pipe.fileno()),
2701
 
            GLib.PRIORITY_DEFAULT, GLib.IO_IN | GLib.IO_HUP,
 
2556
            parent_pipe.fileno(),
 
2557
            GLib.IO_IN | GLib.IO_HUP,
2702
2558
            functools.partial(self.handle_ipc,
2703
2559
                              parent_pipe=parent_pipe,
2704
2560
                              proc=proc))
2718
2574
        command = request[0]
2719
2575
 
2720
2576
        if command == 'init':
2721
 
            key_id = request[1].decode("ascii")
2722
 
            fpr = request[2].decode("ascii")
2723
 
            address = request[3]
 
2577
            fpr = request[1]
 
2578
            address = request[2]
2724
2579
 
2725
2580
            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:
 
2581
                if c.fingerprint == fpr:
2732
2582
                    client = c
2733
2583
                    break
2734
2584
            else:
2735
 
                logger.info("Client not found for key ID: %s, address"
2736
 
                            ": %s", key_id or fpr, address)
 
2585
                logger.info("Client not found for fingerprint: %s, ad"
 
2586
                            "dress: %s", fpr, address)
2737
2587
                if self.use_dbus:
2738
2588
                    # Emit D-Bus signal
2739
 
                    mandos_dbus_service.ClientNotFound(key_id or fpr,
 
2589
                    mandos_dbus_service.ClientNotFound(fpr,
2740
2590
                                                       address[0])
2741
2591
                parent_pipe.send(False)
2742
2592
                return False
2743
2593
 
2744
2594
            GLib.io_add_watch(
2745
 
                GLib.IOChannel.unix_new(parent_pipe.fileno()),
2746
 
                GLib.PRIORITY_DEFAULT, GLib.IO_IN | GLib.IO_HUP,
 
2595
                parent_pipe.fileno(),
 
2596
                GLib.IO_IN | GLib.IO_HUP,
2747
2597
                functools.partial(self.handle_ipc,
2748
2598
                                  parent_pipe=parent_pipe,
2749
2599
                                  proc=proc,
2764
2614
        if command == 'getattr':
2765
2615
            attrname = request[1]
2766
2616
            if isinstance(client_object.__getattribute__(attrname),
2767
 
                          collections.abc.Callable):
 
2617
                          collections.Callable):
2768
2618
                parent_pipe.send(('function', ))
2769
2619
            else:
2770
2620
                parent_pipe.send((
2781
2631
def rfc3339_duration_to_delta(duration):
2782
2632
    """Parse an RFC 3339 "duration" and return a datetime.timedelta
2783
2633
 
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
 
2634
    >>> rfc3339_duration_to_delta("P7D")
 
2635
    datetime.timedelta(7)
 
2636
    >>> rfc3339_duration_to_delta("PT60S")
 
2637
    datetime.timedelta(0, 60)
 
2638
    >>> rfc3339_duration_to_delta("PT60M")
 
2639
    datetime.timedelta(0, 3600)
 
2640
    >>> rfc3339_duration_to_delta("PT24H")
 
2641
    datetime.timedelta(1)
 
2642
    >>> rfc3339_duration_to_delta("P1W")
 
2643
    datetime.timedelta(7)
 
2644
    >>> rfc3339_duration_to_delta("PT5M30S")
 
2645
    datetime.timedelta(0, 330)
 
2646
    >>> rfc3339_duration_to_delta("P1DT3M20S")
 
2647
    datetime.timedelta(1, 200)
2798
2648
    """
2799
2649
 
2800
2650
    # Parsing an RFC 3339 duration with regular expressions is not
2880
2730
def string_to_delta(interval):
2881
2731
    """Parse a string and return a datetime.timedelta
2882
2732
 
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
 
2733
    >>> string_to_delta('7d')
 
2734
    datetime.timedelta(7)
 
2735
    >>> string_to_delta('60s')
 
2736
    datetime.timedelta(0, 60)
 
2737
    >>> string_to_delta('60m')
 
2738
    datetime.timedelta(0, 3600)
 
2739
    >>> string_to_delta('24h')
 
2740
    datetime.timedelta(1)
 
2741
    >>> string_to_delta('1w')
 
2742
    datetime.timedelta(7)
 
2743
    >>> string_to_delta('5m 30s')
 
2744
    datetime.timedelta(0, 330)
2895
2745
    """
2896
2746
 
2897
2747
    try:
2999
2849
 
3000
2850
    options = parser.parse_args()
3001
2851
 
 
2852
    if options.check:
 
2853
        import doctest
 
2854
        fail_count, test_count = doctest.testmod()
 
2855
        sys.exit(os.EX_OK if fail_count == 0 else 1)
 
2856
 
3002
2857
    # 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
2858
    server_defaults = {"interface": "",
3010
2859
                       "address": "",
3011
2860
                       "port": "",
3012
2861
                       "debug": "False",
3013
 
                       "priority": priority,
 
2862
                       "priority":
 
2863
                       "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
 
2864
                       ":+SIGN-DSA-SHA256",
3014
2865
                       "servicename": "Mandos",
3015
2866
                       "use_dbus": "True",
3016
2867
                       "use_ipv6": "True",
3021
2872
                       "foreground": "False",
3022
2873
                       "zeroconf": "True",
3023
2874
                       }
3024
 
    del priority
3025
2875
 
3026
2876
    # Parse config file for server-global settings
3027
 
    server_config = configparser.ConfigParser(server_defaults)
 
2877
    server_config = configparser.SafeConfigParser(server_defaults)
3028
2878
    del server_defaults
3029
2879
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
3030
 
    # Convert the ConfigParser object to a dict
 
2880
    # Convert the SafeConfigParser object to a dict
3031
2881
    server_settings = server_config.defaults()
3032
2882
    # Use the appropriate methods on the non-string config options
3033
 
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
3034
 
                   "foreground", "zeroconf"):
 
2883
    for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
3035
2884
        server_settings[option] = server_config.getboolean("DEFAULT",
3036
2885
                                                           option)
3037
2886
    if server_settings["port"]:
3105
2954
                                  server_settings["servicename"])))
3106
2955
 
3107
2956
    # Parse config file with clients
3108
 
    client_config = configparser.ConfigParser(Client.client_defaults)
 
2957
    client_config = configparser.SafeConfigParser(Client
 
2958
                                                  .client_defaults)
3109
2959
    client_config.read(os.path.join(server_settings["configdir"],
3110
2960
                                    "clients.conf"))
3111
2961
 
3182
3032
        # Close all input and output, do double fork, etc.
3183
3033
        daemon()
3184
3034
 
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()
 
3035
    # multiprocessing will use threads, so before we use GLib we need
 
3036
    # to inform GLib that threads will be used.
 
3037
    GLib.threads_init()
3189
3038
 
3190
3039
    global main_loop
3191
3040
    # From the Avahi example code
3267
3116
                             if isinstance(s, bytes)
3268
3117
                             else s) for s in
3269
3118
                            value["client_structure"]]
3270
 
                        # .name, .host, and .checker_command
3271
 
                        for k in ("name", "host", "checker_command"):
 
3119
                        # .name & .host
 
3120
                        for k in ("name", "host"):
3272
3121
                            if isinstance(value[k], bytes):
3273
3122
                                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
3123
                    #  old_client_settings
3279
3124
                    # .keys()
3280
3125
                    old_client_settings = {
3284
3129
                        for key, value in
3285
3130
                        bytes_old_client_settings.items()}
3286
3131
                    del bytes_old_client_settings
3287
 
                    # .host and .checker_command
 
3132
                    # .host
3288
3133
                    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"))
 
3134
                        if isinstance(value["host"], bytes):
 
3135
                            value["host"] = (value["host"]
 
3136
                                             .decode("utf-8"))
3293
3137
            os.remove(stored_state_path)
3294
3138
        except IOError as e:
3295
3139
            if e.errno == errno.ENOENT:
3418
3262
                pass
3419
3263
 
3420
3264
            @dbus.service.signal(_interface, signature="ss")
3421
 
            def ClientNotFound(self, key_id, address):
 
3265
            def ClientNotFound(self, fingerprint, address):
3422
3266
                "D-Bus signal"
3423
3267
                pass
3424
3268
 
3620
3464
                sys.exit(1)
3621
3465
            # End of Avahi example code
3622
3466
 
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))
 
3467
        GLib.io_add_watch(tcp_server.fileno(), GLib.IO_IN,
 
3468
                          lambda *args, **kwargs:
 
3469
                          (tcp_server.handle_request
 
3470
                           (*args[2:], **kwargs) or True))
3628
3471
 
3629
3472
        logger.debug("Starting main loop")
3630
3473
        main_loop.run()
3640
3483
    # Must run before the D-Bus bus name gets deregistered
3641
3484
    cleanup()
3642
3485
 
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
3486
 
3660
3487
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()
 
3488
    main()