/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-03-05 21:39:15 UTC
  • Revision ID: teddy@recompile.se-20190305213915-xm1vw00jyy3a5tfn
mandos-ctl: Add more tests, including tests for all commands

* mandos-ctl (Test_string_to_delta.test_handles_basic_rfc3339): Add a
                                                  few more test cases.
  (TestCmd.setUp.MockClient.Set, TestCmd.setUp.MockClient.Get): Don't
  append to self.calls, since nobody should use it to check for Set()
  or Get() calls; instead, the return value of Get() should be
  inspected, and the MockClient.attributes dict should be inspected
  after (implicitly) calling Set().
  (Unique): New; stand-in for unittest.mock.sentinel.
  (TestPropertyCmd): New; abstract class testing PropertyCmd classes.
  (TestBumpTimeoutCmd, TestStartCheckerCmd, TestStopCheckerCmd,
  TestApproveByDefaultCmd, TestDenyByDefaultCmd): New.
  (TestValueArgumentPropertyCmd): New; abstract class for testing
                                  those PropertyCmd classes which also
                                  inherit from ValueArgumentMixIn.
  (TestSetCheckerCmd, TestSetHostCmd, TestSetSecretCmd,
  TestSetTimeoutCmd, TestSetExtendedTimeoutCmd, TestSetIntervalCmd,
  TestSetApprovalDelayCmd, TestSetApprovalDurationCmd): New.

Show diffs side-by-side

added added

removed removed

Lines of Context:
80
80
 
81
81
import dbus
82
82
import dbus.service
83
 
import gi
84
83
from gi.repository import GLib
85
84
from dbus.mainloop.glib import DBusGMainLoop
86
85
import ctypes
88
87
import xml.dom.minidom
89
88
import inspect
90
89
 
91
 
if sys.version_info.major == 2:
92
 
    __metaclass__ = type
93
 
 
94
90
# Try to find the value of SO_BINDTODEVICE:
95
91
try:
96
92
    # This is where SO_BINDTODEVICE is in Python 3.3 (or 3.4?) and
119
115
if sys.version_info.major == 2:
120
116
    str = unicode
121
117
 
122
 
if sys.version_info < (3, 2):
123
 
    configparser.Configparser = configparser.SafeConfigParser
124
 
 
125
 
version = "1.8.7"
 
118
version = "1.8.3"
126
119
stored_state_file = "clients.pickle"
127
120
 
128
121
logger = logging.getLogger()
186
179
    pass
187
180
 
188
181
 
189
 
class PGPEngine:
 
182
class PGPEngine(object):
190
183
    """A simple class for OpenPGP symmetric encryption & decryption"""
191
184
 
192
185
    def __init__(self):
282
275
 
283
276
 
284
277
# Pretend that we have an Avahi module
285
 
class avahi:
286
 
    """This isn't so much a class as it is a module-like namespace."""
 
278
class Avahi(object):
 
279
    """This isn't so much a class as it is a module-like namespace.
 
280
    It is instantiated once, and simulates having an Avahi module."""
287
281
    IF_UNSPEC = -1               # avahi-common/address.h
288
282
    PROTO_UNSPEC = -1            # avahi-common/address.h
289
283
    PROTO_INET = 0               # avahi-common/address.h
293
287
    DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
294
288
    DBUS_PATH_SERVER = "/"
295
289
 
296
 
    @staticmethod
297
 
    def string_array_to_txt_array(t):
 
290
    def string_array_to_txt_array(self, t):
298
291
        return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
299
292
                           for s in t), signature="ay")
300
293
    ENTRY_GROUP_ESTABLISHED = 2  # avahi-common/defs.h
305
298
    SERVER_RUNNING = 2           # avahi-common/defs.h
306
299
    SERVER_COLLISION = 3         # avahi-common/defs.h
307
300
    SERVER_FAILURE = 4           # avahi-common/defs.h
 
301
avahi = Avahi()
308
302
 
309
303
 
310
304
class AvahiError(Exception):
322
316
    pass
323
317
 
324
318
 
325
 
class AvahiService:
 
319
class AvahiService(object):
326
320
    """An Avahi (Zeroconf) service.
327
321
 
328
322
    Attributes:
510
504
 
511
505
 
512
506
# Pretend that we have a GnuTLS module
513
 
class gnutls:
514
 
    """This isn't so much a class as it is a module-like namespace."""
 
507
class GnuTLS(object):
 
508
    """This isn't so much a class as it is a module-like namespace.
 
509
    It is instantiated once, and simulates having a GnuTLS module."""
515
510
 
516
511
    library = ctypes.util.find_library("gnutls")
517
512
    if library is None:
518
513
        library = ctypes.util.find_library("gnutls-deb0")
519
514
    _library = ctypes.cdll.LoadLibrary(library)
