/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-24 14:43:51 UTC
  • Revision ID: teddy@recompile.se-20190824144351-2y0l31jpj496vrtu
Server: Add scaffolding for tests

* mandos: Add code to run tests via the unittest module, similar to
          the code in mandos-ctl.  Also shut down logging on exit.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python3 -bI
 
1
#!/usr/bin/python
2
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.
78
78
import collections
79
79
import codecs
80
80
import unittest
81
 
import random
82
 
import shlex
83
81
 
84
82
import dbus
85
83
import dbus.service
93
91
 
94
92
if sys.version_info.major == 2:
95
93
    __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
94
 
118
95
# Try to find the value of SO_BINDTODEVICE:
119
96
try:
140
117
            # No value found
141
118
            SO_BINDTODEVICE = None
142
119
 
 
120
if sys.version_info.major == 2:
 
121
    str = unicode
 
122
 
143
123
if sys.version_info < (3, 2):
144
124
    configparser.Configparser = configparser.SafeConfigParser
145
125
 
146
 
version = "1.8.11"
 
126
version = "1.8.8"
147
127
stored_state_file = "clients.pickle"
148
128
 
149
129
logger = logging.getLogger()
150
 
logging.captureWarnings(True)   # Show warnings via the logging system
151
130
syslogger = None
152
131
 
153
132
try:
218
197
            output = subprocess.check_output(["gpgconf"])
219
198
            for line in output.splitlines():
220
199
                name, text, path = line.split(b":")
221
 
                if name == b"gpg":
 
200
                if name == "gpg":
222
201
                    self.gpg = path
223
202
                    break
224
203
        except OSError as e:
229
208
                          '--force-mdc',
230
209
                          '--quiet']
231
210
        # Only GPG version 1 has the --no-use-agent option.
232
 
        if self.gpg == b"gpg" or self.gpg.endswith(b"/gpg"):
 
211
        if self.gpg == "gpg" or self.gpg.endswith("/gpg"):
233
212
            self.gnupgargs.append("--no-use-agent")
234
213
 
235
214
    def __enter__(self):
1052
1031
        if self.checker_initiator_tag is not None:
1053
1032
            GLib.source_remove(self.checker_initiator_tag)
1054
1033
        self.checker_initiator_tag = GLib.timeout_add(
1055
 
            random.randrange(int(self.interval.total_seconds() * 1000
1056
 
                                 + 1)),
 
1034
            int(self.interval.total_seconds() * 1000),
1057
1035
            self.start_checker)
1058
1036
        # Schedule a disable() when 'timeout' has passed
1059
1037
        if self.disable_initiator_tag is not None:
1069
1047
        # Read return code from connection (see call_pipe)
1070
1048
        returncode = connection.recv()
1071
1049
        connection.close()
1072
 
        if self.checker is not None:
1073
 
            self.checker.join()
 
1050
        self.checker.join()
1074
1051
        self.checker_callback_tag = None
1075
1052
        self.checker = None
1076
1053
 
1134
1111
        if self.checker is None:
1135
1112
            # Escape attributes for the shell
1136
1113
            escaped_attrs = {
1137
 
                attr: shlex.quote(str(getattr(self, attr)))
 
1114
                attr: re.escape(str(getattr(self, attr)))
1138
1115
                for attr in self.runtime_expansions}
1139
1116
            try:
1140
1117
                command = self.checker_command % escaped_attrs
1167
1144
                kwargs=popen_args)
1168
1145
            self.checker.start()
1169
1146
            self.checker_callback_tag = GLib.io_add_watch(
1170
 
                GLib.IOChannel.unix_new(pipe[0].fileno()),
1171
 
                GLib.PRIORITY_DEFAULT, GLib.IO_IN,
 
1147
                pipe[0].fileno(), GLib.IO_IN,
1172
1148
                self.checker_callback, pipe[0], command)
1173
1149
        # Re-run this periodically if run by GLib.timeout_add
1174
1150
        return True
1429
1405
                raise ValueError("Byte arrays not supported for non-"
1430
1406
                                 "'ay' signature {!r}"
1431
1407
                                 .format(prop._dbus_signature))
1432
 
            value = dbus.ByteArray(bytes(value))
 
1408
            value = dbus.ByteArray(b''.join(chr(byte)
 
1409
                                            for byte in value))
1433
1410
        prop(value)
1434
1411
 
