/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-07-30 17:03:57 UTC
  • Revision ID: teddy@recompile.se-20190730170357-jte0piul5mq7j5pr
Server: Reap zombies created by multiprocessing.Process()

When creating checkers as multiprocessing.Process() objects, the
multiprocessing module also creates a parent process (for the
call_pipe() function) to call the actual checker process, but this
parent process is not reaped.  This is not a huge problem, since the
zombie is always reaped automatically the next time the multiprocess
starts a new process, but the zombies can be up to as many as there
have ever been simultaneous checker processes.  To fix this, the
process object must be join():ed when they report completion of the
child checker process.

* mandos (Client): Fix doc string to correctly state that
                   Client.checker is a multiprocess.Process() and not
                   a subprocess.Popen() object.
  (Client.checker_callback): After the returncode of the checker
                             process has been read, wait for the
                             self.checker Process object to finish by
                             calling join() on it.

Reported-by: Peter Palfrader <weasel@debian.org>

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!/usr/bin/python
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 -*-
 
2
# -*- mode: python; coding: utf-8 -*-
3
3
#
4
4
# Mandos server - give out binary blobs to connecting clients.
5
5
#
77
77
import itertools
78
78
import collections
79
79
import codecs
80
 
import unittest
81
80
 
82
81
import dbus
83
82
import dbus.service
84
 
import gi
85
83
from gi.repository import GLib
86
84
from dbus.mainloop.glib import DBusGMainLoop
87
85
import ctypes
89
87
import xml.dom.minidom
90
88
import inspect
91
89
 
92
 
if sys.version_info.major == 2:
93
 
    __metaclass__ = type
94
 
 
95
 
# Show warnings by default
96
 
if not sys.warnoptions:
97
 
    import warnings
98
 
    warnings.simplefilter("default")
99
 
 
100
90
# Try to find the value of SO_BINDTODEVICE:
101
91
try:
102
92
    # This is where SO_BINDTODEVICE is in Python 3.3 (or 3.4?) and
125
115
if sys.version_info.major == 2:
126
116
    str = unicode
127
117
 
128
 
if sys.version_info < (3, 2):
129
 
    configparser.Configparser = configparser.SafeConfigParser
130
 
 
131
 
version = "1.8.8"
 
118
version = "1.8.4"
132
119
stored_state_file = "clients.pickle"
133
120
 
134
121
logger = logging.getLogger()
135
 
logging.captureWarnings(True)   # Show warnings via the logging system
136
122
syslogger = None
137
123
 
138
124
try:
193
179
    pass
194
180
 
195
181
 
196
 
class PGPEngine:
 
182
class PGPEngine(object):
197
183
    """A simple class for OpenPGP symmetric encryption & decryption"""
198
184
 
199
185
    def __init__(self):
289
275
 
290
276
 
291
277
# Pretend that we have an Avahi module
292
 
class avahi:
 
278
class avahi(object):
293
279
    """This isn't so much a class as it is a module-like namespace."""
294
280
    IF_UNSPEC = -1               # avahi-common/address.h
295
281
    PROTO_UNSPEC = -1            # avahi-common/address.h
329
315
    pass
330
316
 
331
317
 
332
 
class AvahiService:
 
318
class AvahiService(object):
333
319
    """An Avahi (Zeroconf) service.
334
320
 
335
321
    Attributes:
517
503
 
518
504
 
519
505
# Pretend that we have a GnuTLS module
520
 
class gnutls:
 
506
class gnutls(object):
521
507
    """This isn't so much a class as it is a module-like namespace."""
522
508
 
523
509
    library = ctypes.util.find_library("gnutls")
586
572
        pass
587
573
 
588
574
    # Classes
589
 
    class Credentials:
 
575
    class Credentials(object):
590
576
        def __init__(self):
591
577
            self._c_object = gnutls.certificate_credentials_t()