520
515
    del library
 
516
    _need_version = b"3.3.0"
 
517
    _tls_rawpk_version = b"3.6.6"
 
518
 
 
519
    def __init__(self):
 
520
        # Need to use "self" here, since this method is called before
 
521
        # the assignment to the "gnutls" global variable happens.
 
522
        if self.check_version(self._need_version) is None:
 
523
            raise self.Error("Needs GnuTLS {} or later"
 
524
                             .format(self._need_version))
521
525
 
522
526
    # Unless otherwise indicated, the constants and types below are
523
527
    # all from the gnutls/gnutls.h C header file.
565
569
 
566
570
    # Exceptions
567
571
    class Error(Exception):
 
572
        # We need to use the class name "GnuTLS" here, since this
 
573
        # exception might be raised from within GnuTLS.__init__,
 
574
        # which is called before the assignment to the "gnutls"
 
575
        # global variable has happened.
568
576
        def __init__(self, message=None, code=None, args=()):
569
577
            # Default usage is by a message string, but if a return
570
578
            # code is passed, convert it to a string with
571
579
            # gnutls.strerror()
572
580
            self.code = code
573
581
            if message is None and code is not None:
574
 
                message = gnutls.strerror(code)
575
 
            return super(gnutls.Error, self).__init__(
 
582
                message = GnuTLS.strerror(code)
 
583
            return super(GnuTLS.Error, self).__init__(
576
584
                message, *args)
577
585
 
578
586
    class CertificateSecurityError(Error):
579
587
        pass
580
588
 
581
589
    # Classes
582
 
    class Credentials:
 
590
    class Credentials(object):
583
591
        def __init__(self):
584
592
            self._c_object = gnutls.certificate_credentials_t()
585
593
            gnutls.certificate_allocate_credentials(
589
597
        def __del__(self):
590
598
            gnutls.certificate_free_credentials(self._c_object)
591
599
 
592
 
    class ClientSession:
 
600
    class ClientSession(object):
593
601
        def __init__(self, socket, credentials=None):
594
602
            self._c_object = gnutls.session_t()
595
603
            gnutls_flags = gnutls.CLIENT
596
 
            if gnutls.check_version(b"3.5.6"):
 
604
            if gnutls.check_version("3.5.6"):
597
605
                gnutls_flags |= gnutls.NO_TICKETS
598
606
            if gnutls.has_rawpk:
599
607
                gnutls_flags |= gnutls.ENABLE_RAWPK
736
744
    check_version.argtypes = [ctypes.c_char_p]
737
745
    check_version.restype = ctypes.c_char_p
738
746
 
739
 
    _need_version = b"3.3.0"
740
 
    if check_version(_need_version) is None:
741
 
        raise self.Error("Needs GnuTLS {} or later"
742
 
                         .format(_need_version))
743
 
 
744
 
    _tls_rawpk_version = b"3.6.6"
745
747
    has_rawpk = bool(check_version(_tls_rawpk_version))
746
748
 
747
749
    if has_rawpk:
801
803
                                                    ctypes.c_size_t)]
802
804
        openpgp_crt_get_fingerprint.restype = _error_code
803
805
 
804
 
    if check_version(b"3.6.4"):
 
806
    if check_version("3.6.4"):
805
807
        certificate_type_get2 = _library.gnutls_certificate_type_get2
806
808
        certificate_type_get2.argtypes = [session_t, ctypes.c_int]
807
809
        certificate_type_get2.restype = _error_code
808
810
 
809
811
    # Remove non-public functions
810
812
    del _error_code, _retry_on_error
 
813
# Create the global "gnutls" object, simulating a module
 
814
gnutls = GnuTLS()
811
815
 
812
816
 
