/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: 2022-04-23 21:14:38 UTC
  • Revision ID: teddy@recompile.se-20220423211438-qadwdpyodk1zfra5
Debian package: Client: Update Lintian override file

* debian/mandos-client.lintian-overrides: Remove all empty commented
  lines.  Re-word text to avoid negation.  Rename "setuid-binary" tag
  to "elevated-privileges".

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-2019 Teddy Hogeborn
15
 
# Copyright © 2008-2019 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
88
91
import xml.dom.minidom
89
92
import inspect
90
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
 
91
118
# Try to find the value of SO_BINDTODEVICE:
92
119
try:
93
120
    # This is where SO_BINDTODEVICE is in Python 3.3 (or 3.4?) and
113
140
            # No value found
114
141
            SO_BINDTODEVICE = None
115
142
 
116
 
if sys.version_info.major == 2:
117
 
    str = unicode
118
 
 
119
143
if sys.version_info < (3, 2):
120
144
    configparser.Configparser = configparser.SafeConfigParser
121
145
 
122
 
version = "1.8.6"
 
146
version = "1.8.14"
123
147
stored_state_file = "clients.pickle"
124
148
 
125
149
logger = logging.getLogger()
 
150
logging.captureWarnings(True)   # Show warnings via the logging system
126
151
syslogger = None
127
152
 
128
153
try:
183
208
    pass
184
209
 
185
210
 
186
 
class PGPEngine(object):
 
211
class PGPEngine:
187
212
    """A simple class for OpenPGP symmetric encryption & decryption"""
188
213
 
189
214
    def __init__(self):
193
218
            output = subprocess.check_output(["gpgconf"])
194
219
            for line in output.splitlines():
195
220
                name, text, path = line.split(b":")
196
 
                if name == "gpg":
 
221
                if name == b"gpg":
197
222
                    self.gpg = path
198
223
                    break
199
224
        except OSError as e:
204
229
                          '--force-mdc',
205
230
                          '--quiet']
206
231
        # Only GPG version 1 has the --no-use-agent option.
207
 
        if self.gpg == "gpg" or self.gpg.endswith("/gpg"):
 
232
        if self.gpg == b"gpg" or self.gpg.endswith(b"/gpg"):
208
233
            self.gnupgargs.append("--no-use-agent")
209
234
 
210
235
    def __enter__(self):
279
304
 
280
305
 
281
306
# Pretend that we have an Avahi module
282
 
class avahi(object):
 
307
class avahi:
283
308
    """This isn't so much a class as it is a module-like namespace."""
284
309
    IF_UNSPEC = -1               # avahi-common/address.h
285
310
    PROTO_UNSPEC = -1            # avahi-common/address.h
319
344
    pass
320
345
 
321
346
 
322
 
class AvahiService(object):
 
347
class AvahiService:
323
348
    """An Avahi (Zeroconf) service.
324
349
 
325
350
    Attributes:
499
524
class AvahiServiceToSyslog(AvahiService):
500
525
    def rename(self, *args, **kwargs):
501
526
        """Add the new name to the syslog messages"""
502
 
        ret = super(AvahiServiceToSyslog, self).rename(*args, **kwargs)
 
527
        ret = super(AvahiServiceToSyslog, self).rename(*args,
 
528
                                                       **kwargs)
503
529
        syslogger.setFormatter(logging.Formatter(
504
530
            'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
505
531
            .format(self.name)))
507
533
 
508
534
 
509
535
# Pretend that we have a GnuTLS module
510
 
class gnutls(object):
 
536
class gnutls:
511
537
    """This isn't so much a class as it is a module-like namespace."""
512
538
 
513
539
    library = ctypes.util.find_library("gnutls")
537
563
    OPENPGP_FMT_RAW = 0         # gnutls/openpgp.h
538
564
 
539
565
    # Types
540
 
    class session_int(ctypes.Structure):
 
566
    class _session_int(ctypes.Structure):
541
567
        _fields_ = []
542
 
    session_t = ctypes.POINTER(session_int)
 
568
    session_t = ctypes.POINTER(_session_int)
543
569
 
544
570
    class certificate_credentials_st(ctypes.Structure):
545
571
        _fields_ = []
551
577
        _fields_ = [('data', ctypes.POINTER(ctypes.c_ubyte)),
552
578
                    ('size', ctypes.c_uint)]