592
578
            gnutls.certificate_allocate_credentials(
596
582
        def __del__(self):
597
583
            gnutls.certificate_free_credentials(self._c_object)
598
584
 
599
 
    class ClientSession:
 
585
    class ClientSession(object):
600
586
        def __init__(self, socket, credentials=None):
601
587
            self._c_object = gnutls.session_t()
602
588
            gnutls_flags = gnutls.CLIENT
828
814
    connection.close()
829
815
 
830
816
 
831
 
class Client:
 
817
class Client(object):
832
818
    """A representation of a client host served by this server.
833
819
 
834
820
    Attributes:
1150
1136
                kwargs=popen_args)
1151
1137
            self.checker.start()
1152
1138
            self.checker_callback_tag = GLib.io_add_watch(
1153
 
                GLib.IOChannel.unix_new(pipe[0].fileno()),
1154
 
                GLib.PRIORITY_DEFAULT, GLib.IO_IN,
 
1139
                pipe[0].fileno(), GLib.IO_IN,
1155
1140
                self.checker_callback, pipe[0], command)
1156
1141
        # Re-run this periodically if run by GLib.timeout_add
1157
1142
        return True
2224
2209
    del _interface
2225
2210
 
2226
2211
 
2227
 
class ProxyClient:
 
2212
class ProxyClient(object):
2228
2213
    def __init__(self, child_pipe, key_id, fpr, address):
2229
2214
        self._pipe = child_pipe
2230
2215
        self._pipe.send(('init', key_id, fpr, address))
2503
2488
        return hex_fpr
2504
2489
 
2505
2490
 
2506
 
class MultiprocessingMixIn:
 
2491
class MultiprocessingMixIn(object):
2507
2492
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
2508
2493
 
2509
2494
    def sub_process_main(self, request, address):
2521
2506
        return proc
2522
2507
 
2523
2508
 
2524
 
class MultiprocessingMixInWithPipe(MultiprocessingMixIn):
 
2509
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
2525
2510
    """ adds a pipe to the MixIn """
2526
2511
 
2527
2512
    def process_request(self, request, client_address):
2542
2527
 
2543
2528
 
2544
2529
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
2545
 
                     socketserver.TCPServer):
 
2530
                     socketserver.TCPServer, object):
2546
2531
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
2547
2532
 
2548
2533
    Attributes:
2681
2666
    def add_pipe(self, parent_pipe, proc):
2682
2667
        # Call "handle_ipc" for both data and EOF events
2683
2668
        GLib.io_add_watch(
2684
 
            GLib.IOChannel.unix_new(parent_pipe.fileno()),
2685
 
            GLib.PRIORITY_DEFAULT, GLib.IO_IN | GLib.IO_HUP,
 
2669
            parent_pipe.fileno(),
 
2670
            GLib.IO_IN | GLib.IO_HUP,
2686
2671
            functools.partial(self.handle_ipc,
2687
2672
                              parent_pipe=parent_pipe,
2688
2673
                              proc=proc))
2726
2711
                return False
2727
2712
 
2728
2713
            GLib.io_add_watch(
2729
 
                GLib.IOChannel.unix_new(parent_pipe.fileno()),
2730
 
                GLib.PRIORITY_DEFAULT, GLib.IO_IN | GLib.IO_HUP,
 
2714
                parent_pipe.fileno(),
 
2715
                GLib.IO_IN | GLib.IO_HUP,
2731
2716
                functools.partial(self.handle_ipc,
2732
2717
                                  parent_pipe=parent_pipe,
2733
2718
                                  proc=proc,
2983
2968
 
2984
2969
    options = parser.parse_args()
2985
2970
 
 
2971
    if options.check:
 
2972
        import doctest
 
2973
        fail_count, test_count = doctest.testmod()
 
2974
        sys.exit(os.EX_OK if fail_count == 0 else 1)
 
2975
 
2986
2976
    # Default values for config file for server-global settings
2987
2977
    if gnutls.has_rawpk:
2988
2978
        priority = ("SECURE128:!CTYPE-X.509:+CTYPE-RAWPK:!RSA"
3008
2998
    del priority
3009
2999
 
3010
3000
    # Parse config file for server-global settings
3011
 
    server_config = configparser.ConfigParser(server_defaults)
 
3001
    server_config = configparser.SafeConfigParser(server_defaults)
3012
3002
    del server_defaults
3013
3003
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
3014
 
    # Convert the ConfigParser object to a dict
 
3004
    # Convert the SafeConfigParser object to a dict
3015
3005
    server_settings = server_config.defaults()
3016
3006
    # Use the appropriate methods on the non-string config options
3017
3007
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
3089
3079
                                  server_settings["servicename"])))
3090
3080
 
3091
3081
    # Parse config file with clients
3092
 
    client_config = configparser.ConfigParser(Client.client_defaults)
 
3082
    client_config = configparser.SafeConfigParser(Client
 
3083
                                                  .client_defaults)
3093
3084
    client_config.read(os.path.join(server_settings["configdir"],
3094
3085
                                    "clients.conf"))
3095
3086
 
3166
3157
        # Close all input and output, do double fork, etc.
3167
3158
        daemon()
3168
3159
 
3169
 
    if gi.version_info < (3, 10, 2):
3170
 
        # multiprocessing will use threads, so before we use GLib we
3171
 
        # need to inform GLib that threads will be used.
3172
 
        GLib.threads_init()
 
3160
    # multiprocessing will use threads, so before we use GLib we need
 
3161
    # to inform GLib that threads will be used.
 
3162
    GLib.threads_init()
3173
3163
 
3174
3164
    global main_loop
3175
3165
    # From the Avahi example code
3603
3593
                sys.exit(1)
3604
3594
            # End of Avahi example code
3605
3595
 
3606
 
        GLib.io_add_watch(
3607
 
            GLib.IOChannel.unix_new(tcp_server.fileno()),
3608
 
            GLib.PRIORITY_DEFAULT, GLib.IO_IN,
3609
 
            lambda *args, **kwargs: (tcp_server.handle_request
3610
 
                                     (*args[2:], **kwargs) or True))
 
3596
        GLib.io_add_watch(tcp_server.fileno(), GLib.IO_IN,
 
3597
                          lambda *args, **kwargs:
 
3598
                          (tcp_server.handle_request
 
3599
                           (*args[2:], **kwargs) or True))
3611
3600
 
3612
3601
        logger.debug("Starting main loop")
3613
3602
        main_loop.run()
3623
3612
    # Must run before the D-Bus bus name gets deregistered
3624
3613
    cleanup()
3625
3614
 
3626
 
 
3627
 
def should_only_run_tests():
3628
 
    parser = argparse.ArgumentParser(add_help=False)
3629
 
    parser.add_argument("--check", action='store_true')
3630
 
    args, unknown_args = parser.parse_known_args()
3631
 
    run_tests = args.check
3632
 
    if run_tests:
3633
 
        # Remove --check argument from sys.argv
3634
 
        sys.argv[1:] = unknown_args
3635
 
    return run_tests
3636
 
 
3637
 
# Add all tests from doctest strings
3638
 
def load_tests(loader, tests, none):
3639
 
    import doctest
3640
 
    tests.addTests(doctest.DocTestSuite())
3641
 
    return tests
3642
3615
 
3643
3616
if __name__ == '__main__':
3644
 
    try:
3645
 
        if should_only_run_tests():
3646
 
            # Call using ./mandos --check [--verbose]
3647
 
            unittest.main()
3648
 
        else:
3649
 
            main()
3650
 
    finally:
3651
 
        logging.shutdown()
 
3617
    main()