/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2021-03-21 19:51:15 UTC
  • Revision ID: teddy@recompile.se-20210321195115-qe6g0fyj1kabwlav
Fix theoretical GnuTLS bug

Fix "NameError: global name '_error_code' is not defined" error if
GNUTLS_E_INTERRUPTED or GNUTLS_E_AGAIN was ever returned from
gnutls_record_send().

* mandos (gnutls._retry_on_error): Import "_error_code" from outer
  class scope to local function scope via a keyword argument.

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
90
93
 
91
94
if sys.version_info.major == 2:
92
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")
93
117
 
94
118
# Try to find the value of SO_BINDTODEVICE:
95
119
try:
116
140
            # No value found
117
141
            SO_BINDTODEVICE = None
118
142
 
119
 
if sys.version_info.major == 2:
120
 
    str = unicode
121
 
 
122
143
if sys.version_info < (3, 2):
123
144
    configparser.Configparser = configparser.SafeConfigParser
124
145
 
125
 
version = "1.8.7"
 
146
version = "1.8.14"
126
147
stored_state_file = "clients.pickle"
127
148
 
128
149
logger = logging.getLogger()
 
150
logging.captureWarnings(True)   # Show warnings via the logging system
129
151
syslogger = None
130
152
 
131
153
try:
196
218
            output = subprocess.check_output(["gpgconf"])
197
219
            for line in output.splitlines():
198
220
                name, text, path = line.split(b":")
199
 
                if name == "gpg":
 
221
                if name == b"gpg":
200
222
                    self.gpg = path
201
223
                    break
202
224
        except OSError as e:
207
229
                          '--force-mdc',
208
230
                          '--quiet']
209
231
        # Only GPG version 1 has the --no-use-agent option.
210
 
        if self.gpg == "gpg" or self.gpg.endswith("/gpg"):
 
232
        if self.gpg == b"gpg" or self.gpg.endswith(b"/gpg"):
211
233
            self.gnupgargs.append("--no-use-agent")
212
234
 
213
235
    def __enter__(self):
502
524
class AvahiServiceToSyslog(AvahiService):
503
525
    def rename(self, *args, **kwargs):
504
526
        """Add the new name to the syslog messages"""
505
 
        ret = super(AvahiServiceToSyslog, self).rename(*args, **kwargs)
 
527
        ret = super(AvahiServiceToSyslog, self).rename(*args,
 
528
                                                       **kwargs)
506
529
        syslogger.setFormatter(logging.Formatter(
507
530
            'Mandos ({}) [%(process)d]: %(levelname)s: %(message)s'
508
531
            .format(self.name)))
638
661
            raise gnutls.CertificateSecurityError(code=result)
639
662
        raise gnutls.Error(code=result)
640
663
 
641
 
    def _retry_on_error(result, func, arguments):
 
664
    def _retry_on_error(result, func, arguments,
 
665
                        _error_code=_error_code):
642
666
        """A function to retry on some errors, suitable
643
667
        for the 'errcheck' attribute on ctypes functions"""
644
668
        while result < 0:
752
776
 
753
777
        x509_crt_fmt_t = ctypes.c_int
754
778
 
755
 
        # All the function declarations below are from gnutls/abstract.h
 
779
        # All the function declarations below are from
 
780
        # gnutls/abstract.h
756
781
        pubkey_init = _library.gnutls_pubkey_init
757
782
        pubkey_init.argtypes = [ctypes.POINTER(pubkey_t)]
758
783
        pubkey_init.restype = _error_code
772
797
        pubkey_deinit.argtypes = [pubkey_t]
773
798
        pubkey_deinit.restype = None
774
799
    else:
775
 
        # All the function declarations below are from gnutls/openpgp.h
 
800
        # All the function declarations below are from
 
801
        # gnutls/openpgp.h
776
802
 
777
803
        openpgp_crt_init = _library.gnutls_openpgp_crt_init
778
804
        openpgp_crt_init.argtypes = [ctypes.POINTER(openpgp_crt_t)]
784
810
                                       openpgp_crt_fmt_t]
785
811
        openpgp_crt_import.restype = _error_code
786
812
 
787
 
        openpgp_crt_verify_self = _library.gnutls_openpgp_crt_verify_self
788
 
        openpgp_crt_verify_self.argtypes = [openpgp_crt_t, ctypes.c_uint,
789
 
                                            ctypes.POINTER(ctypes.c_uint)]
 