553
579
 
554
 
    class openpgp_crt_int(ctypes.Structure):
 
580
    class _openpgp_crt_int(ctypes.Structure):
555
581
        _fields_ = []
556
 
    openpgp_crt_t = ctypes.POINTER(openpgp_crt_int)
 
582
    openpgp_crt_t = ctypes.POINTER(_openpgp_crt_int)
557
583
    openpgp_crt_fmt_t = ctypes.c_int  # gnutls/openpgp.h
558
584
    log_func = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)
559
585
    credentials_type_t = ctypes.c_int
568
594
            # gnutls.strerror()
569
595
            self.code = code
570
596
            if message is None and code is not None:
571
 
                message = gnutls.strerror(code)
 
597
                message = gnutls.strerror(code).decode(
 
598
                    "utf-8", errors="replace")
572
599
            return super(gnutls.Error, self).__init__(
573
600
                message, *args)
574
601
 
575
602
    class CertificateSecurityError(Error):
576
603
        pass
577
604
 
 
605
    class PointerTo:
 
606
        def __init__(self, cls):
 
607
            self.cls = cls
 
608
 
 
609
        def from_param(self, obj):
 
610
            if not isinstance(obj, self.cls):
 
611
                raise TypeError("Not of type {}: {!r}"
 
612
                                .format(self.cls.__name__, obj))
 
613
            return ctypes.byref(obj.from_param(obj))
 
614
 
 
615
    class CastToVoidPointer:
 
616
        def __init__(self, cls):
 
617
            self.cls = cls
 
618
 
 
619
        def from_param(self, obj):
 
620
            if not isinstance(obj, self.cls):
 
621
                raise TypeError("Not of type {}: {!r}"
 
622
                                .format(self.cls.__name__, obj))
 
623
            return ctypes.cast(obj.from_param(obj), ctypes.c_void_p)
 
624
 
 
625
    class With_from_param:
 
626
        @classmethod
 
627
        def from_param(cls, obj):
 
628
            return obj._as_parameter_
 
629
 
578
630
    # Classes
579
 
    class Credentials(object):
 
631
    class Credentials(With_from_param):
580
632
        def __init__(self):
581
 
            self._c_object = gnutls.certificate_credentials_t()
582
 
            gnutls.certificate_allocate_credentials(
583
 
                ctypes.byref(self._c_object))
 
633
            self._as_parameter_ = gnutls.certificate_credentials_t()
 
634
            gnutls.certificate_allocate_credentials(self)
584
635
            self.type = gnutls.CRD_CERTIFICATE
585
636
 
586
637
        def __del__(self):
587
 
            gnutls.certificate_free_credentials(self._c_object)
 
638
            gnutls.certificate_free_credentials(self)
588
639
 
589
 
    class ClientSession(object):
 
640
    class ClientSession(With_from_param):
590
641
        def __init__(self, socket, credentials=None):
591
 
            self._c_object = gnutls.session_t()
 
642
            self._as_parameter_ = gnutls.session_t()
592
643
            gnutls_flags = gnutls.CLIENT
593
644
            if gnutls.check_version(b"3.5.6"):
594
645
                gnutls_flags |= gnutls.NO_TICKETS
595
646
            if gnutls.has_rawpk:
596
647
                gnutls_flags |= gnutls.ENABLE_RAWPK
597
 
            gnutls.init(ctypes.byref(self._c_object), gnutls_flags)
 
648
            gnutls.init(self, gnutls_flags)
598
649
            del gnutls_flags
599
 
            gnutls.set_default_priority(self._c_object)
600
 
            gnutls.transport_set_ptr(self._c_object, socket.fileno())
601
 
            gnutls.handshake_set_private_extensions(self._c_object,
602
 
                                                    True)
 
650
            gnutls.set_default_priority(self)
 
651
            gnutls.transport_set_ptr(self, socket.fileno())
 
652
            gnutls.handshake_set_private_extensions(self, True)
603
653
            self.socket = socket
604
654
            if credentials is None:
605
655
                credentials = gnutls.Credentials()
606
 
            gnutls.credentials_set(self._c_object, credentials.type,
607
 
                                   ctypes.cast(credentials._c_object,
608
 
                                               ctypes.c_void_p))
 
