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