813
        openpgp_crt_verify_self = \
 
814
            _library.gnutls_openpgp_crt_verify_self
 
815
        openpgp_crt_verify_self.argtypes = [
 
816
            openpgp_crt_t,
 
817
            ctypes.c_uint,
 
818
            ctypes.POINTER(ctypes.c_uint),
 
819
        ]
790
820
        openpgp_crt_verify_self.restype = _error_code
791
821
 
792
822
        openpgp_crt_deinit = _library.gnutls_openpgp_crt_deinit
1030
1060
        if self.checker_initiator_tag is not None:
1031
1061
            GLib.source_remove(self.checker_initiator_tag)
1032
1062
        self.checker_initiator_tag = GLib.timeout_add(
1033
 
            int(self.interval.total_seconds() * 1000),
 
1063
            random.randrange(int(self.interval.total_seconds() * 1000
 
1064
                                 + 1)),
1034
1065
            self.start_checker)
1035
1066
        # Schedule a disable() when 'timeout' has passed
1036
1067
        if self.disable_initiator_tag is not None:
1046
1077
        # Read return code from connection (see call_pipe)
1047
1078
        returncode = connection.recv()
1048
1079
        connection.close()
1049
 
        self.checker.join()
 
1080
        if self.checker is not None:
 
1081
            self.checker.join()
1050
1082
        self.checker_callback_tag = None
1051
1083
        self.checker = None
1052
1084
 
1110
1142
        if self.checker is None:
1111
1143
            # Escape attributes for the shell
1112
1144
            escaped_attrs = {
1113
 
                attr: re.escape(str(getattr(self, attr)))
 
1145
                attr: shlex.quote(str(getattr(self, attr)))
1114
1146
                for attr in self.runtime_expansions}
1115
1147
            try:
1116
1148
                command = self.checker_command % escaped_attrs
1143
1175
                kwargs=popen_args)
1144
1176
            self.checker.start()
1145
1177
            self.checker_callback_tag = GLib.io_add_watch(
1146
 
                pipe[0].fileno(), GLib.IO_IN,
 
1178
                GLib.IOChannel.unix_new(pipe[0].fileno()),
 
1179
                GLib.PRIORITY_DEFAULT, GLib.IO_IN,
1147
1180
                self.checker_callback, pipe[0], command)
1148
1181
        # Re-run this periodically if run by GLib.timeout_add
1149
1182
        return True
1404
1437
                raise ValueError("Byte arrays not supported for non-"
1405
1438
                                 "'ay' signature {!r}"
1406
1439
                                 .format(prop._dbus_signature))
1407
 
            value = dbus.ByteArray(b''.join(chr(byte)
1408
 
                                            for byte in value))
 
1440
            value = dbus.ByteArray(bytes(value))
1409
1441
        prop(value)
1410
1442
 