1435
1412
    @dbus.service.method(dbus.PROPERTIES_IFACE,
2697
2674
    def add_pipe(self, parent_pipe, proc):
2698
2675
        # Call "handle_ipc" for both data and EOF events
2699
2676
        GLib.io_add_watch(
2700
 
            GLib.IOChannel.unix_new(parent_pipe.fileno()),
2701
 
            GLib.PRIORITY_DEFAULT, GLib.IO_IN | GLib.IO_HUP,
 
2677
            parent_pipe.fileno(),
 
2678
            GLib.IO_IN | GLib.IO_HUP,
2702
2679
            functools.partial(self.handle_ipc,
2703
2680
                              parent_pipe=parent_pipe,
2704
2681
                              proc=proc))
2742
2719
                return False
2743
2720
 
2744
2721
            GLib.io_add_watch(
2745
 
                GLib.IOChannel.unix_new(parent_pipe.fileno()),
2746
 
                GLib.PRIORITY_DEFAULT, GLib.IO_IN | GLib.IO_HUP,
 
2722
                parent_pipe.fileno(),
 
2723
                GLib.IO_IN | GLib.IO_HUP,
2747
2724
                functools.partial(self.handle_ipc,
2748
2725
                                  parent_pipe=parent_pipe,
2749
2726
                                  proc=proc,
2764
2741
        if command == 'getattr':
2765
2742
            attrname = request[1]
2766
2743
            if isinstance(client_object.__getattribute__(attrname),
2767
 
                          collections.abc.Callable):
 
2744
                          collections.Callable):
2768
2745
                parent_pipe.send(('function', ))
2769
2746
            else:
2770
2747
                parent_pipe.send((
2781
2758
def rfc3339_duration_to_delta(duration):
2782
2759
    """Parse an RFC 3339 "duration" and return a datetime.timedelta
2783
2760
 
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
 
2761
    >>> rfc3339_duration_to_delta("P7D")
 
2762
    datetime.timedelta(7)
 
2763
    >>> rfc3339_duration_to_delta("PT60S")
 
2764
    datetime.timedelta(0, 60)
 
2765
    >>> rfc3339_duration_to_delta("PT60M")
 
2766
    datetime.timedelta(0, 3600)
 
2767
    >>> rfc3339_duration_to_delta("PT24H")
 
2768
    datetime.timedelta(1)
 
2769
    >>> rfc3339_duration_to_delta("P1W")
 
2770
    datetime.timedelta(7)
 
2771
    >>> rfc3339_duration_to_delta("PT5M30S")
 
2772
    datetime.timedelta(0, 330)
 
2773
    >>> rfc3339_duration_to_delta("P1DT3M20S")
 
2774
    datetime.timedelta(1, 200)
2798
2775
    """
2799
2776
 
2800
2777
    # Parsing an RFC 3339 duration with regular expressions is not
2880
2857
def string_to_delta(interval):
2881
2858
    """Parse a string and return a datetime.timedelta
2882
2859
 
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
 
2860
    >>> string_to_delta('7d')
 
2861
    datetime.timedelta(7)
 
2862
    >>> string_to_delta('60s')
 
2863
    datetime.timedelta(0, 60)
 
2864
    >>> string_to_delta('60m')
 
2865
    datetime.timedelta(0, 3600)
 
2866
    >>> string_to_delta('24h')
 
2867
    datetime.timedelta(1)
 
2868
    >>> string_to_delta('1w')
 
2869
    datetime.timedelta(7)
 
2870
    >>> string_to_delta('5m 30s')
 
2871
    datetime.timedelta(0, 330)
2895
2872
    """
2896
2873
 
2897
2874
    try:
3267
3244
                             if isinstance(s, bytes)
3268
3245
                             else s) for s in
3269
3246
                            value["client_structure"]]
3270
 
                        # .name, .host, and .checker_command
3271
 
                        for k in ("name", "host", "checker_command"):
 
3247
                        # .name & .host
 
3248
                        for k in ("name", "host"):
3272
3249
                            if isinstance(value[k], bytes):
3273
3250
                                value[k] = value[k].decode("utf-8")
3274
3251
                        if "key_id" not in value:
3284
3261
                        for key, value in
3285
3262
                        bytes_old_client_settings.items()}
3286
3263
                    del bytes_old_client_settings
3287
 
                    # .host and .checker_command
 
3264
                    # .host
3288
3265
                    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"))
 
3266
                        if isinstance(value["host"], bytes):
 
3267
                            value["host"] = (value["host"]
 
3268
                                             .decode("utf-8"))
3293
3269
            os.remove(stored_state_path)
3294
3270
        except IOError as e:
3295
3271
            if e.errno == errno.ENOENT:
3620
3596
                sys.exit(1)
3621
3597
            # End of Avahi example code
3622
3598
 
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))
 
3599
        GLib.io_add_watch(tcp_server.fileno(), GLib.IO_IN,
 
3600
                          lambda *args, **kwargs:
 
3601
                          (tcp_server.handle_request
 
3602
                           (*args[2:], **kwargs) or True))
3628
3603
 
3629
3604
        logger.debug("Starting main loop")
3630
3605
        main_loop.run()