656
            gnutls.credentials_set(self, credentials.type,
 
657
                                   credentials)
609
658
            self.credentials = credentials
610
659
 
611
660
        def __del__(self):
612
 
            gnutls.deinit(self._c_object)
 
661
            gnutls.deinit(self)
613
662
 
614
663
        def handshake(self):
615
 
            return gnutls.handshake(self._c_object)
 
664
            return gnutls.handshake(self)
616
665
 
617
666
        def send(self, data):
618
667
            data = bytes(data)
619
668
            data_len = len(data)
620
669
            while data_len > 0:
621
 
                data_len -= gnutls.record_send(self._c_object,
622
 
                                               data[-data_len:],
 
670
                data_len -= gnutls.record_send(self, data[-data_len:],
623
671
                                               data_len)
624
672
 
625
673
        def bye(self):
626
 
            return gnutls.bye(self._c_object, gnutls.SHUT_RDWR)
 
674
            return gnutls.bye(self, gnutls.SHUT_RDWR)
627
675
 
628
676
    # Error handling functions
629
677
    def _error_code(result):
630
678
        """A function to raise exceptions on errors, suitable
631
679
        for the 'restype' attribute on ctypes functions"""
632
 
        if result >= 0:
 
680
        if result >= gnutls.E_SUCCESS:
633
681
            return result
634
682
        if result == gnutls.E_NO_CERTIFICATE_FOUND:
635
683
            raise gnutls.CertificateSecurityError(code=result)
636
684
        raise gnutls.Error(code=result)
637
685
 
638
 
    def _retry_on_error(result, func, arguments):
 
686
    def _retry_on_error(result, func, arguments,
 
687
                        _error_code=_error_code):
639
688
        """A function to retry on some errors, suitable
640
689
        for the 'errcheck' attribute on ctypes functions"""
641
 
        while result < 0:
 
690
        while result < gnutls.E_SUCCESS:
642
691
            if result not in (gnutls.E_INTERRUPTED, gnutls.E_AGAIN):
643
692
                return _error_code(result)
644
693
            result = func(*arguments)
649
698
 
650
699
    # Functions
651
700
    priority_set_direct = _library.gnutls_priority_set_direct
652
 
    priority_set_direct.argtypes = [session_t, ctypes.c_char_p,
 
701
    priority_set_direct.argtypes = [ClientSession, ctypes.c_char_p,
653
702
                                    ctypes.POINTER(ctypes.c_char_p)]
654
703
    priority_set_direct.restype = _error_code
655
704
 
656
705
    init = _library.gnutls_init
657
 
    init.argtypes = [ctypes.POINTER(session_t), ctypes.c_int]
 
706
    init.argtypes = [PointerTo(ClientSession), ctypes.c_int]
658
707
    init.restype = _error_code
659
708
 
660
709
    set_default_priority = _library.gnutls_set_default_priority
661
 
    set_default_priority.argtypes = [session_t]
 
710
    set_default_priority.argtypes = [ClientSession]
662
711
    set_default_priority.restype = _error_code
663
712
 
664
713
    record_send = _library.gnutls_record_send
665
 
    record_send.argtypes = [session_t, ctypes.c_void_p,
 
714
    record_send.argtypes = [ClientSession, ctypes.c_void_p,
666
715
                            ctypes.c_size_t]
667
716
    record_send.restype = ctypes.c_ssize_t
668
717
    record_send.errcheck = _retry_on_error
670
719
    certificate_allocate_credentials = (
671
720
        _library.gnutls_certificate_allocate_credentials)
672
721
    certificate_allocate_credentials.argtypes = [
673
 
        ctypes.POINTER(certificate_credentials_t)]
 
722
        PointerTo(Credentials)]
674
723
    certificate_allocate_credentials.restype = _error_code
675
724
 
676
725
    certificate_free_credentials = (
677
726
        _library.gnutls_certificate_free_credentials)
678
 
    certificate_free_credentials.argtypes = [
679
 
        certificate_credentials_t]
 
727
    certificate_free_credentials.argtypes = [Credentials]
680
728
    certificate_free_credentials.restype = None
681
729
 
682
730
    handshake_set_private_extensions = (
683
731
        _library.gnutls_handshake_set_private_extensions)
684
 
    handshake_set_private_extensions.argtypes = [session_t,
 
732
    handshake_set_private_extensions.argtypes = [ClientSession,
685
733
                                                 ctypes.c_int]
686
734
    handshake_set_private_extensions.restype = None
687
735
 
688
736
    credentials_set = _library.gnutls_credentials_set
689
 
    credentials_set.argtypes = [session_t, credentials_type_t,
690
 
                                ctypes.c_void_p]
 
737
    credentials_set.argtypes = [ClientSession, credentials_type_t,
 
738
                                CastToVoidPointer(Credentials)]
691
739
    credentials_set.restype = _error_code
692
740
 
693
741
    strerror = _library.gnutls_strerror
695
743
    strerror.restype = ctypes.c_char_p
696
744
 
697
745
    certificate_type_get = _library.gnutls_certificate_type_get
698
 
    certificate_type_get.argtypes = [session_t]
 
746
    certificate_type_get.argtypes = [ClientSession]
699
747
    certificate_type_get.restype = _error_code
700
748
 
701
749
    certificate_get_peers = _library.gnutls_certificate_get_peers
702
 
    certificate_get_peers.argtypes = [session_t,
 
750
    certificate_get_peers.argtypes = [ClientSession,
703
751
                                      ctypes.POINTER(ctypes.c_uint)]
704
752
    certificate_get_peers.restype = ctypes.POINTER(datum_t)
705
753
 
712
760
    global_set_log_function.restype = None
713
761
 
714
762
    deinit = _library.gnutls_deinit
715
 
    deinit.argtypes = [session_t]
 
763
    deinit.argtypes = [ClientSession]
716
764
    deinit.restype = None
717
765
 
718
766
    handshake = _library.gnutls_handshake
719
 
    handshake.argtypes = [session_t]
720
 
    handshake.restype = _error_code
 
767
    handshake.argtypes = [ClientSession]
 
768
    handshake.restype = ctypes.c_int
721
769
    handshake.errcheck = _retry_on_error
722
770
 
723
771
    transport_set_ptr = _library.gnutls_transport_set_ptr
724
 
    transport_set_ptr.argtypes = [session_t, transport_ptr_t]
 
772
    transport_set_ptr.argtypes = [ClientSession, transport_ptr_t]
725
773
    transport_set_ptr.restype = None
726
774
 
727
775
    bye = _library.gnutls_bye
728
 
    bye.argtypes = [session_t, close_request_t]
729
 
    bye.restype = _error_code
 
776
    bye.argtypes = [ClientSession, close_request_t]
 
777
    bye.restype = ctypes.c_int
730
778
    bye.errcheck = _retry_on_error
731
779
 
732
780
    check_version = _library.gnutls_check_version
749
797
 
750
798
        x509_crt_fmt_t = ctypes.c_int
751
799
 
752
 
        # All the function declarations below are from gnutls/abstract.h
 
800
        # All the function declarations below are from
 
801
        # gnutls/abstract.h
753
802
        pubkey_init = _library.gnutls_pubkey_init
754
803
        pubkey_init.argtypes = [ctypes.POINTER(pubkey_t)]
755
804
        pubkey_init.restype = _error_code
769
818
        pubkey_deinit.argtypes = [pubkey_t]
770
819
        pubkey_deinit.restype = None
771
820
    else:
772
 
        # All the function declarations below are from gnutls/openpgp.h
 
821
        # All the function declarations below are from
 
822
        # gnutls/openpgp.h
773
823
 
774
824
        openpgp_crt_init = _library.gnutls_openpgp_crt_init
775
825
        openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
781
831
                                       openpgp_crt_fmt_t]
782
832
        openpgp_crt_import.restype = _error_code
783
833
 
784
 
        openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
785
 
        openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
786
 
                                            ctypes.POINTER(ctypes.c_uint)]
 
834
        openpgp_crt_verify_self = \
 
835
            _library.gnutls_openpgp_crt_verify_self
 
836
        openpgp_crt_verify_self.argtypes = [
 
837
            openpgp_crt_t,
 
838
            ctypes.c_uint,
 
839
            ctypes.POINTER(ctypes.c_uint),
 
840
        ]
787
841
        openpgp_crt_verify_self.restype = _error_code
788
842
 
789
843
        openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
800
854
 
801
855
    if check_version(b"3.6.4"):
802
856
        certificate_type_get2 = _library.gnutls_certificate_type_get2
803
 
        certificate_type_get2.argtypes = [session_t, ctypes.c_int]
 
857
        certificate_type_get2.argtypes = [ClientSession, ctypes.c_int]
804
858
        certificate_type_get2.restype = _error_code
805
859
 
806
860
    # Remove non-public functions
818
872
    connection.close()
819
873
 
820
874
 
821
 
class Client(object):
 
875
class Client:
822
876
    """A representation of a client host served by this server.
823
877
 
824
878
    Attributes:
1027
1081
        if self.checker_initiator_tag is not None:
1028
1082
            GLib.source_remove(self.checker_initiator_tag)
1029
1083
        self.checker_initiator_tag = GLib.timeout_add(
1030
 
            int(self.interval.total_seconds() * 1000),
 
1084
            random.randrange(int(self.interval.total_seconds() * 1000
 
1085
                                 + 1)),
1031
1086
            self.start_checker)
1032
1087
        # Schedule a disable() when 'timeout' has passed
1033
1088
        if self.disable_initiator_tag is not None:
1043
1098
        # Read return code from connection (see call_pipe)
1044
1099
        returncode = connection.recv()
1045
1100
        connection.close()
1046
 
        self.checker.join()
 
1101
        if self.checker is not None:
 
1102
            self.checker.join()
1047
1103
        self.checker_callback_tag = None
1048
1104
        self.checker = None
1049
1105
 
1107
1163
        if self.checker is None:
1108
1164
            # Escape attributes for the shell
1109
1165
            escaped_attrs = {
1110
 
                attr: re.escape(str(getattr(self, attr)))
 
1166
                attr: shlex.quote(str(getattr(self, attr)))
1111
1167
                for attr in self.runtime_expansions}
1112
1168
            try:
1113
1169
                command = self.checker_command % escaped_attrs
1140
1196
                kwargs=popen_args)
1141
1197
            self.checker.start()
1142
1198
            self.checker_callback_tag = GLib.io_add_watch(
1143
 
                pipe[0].fileno(), GLib.IO_IN,
 
1199
                GLib.IOChannel.unix_new(pipe[0].fileno()),
 
1200
                GLib.PRIORITY_DEFAULT, GLib.IO_IN,
1144
1201
                self.checker_callback, pipe[0], command)
1145
1202
        # Re-run this periodically if run by GLib.timeout_add
1146
1203
        return True
1401
1458
                raise ValueError("Byte arrays not supported for non-"
1402
1459
                                 "'ay' signature {!r}"
1403
1460
                                 .format(prop._dbus_signature))
1404
 
            value = dbus.ByteArray(b''.join(chr(byte)
1405
 
                                            for byte in value))
 
1461
            value = dbus.ByteArray(bytes(value))
1406
1462
        prop(value)
1407
1463
 
1408
1464
    @dbus.service.method(dbus.PROPERTIES_IFACE,
2213
2269
    del _interface
2214
2270
 
2215
2271
 
2216
 
class ProxyClient(object):
 
2272
class ProxyClient:
2217
2273
    def __init__(self, child_pipe, key_id, fpr, address):
2218
2274
        self._pipe = child_pipe
2219
2275
        self._pipe.send(('init', key_id, fpr, address))
2264
2320
            priority = self.server.gnutls_priority
2265
2321
            if priority is None:
2266
2322
                priority = "NORMAL"
2267
 
            gnutls.priority_set_direct(session._c_object,
2268
 
                                       priority.encode("utf-8"),
2269
 
                                       None)
 
2323
            gnutls.priority_set_direct(session,
 
2324
                                       priority.encode("utf-8"), None)
2270
2325
 
2271
2326
            # Start communication using the Mandos protocol
2272
2327
            # Get protocol number
2299
2354
                    except (TypeError, gnutls.Error) as error:
2300
2355
                        logger.warning("Bad certificate: %s", error)
2301
2356
                        return
2302
 
                    logger.debug("Key ID: %s", key_id)
 
2357
                    logger.debug("Key ID: %s",
 
2358
                                 key_id.decode("utf-8",
 
2359
                                               errors="replace"))
2303
2360
 
2304
2361
                else:
2305
2362
                    key_id = b""
2397
2454
    def peer_certificate(session):
2398
2455
        "Return the peer's certificate as a bytestring"
2399
2456
        try:
2400
 
            cert_type = gnutls.certificate_type_get2(session._c_object,
2401
 
                                                     gnutls.CTYPE_PEERS)
 
2457
            cert_type = gnutls.certificate_type_get2(
 
2458
                session, gnutls.CTYPE_PEERS)
2402
2459
        except AttributeError:
2403
 
            cert_type = gnutls.certificate_type_get(session._c_object)
 
2460
            cert_type = gnutls.certificate_type_get(session)
2404
2461
        if gnutls.has_rawpk:
2405
2462
            valid_cert_types = frozenset((gnutls.CRT_RAWPK,))
2406
2463
        else:
2413
2470
            return b""
2414
2471
        list_size = ctypes.c_uint(1)
2415
2472
        cert_list = (gnutls.certificate_get_peers
2416
 
                     (session._c_object, ctypes.byref(list_size)))
 
2473
                     (session, ctypes.byref(list_size)))
2417
2474
        if not bool(cert_list) and list_size.value != 0:
2418
2475
            raise gnutls.Error("error getting peer certificate")
2419
2476
        if list_size.value == 0:
2441
2498
        buf = ctypes.create_string_buffer(32)
2442
2499
        buf_len = ctypes.c_size_t(len(buf))
2443
2500
        # Get the key ID from the raw public key into the buffer
2444
 
        gnutls.pubkey_get_key_id(pubkey,
2445
 
                                 gnutls.KEYID_USE_SHA256,
2446
 
                                 ctypes.cast(ctypes.byref(buf),
2447
 
                                             ctypes.POINTER(ctypes.c_ubyte)),
2448
 
                                 ctypes.byref(buf_len))
 
2501
        gnutls.pubkey_get_key_id(
 
2502
            pubkey,
 
2503
            gnutls.KEYID_USE_SHA256,
 
2504
            ctypes.cast(ctypes.byref(buf),
 
2505
                        ctypes.POINTER(ctypes.c_ubyte)),
 
2506
            ctypes.byref(buf_len))
2449
2507
        # Deinit the certificate
2450
2508
        gnutls.pubkey_deinit(pubkey)
2451
2509
 
2492
2550
        return hex_fpr
2493
2551
 
2494
2552
 
2495
 
class MultiprocessingMixIn(object):
 
2553
class MultiprocessingMixIn:
2496
2554
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
2497
2555
 
2498
2556
    def sub_process_main(self, request, address):
2510
2568
        return proc
2511
2569
 
2512
2570
 
2513
 
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
 
2571
class MultiprocessingMixInWithPipe(MultiprocessingMixIn):
2514
2572
    """ adds a pipe to the MixIn """
2515
2573
 
2516
2574
    def process_request(self, request, client_address):
2531
2589
 
2532
2590
 
2533
2591
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
2534
 
                     socketserver.TCPServer, object):
 
2592
                     socketserver.TCPServer):
2535
2593
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
2536
2594
 
2537
2595
    Attributes:
2670
2728
    def add_pipe(self, parent_pipe, proc):
2671
2729
        # Call "handle_ipc" for both data and EOF events
2672
2730
        GLib.io_add_watch(
2673
 
            parent_pipe.fileno(),
2674
 
            GLib.IO_IN | GLib.IO_HUP,
 
2731
            GLib.IOChannel.unix_new(parent_pipe.fileno()),
 
2732
            GLib.PRIORITY_DEFAULT, GLib.IO_IN | GLib.IO_HUP,
2675
2733
            functools.partial(self.handle_ipc,
2676
2734
                              parent_pipe=parent_pipe,
2677
2735
                              proc=proc))
2696
2754
            address = request[3]
2697
2755
 
2698
2756
            for c in self.clients.values():
2699
 
                if key_id == "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855":
 
2757
                if key_id == ("E3B0C44298FC1C149AFBF4C8996FB924"
 
2758
                              "27AE41E4649B934CA495991B7852B855"):
2700
2759
                    continue
2701
2760
                if key_id and c.key_id == key_id:
2702
2761
                    client = c
2715
2774
                return False
2716
2775
 
2717
2776
            GLib.io_add_watch(
2718
 
                parent_pipe.fileno(),
2719
 
                GLib.IO_IN | GLib.IO_HUP,
 
2777
                GLib.IOChannel.unix_new(parent_pipe.fileno()),
 
2778
                GLib.PRIORITY_DEFAULT, GLib.IO_IN | GLib.IO_HUP,
2720
2779
                functools.partial(self.handle_ipc,
2721
2780
                                  parent_pipe=parent_pipe,
2722
2781
                                  proc=proc,
2737
2796
        if command == 'getattr':
2738
2797
            attrname = request[1]
2739
2798
            if isinstance(client_object.__getattribute__(attrname),
2740
 
                          collections.Callable):
 
2799
                          collections.abc.Callable):
2741
2800
                parent_pipe.send(('function', ))
2742
2801
            else:
2743
2802
                parent_pipe.send((
2754
2813
def rfc3339_duration_to_delta(duration):
2755
2814
    """Parse an RFC 3339 "duration" and return a datetime.timedelta
2756
2815
 
2757
 
    >>> rfc3339_duration_to_delta("P7D")
2758
 
    datetime.timedelta(7)
2759
 
    >>> rfc3339_duration_to_delta("PT60S")
2760
 
    datetime.timedelta(0, 60)
2761
 
    >>> rfc3339_duration_to_delta("PT60M")
2762
 
    datetime.timedelta(0, 3600)
2763
 
    >>> rfc3339_duration_to_delta("PT24H")
2764
 
    datetime.timedelta(1)
2765
 
    >>> rfc3339_duration_to_delta("P1W")
2766
 
    datetime.timedelta(7)
2767
 
    >>> rfc3339_duration_to_delta("PT5M30S")
2768
 
    datetime.timedelta(0, 330)
2769
 
    >>> rfc3339_duration_to_delta("P1DT3M20S")
2770
 
    datetime.timedelta(1, 200)
 
2816
    >>> timedelta = datetime.timedelta
 
2817
    >>> rfc3339_duration_to_delta("P7D") == timedelta(7)
 
2818
    True
 
2819
    >>> rfc3339_duration_to_delta("PT60S") == timedelta(0, 60)
 
2820
    True
 
2821
    >>> rfc3339_duration_to_delta("PT60M") == timedelta(0, 3600)
 
2822
    True
 
2823
    >>> rfc3339_duration_to_delta("PT24H") == timedelta(1)
 
2824
    True
 
2825
    >>> rfc3339_duration_to_delta("P1W") == timedelta(7)
 
2826
    True
 
2827
    >>> rfc3339_duration_to_delta("PT5M30S") == timedelta(0, 330)
 
2828
    True
 
2829
    >>> rfc3339_duration_to_delta("P1DT3M20S") == timedelta(1, 200)
 
2830
    True
 
2831
    >>> del timedelta
2771
2832
    """
2772
2833
 
2773
2834
    # Parsing an RFC 3339 duration with regular expressions is not
2853
2914
def string_to_delta(interval):
2854
2915
    """Parse a string and return a datetime.timedelta
2855
2916
 
2856
 
    >>> string_to_delta('7d')
2857
 
    datetime.timedelta(7)
2858
 
    >>> string_to_delta('60s')
2859
 
    datetime.timedelta(0, 60)
2860
 
    >>> string_to_delta('60m')
2861
 
    datetime.timedelta(0, 3600)
2862
 
    >>> string_to_delta('24h')
2863
 
    datetime.timedelta(1)
2864
 
    >>> string_to_delta('1w')
2865
 
    datetime.timedelta(7)
2866
 
    >>> string_to_delta('5m 30s')
2867
 
    datetime.timedelta(0, 330)
 
2917
    >>> string_to_delta('7d') == datetime.timedelta(7)
 
2918
    True
 
2919
    >>> string_to_delta('60s') == datetime.timedelta(0, 60)
 
2920
    True
 
2921
    >>> string_to_delta('60m') == datetime.timedelta(0, 3600)
 
2922
    True
 
2923
    >>> string_to_delta('24h') == datetime.timedelta(1)
 
2924
    True
 
2925
    >>> string_to_delta('1w') == datetime.timedelta(7)
 
2926
    True
 
2927
    >>> string_to_delta('5m 30s') == datetime.timedelta(0, 330)
 
2928
    True
2868
2929
    """
2869
2930
 
2870
2931
    try:
2972
3033
 
2973
3034
    options = parser.parse_args()
2974
3035
 
2975
 
    if options.check:
2976
 
        import doctest
2977
 
        fail_count, test_count = doctest.testmod()
2978
 
        sys.exit(os.EX_OK if fail_count == 0 else 1)
2979
 
 
2980
3036
    # Default values for config file for server-global settings
2981
3037
    if gnutls.has_rawpk:
2982
3038
        priority = ("SECURE128:!CTYPE-X.509:+CTYPE-RAWPK:!RSA"
3145
3201
 
3146
3202
        @gnutls.log_func
3147
3203
        def debug_gnutls(level, string):
3148
 
            logger.debug("GnuTLS: %s", string[:-1])
 
3204
            logger.debug("GnuTLS: %s",
 
3205
                         string[:-1].decode("utf-8",
 
3206
                                            errors="replace"))
3149
3207
 
3150
3208
        gnutls.global_set_log_function(debug_gnutls)
3151
3209
 
3245
3303
                             if isinstance(s, bytes)
3246
3304
                             else s) for s in
3247
3305
                            value["client_structure"]]
3248
 
                        # .name & .host
3249
 
                        for k in ("name", "host"):
 
3306
                        # .name, .host, and .checker_command
 
3307
                        for k in ("name", "host", "checker_command"):
3250
3308
                            if isinstance(value[k], bytes):
3251
3309
                                value[k] = value[k].decode("utf-8")
3252
3310
                        if "key_id" not in value:
3262
3320
                        for key, value in
3263
3321
                        bytes_old_client_settings.items()}
3264
3322
                    del bytes_old_client_settings
3265
 
                    # .host
 
3323
                    # .host and .checker_command
3266
3324
                    for value in old_client_settings.values():
3267
 
                        if isinstance(value["host"], bytes):
3268
 
                            value["host"] = (value["host"]
3269
 
                                             .decode("utf-8"))
 
3325
                        for attribute in ("host", "checker_command"):
 
3326
                            if isinstance(value[attribute], bytes):
 
3327
                                value[attribute] = (value[attribute]
 
3328
                                                    .decode("utf-8"))
3270
3329
            os.remove(stored_state_path)
3271
3330
        except IOError as e:
3272
3331
            if e.errno == errno.ENOENT:
3597
3656
                sys.exit(1)
3598
3657
            # End of Avahi example code
3599
3658
 
3600
 
        GLib.io_add_watch(tcp_server.fileno(), GLib.IO_IN,
3601
 
                          lambda *args, **kwargs:
3602
 
                          (tcp_server.handle_request
3603
 
                           (*args[2:], **kwargs) or True))
 
3659
        GLib.io_add_watch(
 
3660
            GLib.IOChannel.unix_new(tcp_server.fileno()),
 
3661
            GLib.PRIORITY_DEFAULT, GLib.IO_IN,
 
3662
            lambda *args, **kwargs: (tcp_server.handle_request
 
3663
                                     (*args[2:], **kwargs) or True))
3604
3664
 
3605
3665
        logger.debug("Starting main loop")
3606
3666
        main_loop.run()
3616
3676
    # Must run before the D-Bus bus name gets deregistered
3617
3677
    cleanup()
3618
3678
 
 
3679
 
 
3680
def should_only_run_tests():
 
3681
    parser = argparse.ArgumentParser(add_help=False)
 
3682
    parser.add_argument("--check", action='store_true')
 
3683
    args, unknown_args = parser.parse_known_args()
 
3684
    run_tests = args.check
 
3685
    if run_tests:
 
3686
        # Remove --check argument from sys.argv
 
3687
        sys.argv[1:] = unknown_args
 
3688
    return run_tests
 
3689
 
 
3690
# Add all tests from doctest strings
 
3691
def load_tests(loader, tests, none):
 
3692
    import doctest
 
3693
    tests.addTests(doctest.DocTestSuite())
 
3694
    return tests
3619
3695
 
3620
3696
if __name__ == '__main__':
3621
 
    main()
 
3697
    try:
 
3698
        if should_only_run_tests():
 
3699
            # Call using ./mandos --check [--verbose]
 
3700
            unittest.main()
 
3701
        else:
 
3702
            main()
 
3703
    finally:
 
3704
        logging.shutdown()