1411
1443
    @dbus.service.method(dbus.PROPERTIES_IFACE,
2444
2476
        buf = ctypes.create_string_buffer(32)
2445
2477
        buf_len = ctypes.c_size_t(len(buf))
2446
2478
        # Get the key ID from the raw public key into the buffer
2447
 
        gnutls.pubkey_get_key_id(pubkey,
2448
 
                                 gnutls.KEYID_USE_SHA256,
2449
 
                                 ctypes.cast(ctypes.byref(buf),
2450
 
                                             ctypes.POINTER(ctypes.c_ubyte)),
2451
 
                                 ctypes.byref(buf_len))
 
2479
        gnutls.pubkey_get_key_id(
 
2480
            pubkey,
 
2481
            gnutls.KEYID_USE_SHA256,
 
2482
            ctypes.cast(ctypes.byref(buf),
 
2483
                        ctypes.POINTER(ctypes.c_ubyte)),
 
2484
            ctypes.byref(buf_len))
2452
2485
        # Deinit the certificate
2453
2486
        gnutls.pubkey_deinit(pubkey)
2454
2487
 
2673
2706
    def add_pipe(self, parent_pipe, proc):
2674
2707
        # Call "handle_ipc" for both data and EOF events
2675
2708
        GLib.io_add_watch(
2676
 
            parent_pipe.fileno(),
2677
 
            GLib.IO_IN | GLib.IO_HUP,
 
2709
            GLib.IOChannel.unix_new(parent_pipe.fileno()),
 
2710
            GLib.PRIORITY_DEFAULT, GLib.IO_IN | GLib.IO_HUP,
2678
2711
            functools.partial(self.handle_ipc,
2679
2712
                              parent_pipe=parent_pipe,
2680
2713
                              proc=proc))
2699
2732
            address = request[3]
2700
2733
 
2701
2734
            for c in self.clients.values():
2702
 
                if key_id == "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855":
 
2735
                if key_id == ("E3B0C44298FC1C149AFBF4C8996FB924"
 
2736
                              "27AE41E4649B934CA495991B7852B855"):
2703
2737
                    continue
2704
2738
                if key_id and c.key_id == key_id:
2705
2739
                    client = c
2718
2752
                return False
2719
2753
 
2720
2754
            GLib.io_add_watch(
2721
 
                parent_pipe.fileno(),
2722
 
                GLib.IO_IN | GLib.IO_HUP,
 
2755
                GLib.IOChannel.unix_new(parent_pipe.fileno()),
 
2756
                GLib.PRIORITY_DEFAULT, GLib.IO_IN | GLib.IO_HUP,
2723
2757
                functools.partial(self.handle_ipc,
2724
2758
                                  parent_pipe=parent_pipe,
2725
2759
                                  proc=proc,
2740
2774
        if command == 'getattr':
2741
2775
            attrname = request[1]
2742
2776
            if isinstance(client_object.__getattribute__(attrname),
2743
 
                          collections.Callable):
 
2777
                          collections.abc.Callable):
2744
2778
                parent_pipe.send(('function', ))
2745
2779
            else:
2746
2780
                parent_pipe.send((
2757
2791
def rfc3339_duration_to_delta(duration):
2758
2792
    """Parse an RFC 3339 "duration" and return a datetime.timedelta
2759
2793
 
2760
 
    >>> rfc3339_duration_to_delta("P7D")
2761
 
    datetime.timedelta(7)
2762
 
    >>> rfc3339_duration_to_delta("PT60S")
2763
 
    datetime.timedelta(0, 60)
2764
 
    >>> rfc3339_duration_to_delta("PT60M")
2765
 
    datetime.timedelta(0, 3600)
2766
 
    >>> rfc3339_duration_to_delta("PT24H")
2767
 
    datetime.timedelta(1)
2768
 
    >>> rfc3339_duration_to_delta("P1W")
2769
 
    datetime.timedelta(7)
2770
 
    >>> rfc3339_duration_to_delta("PT5M30S")
2771
 
    datetime.timedelta(0, 330)
2772
 
    >>> rfc3339_duration_to_delta("P1DT3M20S")
2773
 
    datetime.timedelta(1, 200)
 
2794
    >>> timedelta = datetime.timedelta
 
2795
    >>> rfc3339_duration_to_delta("P7D") == timedelta(7)
 
2796
    True
 
2797
    >>> rfc3339_duration_to_delta("PT60S") == timedelta(0, 60)
 
2798
    True
 
2799
    >>> rfc3339_duration_to_delta("PT60M") == timedelta(0, 3600)
 
2800
    True
 
2801
    >>> rfc3339_duration_to_delta("PT24H") == timedelta(1)
 
2802
    True
 
2803
    >>> rfc3339_duration_to_delta("P1W") == timedelta(7)
 
2804
    True
 
2805
    >>> rfc3339_duration_to_delta("PT5M30S") == timedelta(0, 330)
 
2806
    True
 
2807
    >>> rfc3339_duration_to_delta("P1DT3M20S") == timedelta(1, 200)
 
2808
    True
 
2809
    >>> del timedelta
2774
2810
    """
2775
2811
 
2776
2812
    # Parsing an RFC 3339 duration with regular expressions is not
2856
2892
def string_to_delta(interval):
2857
2893
    """Parse a string and return a datetime.timedelta
2858
2894
 
2859
 
    >>> string_to_delta('7d')
2860
 
    datetime.timedelta(7)
2861
 
    >>> string_to_delta('60s')
2862
 
    datetime.timedelta(0, 60)
2863
 
    >>> string_to_delta('60m')
2864
 
    datetime.timedelta(0, 3600)
2865
 
    >>> string_to_delta('24h')
2866
 
    datetime.timedelta(1)
2867
 
    >>> string_to_delta('1w')
2868
 
    datetime.timedelta(7)
2869
 
    >>> string_to_delta('5m 30s')
2870
 
    datetime.timedelta(0, 330)
 
2895
    >>> string_to_delta('7d') == datetime.timedelta(7)
 
2896
    True
 
2897
    >>> string_to_delta('60s') == datetime.timedelta(0, 60)
 
2898
    True
 
2899
    >>> string_to_delta('60m') == datetime.timedelta(0, 3600)
 
2900
    True
 
2901
    >>> string_to_delta('24h') == datetime.timedelta(1)
 
2902
    True
 
2903
    >>> string_to_delta('1w') == datetime.timedelta(7)
 
2904
    True
 
2905
    >>> string_to_delta('5m 30s') == datetime.timedelta(0, 330)
 
2906
    True
2871
2907
    """
2872
2908
 
2873
2909
    try:
2975
3011
 
2976
3012
    options = parser.parse_args()
2977
3013
 
2978
 
    if options.check:
2979
 
        import doctest
2980
 
        fail_count, test_count = doctest.testmod()
2981
 
        sys.exit(os.EX_OK if fail_count == 0 else 1)
2982
 
 
2983
3014
    # Default values for config file for server-global settings
2984
3015
    if gnutls.has_rawpk:
2985
3016
        priority = ("SECURE128:!CTYPE-X.509:+CTYPE-RAWPK:!RSA"
3248
3279
                             if isinstance(s, bytes)
3249
3280
                             else s) for s in
3250
3281
                            value["client_structure"]]
3251
 
                        # .name & .host
3252
 
                        for k in ("name", "host"):
 
3282
                        # .name, .host, and .checker_command
 
3283
                        for k in ("name", "host", "checker_command"):
3253
3284
                            if isinstance(value[k], bytes):
3254
3285
                                value[k] = value[k].decode("utf-8")
3255
3286
                        if "key_id" not in value:
3265
3296
                        for key, value in
3266
3297
                        bytes_old_client_settings.items()}
3267
3298
                    del bytes_old_client_settings
3268
 
                    # .host
 
3299
                    # .host and .checker_command
3269
3300
                    for value in old_client_settings.values():
3270
 
                        if isinstance(value["host"], bytes):
3271
 
                            value["host"] = (value["host"]
3272
 
                                             .decode("utf-8"))
 
3301
                        for attribute in ("host", "checker_command"):
 
3302
                            if isinstance(value[attribute], bytes):
 
3303
                                value[attribute] = (value[attribute]
 
3304
                                                    .decode("utf-8"))
3273
3305
            os.remove(stored_state_path)
3274
3306
        except IOError as e:
3275
3307
            if e.errno == errno.ENOENT:
3600
3632
                sys.exit(1)
3601
3633
            # End of Avahi example code
3602
3634
 
3603
 
        GLib.io_add_watch(tcp_server.fileno(), GLib.IO_IN,
3604
 
                          lambda *args, **kwargs:
3605
 
                          (tcp_server.handle_request
3606
 
                           (*args[2:], **kwargs) or True))
 
3635
        GLib.io_add_watch(
 
3636
            GLib.IOChannel.unix_new(tcp_server.fileno()),
 
3637
            GLib.PRIORITY_DEFAULT, GLib.IO_IN,
 
3638
            lambda *args, **kwargs: (tcp_server.handle_request
 
3639
                                     (*args[2:], **kwargs) or True))
3607
3640
 
3608
3641
        logger.debug("Starting main loop")
3609
3642
        main_loop.run()
3619
3652
    # Must run before the D-Bus bus name gets deregistered
3620
3653
    cleanup()
3621
3654
 
 
3655
 
 
3656
def should_only_run_tests():
 
3657
    parser = argparse.ArgumentParser(add_help=False)
 
3658
    parser.add_argument("--check", action='store_true')
 
3659
    args, unknown_args = parser.parse_known_args()
 
3660
    run_tests = args.check
 
3661
    if run_tests:
 
3662
        # Remove --check argument from sys.argv
 
3663
        sys.argv[1:] = unknown_args
 
3664
    return run_tests
 
3665
 
 
3666
# Add all tests from doctest strings
 
3667
def load_tests(loader, tests, none):
 
3668
    import doctest
 
3669
    tests.addTests(doctest.DocTestSuite())
 
3670
    return tests
3622
3671
 
3623
3672
if __name__ == '__main__':
3624
 
    main()
 
3673
    try:
 
3674
        if should_only_run_tests():
 
3675
            # Call using ./mandos --check [--verbose]
 
3676
            unittest.main()
 
3677
        else:
 
3678
            main()
 
3679
    finally:
 
3680
        logging.shutdown()