813
817
def call_pipe(connection,       # : multiprocessing.Connection
821
825
    connection.close()
822
826
 
823
827
 
824
 
class Client:
 
828
class Client(object):
825
829
    """A representation of a client host served by this server.
826
830
 
827
831
    Attributes:
828
832
    approved:   bool(); 'None' if not yet approved/disapproved
829
833
    approval_delay: datetime.timedelta(); Time to wait for approval
830
834
    approval_duration: datetime.timedelta(); Duration of one approval
831
 
    checker: multiprocessing.Process(); a running checker process used
832
 
             to see if the client lives. 'None' if no process is
833
 
             running.
 
835
    checker:    subprocess.Popen(); a running checker process used
 
836
                                    to see if the client lives.
 
837
                                    'None' if no process is running.
834
838
    checker_callback_tag: a GLib event source tag, or None
835
839
    checker_command: string; External command which is run to check
836
840
                     if client lives.  %() expansions are done at
1043
1047
    def checker_callback(self, source, condition, connection,
1044
1048
                         command):
1045
1049
        """The checker has completed, so take appropriate actions."""
 
1050
        self.checker_callback_tag = None
 
1051
        self.checker = None
1046
1052
        # Read return code from connection (see call_pipe)
1047
1053
        returncode = connection.recv()
1048
1054
        connection.close()
1049
 
        self.checker.join()
1050
 
        self.checker_callback_tag = None
1051
 
        self.checker = None
1052
1055
 
1053
1056
        if returncode >= 0:
1054
1057
            self.last_checker_status = returncode
2216
2219
    del _interface
2217
2220
 
2218
2221
 
2219
 
class ProxyClient:
 
2222
class ProxyClient(object):
2220
2223
    def __init__(self, child_pipe, key_id, fpr, address):
2221
2224
        self._pipe = child_pipe
2222
2225
        self._pipe.send(('init', key_id, fpr, address))
2295
2298
            approval_required = False
2296
2299
            try:
2297
2300
                if gnutls.has_rawpk:
2298
 
                    fpr = b""
 
2301
                    fpr = ""
2299
2302
                    try:
2300
2303
                        key_id = self.key_id(
2301
2304
                            self.peer_certificate(session))
2305
2308
                    logger.debug("Key ID: %s", key_id)
2306
2309
 
2307
2310
                else:
2308
 
                    key_id = b""
 
2311
                    key_id = ""
2309
2312
                    try:
2310
2313
                        fpr = self.fingerprint(
2311
2314
                            self.peer_certificate(session))
2495
2498
        return hex_fpr
2496
2499
 
2497
2500
 
2498
 
class MultiprocessingMixIn:
 
2501
class MultiprocessingMixIn(object):
2499
2502
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
2500
2503
 
2501
2504
    def sub_process_main(self, request, address):
2513
2516
        return proc
2514
2517
 
2515
2518
 
2516
 
class MultiprocessingMixInWithPipe(MultiprocessingMixIn):
 
2519
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
2517
2520
    """ adds a pipe to the MixIn """
2518
2521
 
2519
2522
    def process_request(self, request, client_address):
2534
2537
 
2535
2538
 
2536
2539
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
2537
 
                     socketserver.TCPServer):
 
2540
                     socketserver.TCPServer, object):
2538
2541
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
2539
2542
 
2540
2543
    Attributes:
2613
2616
                    raise
2614
2617
        # Only bind(2) the socket if we really need to.
2615
2618
        if self.server_address[0] or self.server_address[1]:
2616
 
            if self.server_address[1]:
2617
 
                self.allow_reuse_address = True
2618
2619
            if not self.server_address[0]:
2619
2620
                if self.address_family == socket.AF_INET6:
2620
2621
                    any_address = "::"  # in6addr_any
3005
3006
    del priority
3006
3007
 
3007
3008
    # Parse config file for server-global settings
3008
 
    server_config = configparser.ConfigParser(server_defaults)
 
3009
    server_config = configparser.SafeConfigParser(server_defaults)
3009
3010
    del server_defaults
3010
3011
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
3011
 
    # Convert the ConfigParser object to a dict
 
3012
    # Convert the SafeConfigParser object to a dict
3012
3013
    server_settings = server_config.defaults()
3013
3014
    # Use the appropriate methods on the non-string config options
3014
3015
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
3086
3087
                                  server_settings["servicename"])))
3087
3088
 
3088
3089
    # Parse config file with clients
3089
 
    client_config = configparser.ConfigParser(Client.client_defaults)
 
3090
    client_config = configparser.SafeConfigParser(Client
 
3091
                                                  .client_defaults)
3090
3092
    client_config.read(os.path.join(server_settings["configdir"],
3091
3093
                                    "clients.conf"))
3092
3094
 
3163
3165
        # Close all input and output, do double fork, etc.
3164
3166
        daemon()
3165
3167
 
3166
 
    if gi.version_info < (3, 10, 2):
3167
 
        # multiprocessing will use threads, so before we use GLib we
3168
 
        # need to inform GLib that threads will be used.
3169
 
        GLib.threads_init()
 
3168
    # multiprocessing will use threads, so before we use GLib we need
 
3169
    # to inform GLib that threads will be used.
 
3170
    GLib.threads_init()
3170
3171
 
3171
3172
    global main_loop
3172
3173
    # From the Avahi example code
3252
3253
                        for k in ("name", "host"):
3253
3254
                            if isinstance(value[k], bytes):
3254
3255
                                value[k] = value[k].decode("utf-8")
3255
 
                        if "key_id" not in value:
 
3256
                        if not value.has_key("key_id"):
3256
3257
                            value["key_id"] = ""
3257
 
                        elif "fingerprint" not in value:
 
3258
                        elif not value.has_key("fingerprint"):
3258
3259
                            value["fingerprint"] = ""
3259
3260
                    #  old_client_settings
3260
3261
                    # .keys()