/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: 2019-08-18 00:42:22 UTC
  • Revision ID: teddy@recompile.se-20190818004222-lfrgtnmqz766a08e
Client: Use the systemd sysusers.d mechanism, if present

* Makefile (install-client-nokey): Also install sysusers.d file, if
                                   $(SYSUSERS) exists.
* sysusers.d-mandos.conf: Adjust comment to match reality.

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