/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: 2012-06-17 02:30:59 UTC
  • Revision ID: teddy@recompile.se-20120617023059-em4nfnxg1tsn64xj
* plugins.d/mandos-client (start_mandos_communication): Bug fix; skip
                                                        non-specified
                                                        interfaces.
  (main): Use lower_privileges() consistently.  Bug fix: Don't remove
          "none" from list of interfaces.  Make --interface=none work
          again by not bringing up interfaces specified after "none".
* plugins.d/mandos-client.xml (OPTIONS): Document new meaning of
                                         specifying --interface=none
                                         together with other
                                         interface names,

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
# "AvahiService" class, and some lines in "main".
12
12
13
13
# Everything else is
14
 
# Copyright © 2008-2013 Teddy Hogeborn
15
 
# Copyright © 2008-2013 Björn Påhlsson
 
14
# Copyright © 2008-2012 Teddy Hogeborn
 
15
# Copyright © 2008-2012 Björn Påhlsson
16
16
17
17
# This program is free software: you can redistribute it and/or modify
18
18
# it under the terms of the GNU General Public License as published by
68
68
import binascii
69
69
import tempfile
70
70
import itertools
71
 
import collections
72
71
 
73
72
import dbus
74
73
import dbus.service
79
78
import ctypes.util
80
79
import xml.dom.minidom
81
80
import inspect
 
81
import GnuPGInterface
82
82
 
83
83
try:
84
84
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
88
88
    except ImportError:
89
89
        SO_BINDTODEVICE = None
90
90
 
91
 
version = "1.6.1"
 
91
version = "1.5.5"
92
92
stored_state_file = "clients.pickle"
93
93
 
94
94
logger = logging.getLogger()
139
139
class PGPEngine(object):
140
140
    """A simple class for OpenPGP symmetric encryption & decryption"""
141
141
    def __init__(self):
 
142
        self.gnupg = GnuPGInterface.GnuPG()
142
143
        self.tempdir = tempfile.mkdtemp(prefix="mandos-")
143
 
        self.gnupgargs = ['--batch',
144
 
                          '--home', self.tempdir,
145
 
                          '--force-mdc',
146
 
                          '--quiet',
147
 
                          '--no-use-agent']
 
144
        self.gnupg = GnuPGInterface.GnuPG()
 
145
        self.gnupg.options.meta_interactive = False
 
146
        self.gnupg.options.homedir = self.tempdir
 
147
        self.gnupg.options.extra_args.extend(['--force-mdc',
 
148
                                              '--quiet',
 
149
                                              '--no-use-agent'])
148
150
    
149
151
    def __enter__(self):
150
152
        return self
175
177
        return b"mandos" + binascii.hexlify(password)
176
178
    
177
179
    def encrypt(self, data, password):
178
 
        passphrase = self.password_encode(password)
179
 
        with tempfile.NamedTemporaryFile(dir=self.tempdir
180
 
                                         ) as passfile:
181
 
            passfile.write(passphrase)
182
 
            passfile.flush()
183
 
            proc = subprocess.Popen(['gpg', '--symmetric',
184
 
                                     '--passphrase-file',
185
 
                                     passfile.name]
186
 
                                    + self.gnupgargs,
187
 
                                    stdin = subprocess.PIPE,
188
 
                                    stdout = subprocess.PIPE,
189
 
                                    stderr = subprocess.PIPE)
190
 
            ciphertext, err = proc.communicate(input = data)
191
 
        if proc.returncode != 0:
192
 
            raise PGPError(err)
 
180
        self.gnupg.passphrase = self.password_encode(password)
 
181
        with open(os.devnull, "w") as devnull:
 
182
            try:
 
183
                proc = self.gnupg.run(['--symmetric'],
 
184
                                      create_fhs=['stdin', 'stdout'],
 
185
                                      attach_fhs={'stderr': devnull})
 
186
                with contextlib.closing(proc.handles['stdin']) as f:
 
187
                    f.write(data)
 
188
                with contextlib.closing(proc.handles['stdout']) as f:
 
189
                    ciphertext = f.read()
 
190
                proc.wait()
 
191
            except IOError as e:
 
192
                raise PGPError(e)
 
193
        self.gnupg.passphrase = None
193
194
        return ciphertext
194
195
    
195
196
    def decrypt(self, data, password):
196
 
        passphrase = self.password_encode(password)
197
 
        with tempfile.NamedTemporaryFile(dir = self.tempdir
198
 
                                         ) as passfile:
199
 
            passfile.write(passphrase)
200
 
            passfile.flush()
201
 
            proc = subprocess.Popen(['gpg', '--decrypt',
202
 
                                     '--passphrase-file',
203
 
                                     passfile.name]
204
 
                                    + self.gnupgargs,
205
 
                                    stdin = subprocess.PIPE,
206
 
                                    stdout = subprocess.PIPE,
207
 
                                    stderr = subprocess.PIPE)
208
 
            decrypted_plaintext, err = proc.communicate(input
209
 
                                                        = data)
210
 
        if proc.returncode != 0:
211
 
            raise PGPError(err)
 
197
        self.gnupg.passphrase = self.password_encode(password)
 
198
        with open(os.devnull, "w") as devnull:
 
199
            try:
 
200
                proc = self.gnupg.run(['--decrypt'],
 
201
                                      create_fhs=['stdin', 'stdout'],
 
202
                                      attach_fhs={'stderr': devnull})
 
203
                with contextlib.closing(proc.handles['stdin']) as f:
 
204
                    f.write(data)
 
205
                with contextlib.closing(proc.handles['stdout']) as f:
 
206
                    decrypted_plaintext = f.read()
 
207
                proc.wait()
 
208
            except IOError as e:
 
209
                raise PGPError(e)
 
210
        self.gnupg.passphrase = None
212
211
        return decrypted_plaintext
213
212
 
214
213
 
234
233
               Used to optionally bind to the specified interface.
235
234
    name: string; Example: 'Mandos'
236
235
    type: string; Example: '_mandos._tcp'.
237
 
     See <https://www.iana.org/assignments/service-names-port-numbers>
 
236
                  See <http://www.dns-sd.org/ServiceTypes.html>
238
237
    port: integer; what port to announce
239
238
    TXT: list of strings; TXT record for the service
240
239
    domain: string; Domain to publish on, default to .local if empty.
440
439
    runtime_expansions: Allowed attributes for runtime expansion.
441
440
    expires:    datetime.datetime(); time (UTC) when a client will be
442
441
                disabled, or None
443
 
    server_settings: The server_settings dict from main()
444
442
    """
445
443
    
446
444
    runtime_expansions = ("approval_delay", "approval_duration",
448
446
                          "fingerprint", "host", "interval",
449
447
                          "last_approval_request", "last_checked_ok",
450
448
                          "last_enabled", "name", "timeout")
451
 
    client_defaults = { "timeout": "PT5M",
452
 
                        "extended_timeout": "PT15M",
453
 
                        "interval": "PT2M",
 
449
    client_defaults = { "timeout": "5m",
 
450
                        "extended_timeout": "15m",
 
451
                        "interval": "2m",
454
452
                        "checker": "fping -q -- %%(host)s",
455
453
                        "host": "",
456
 
                        "approval_delay": "PT0S",
457
 
                        "approval_duration": "PT1S",
 
454
                        "approval_delay": "0s",
 
455
                        "approval_duration": "1s",
458
456
                        "approved_by_default": "True",
459
457
                        "enabled": "True",
460
458
                        }
521
519
        
522
520
        return settings
523
521
    
524
 
    def __init__(self, settings, name = None, server_settings=None):
 
522
    def __init__(self, settings, name = None):
525
523
        self.name = name
526
 
        if server_settings is None:
527
 
            server_settings = {}
528
 
        self.server_settings = server_settings
529
524
        # adding all client settings
530
525
        for setting, value in settings.iteritems():
531
526
            setattr(self, setting, value)
715
710
                # in normal mode, that is already done by daemon(),
716
711
                # and in debug mode we don't want to.  (Stdin is
717
712
                # always replaced by /dev/null.)
718
 
                # The exception is when not debugging but nevertheless
719
 
                # running in the foreground; use the previously
720
 
                # created wnull.
721
 
                popen_args = {}
722
 
                if (not self.server_settings["debug"]
723
 
                    and self.server_settings["foreground"]):
724
 
                    popen_args.update({"stdout": wnull,
725
 
                                       "stderr": wnull })
726
713
                self.checker = subprocess.Popen(command,
727
714
                                                close_fds=True,
728
 
                                                shell=True, cwd="/",
729
 
                                                **popen_args)
 
715
                                                shell=True, cwd="/")
730
716
            except OSError as error:
731
717
                logger.error("Failed to start subprocess",
732
718
                             exc_info=error)
733
 
                return True
734
719
            self.checker_callback_tag = (gobject.child_watch_add
735
720
                                         (self.checker.pid,
736
721
                                          self.checker_callback,
737
722
                                          data=command))
738
723
            # The checker may have completed before the gobject
739
724
            # watch was added.  Check for this.
740
 
            try:
741
 
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
742
 
            except OSError as error:
743
 
                if error.errno == errno.ECHILD:
744
 
                    # This should never happen
745
 
                    logger.error("Child process vanished",
746
 
                                 exc_info=error)
747
 
                    return True
748
 
                raise
 
725
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
749
726
            if pid:
750
727
                gobject.source_remove(self.checker_callback_tag)
751
728
                self.checker_callback(pid, status, command)
1091
1068
                interface_names.add(alt_interface)
1092
1069
                # Is this a D-Bus signal?
1093
1070
                if getattr(attribute, "_dbus_is_signal", False):
1094
 
                    # Extract the original non-method undecorated
1095
 
                    # function by black magic
 
1071
                    # Extract the original non-method function by
 
1072
                    # black magic
1096
1073
                    nonmethod_func = (dict(
1097
1074
                            zip(attribute.func_code.co_freevars,
1098
1075
                                attribute.__closure__))["func"]
1971
1948
                try:
1972
1949
                    self.socket.setsockopt(socket.SOL_SOCKET,
1973
1950
                                           SO_BINDTODEVICE,
1974
 
                                           str(self.interface + '\0'))
 
1951
                                           str(self.interface
 
1952
                                               + '\0'))
1975
1953
                except socket.error as error:
1976
1954
                    if error.errno == errno.EPERM:
1977
 
                        logger.error("No permission to bind to"
1978
 
                                     " interface %s", self.interface)
 
1955
                        logger.error("No permission to"
 
1956
                                     " bind to interface %s",
 
1957
                                     self.interface)
1979
1958
                    elif error.errno == errno.ENOPROTOOPT:
1980
1959
                        logger.error("SO_BINDTODEVICE not available;"
1981
1960
                                     " cannot bind to interface %s",
1982
1961
                                     self.interface)
1983
1962
                    elif error.errno == errno.ENODEV:
1984
 
                        logger.error("Interface %s does not exist,"
1985
 
                                     " cannot bind", self.interface)
 
1963
                        logger.error("Interface %s does not"
 
1964
                                     " exist, cannot bind",
 
1965
                                     self.interface)
1986
1966
                    else:
1987
1967
                        raise
1988
1968
        # Only bind(2) the socket if we really need to.
1991
1971
                if self.address_family == socket.AF_INET6:
1992
1972
                    any_address = "::" # in6addr_any
1993
1973
                else:
1994
 
                    any_address = "0.0.0.0" # INADDR_ANY
 
1974
                    any_address = socket.INADDR_ANY
1995
1975
                self.server_address = (any_address,
1996
1976
                                       self.server_address[1])
1997
1977
            elif not self.server_address[1]:
2113
2093
        return True
2114
2094
 
2115
2095
 
2116
 
def rfc3339_duration_to_delta(duration):
2117
 
    """Parse an RFC 3339 "duration" and return a datetime.timedelta
2118
 
    
2119
 
    >>> rfc3339_duration_to_delta("P7D")
2120
 
    datetime.timedelta(7)
2121
 
    >>> rfc3339_duration_to_delta("PT60S")
2122
 
    datetime.timedelta(0, 60)
2123
 
    >>> rfc3339_duration_to_delta("PT60M")
2124
 
    datetime.timedelta(0, 3600)
2125
 
    >>> rfc3339_duration_to_delta("PT24H")
2126
 
    datetime.timedelta(1)
2127
 
    >>> rfc3339_duration_to_delta("P1W")
2128
 
    datetime.timedelta(7)
2129
 
    >>> rfc3339_duration_to_delta("PT5M30S")
2130
 
    datetime.timedelta(0, 330)
2131
 
    >>> rfc3339_duration_to_delta("P1DT3M20S")
2132
 
    datetime.timedelta(1, 200)
2133
 
    """
2134
 
    
2135
 
    # Parsing an RFC 3339 duration with regular expressions is not
2136
 
    # possible - there would have to be multiple places for the same
2137
 
    # values, like seconds.  The current code, while more esoteric, is
2138
 
    # cleaner without depending on a parsing library.  If Python had a
2139
 
    # built-in library for parsing we would use it, but we'd like to
2140
 
    # avoid excessive use of external libraries.
2141
 
    
2142
 
    # New type for defining tokens, syntax, and semantics all-in-one
2143
 
    Token = collections.namedtuple("Token",
2144
 
                                   ("regexp", # To match token; if
2145
 
                                              # "value" is not None,
2146
 
                                              # must have a "group"
2147
 
                                              # containing digits
2148
 
                                    "value",  # datetime.timedelta or
2149
 
                                              # None
2150
 
                                    "followers")) # Tokens valid after
2151
 
                                                  # this token
2152
 
    # RFC 3339 "duration" tokens, syntax, and semantics; taken from
2153
 
    # the "duration" ABNF definition in RFC 3339, Appendix A.
2154
 
    token_end = Token(re.compile(r"$"), None, frozenset())
2155
 
    token_second = Token(re.compile(r"(\d+)S"),
2156
 
                         datetime.timedelta(seconds=1),
2157
 
                         frozenset((token_end,)))
2158
 
    token_minute = Token(re.compile(r"(\d+)M"),
2159
 
                         datetime.timedelta(minutes=1),
2160
 
                         frozenset((token_second, token_end)))
2161
 
    token_hour = Token(re.compile(r"(\d+)H"),
2162
 
                       datetime.timedelta(hours=1),
2163
 
                       frozenset((token_minute, token_end)))
2164
 
    token_time = Token(re.compile(r"T"),
2165
 
                       None,
2166
 
                       frozenset((token_hour, token_minute,
2167
 
                                  token_second)))
2168
 
    token_day = Token(re.compile(r"(\d+)D"),
2169
 
                      datetime.timedelta(days=1),
2170
 
                      frozenset((token_time, token_end)))
2171
 
    token_month = Token(re.compile(r"(\d+)M"),
2172
 
                        datetime.timedelta(weeks=4),
2173
 
                        frozenset((token_day, token_end)))
2174
 
    token_year = Token(re.compile(r"(\d+)Y"),
2175
 
                       datetime.timedelta(weeks=52),
2176
 
                       frozenset((token_month, token_end)))
2177
 
    token_week = Token(re.compile(r"(\d+)W"),
2178
 
                       datetime.timedelta(weeks=1),
2179
 
                       frozenset((token_end,)))
2180
 
    token_duration = Token(re.compile(r"P"), None,
2181
 
                           frozenset((token_year, token_month,
2182
 
                                      token_day, token_time,
2183
 
                                      token_week))),
2184
 
    # Define starting values
2185
 
    value = datetime.timedelta() # Value so far
2186
 
    found_token = None
2187
 
    followers = frozenset(token_duration,) # Following valid tokens
2188
 
    s = duration                # String left to parse
2189
 
    # Loop until end token is found
2190
 
    while found_token is not token_end:
2191
 
        # Search for any currently valid tokens
2192
 
        for token in followers:
2193
 
            match = token.regexp.match(s)
2194
 
            if match is not None:
2195
 
                # Token found
2196
 
                if token.value is not None:
2197
 
                    # Value found, parse digits
2198
 
                    factor = int(match.group(1), 10)
2199
 
                    # Add to value so far
2200
 
                    value += factor * token.value
2201
 
                # Strip token from string
2202
 
                s = token.regexp.sub("", s, 1)
2203
 
                # Go to found token
2204
 
                found_token = token
2205
 
                # Set valid next tokens
2206
 
                followers = found_token.followers
2207
 
                break
2208
 
        else:
2209
 
            # No currently valid tokens were found
2210
 
            raise ValueError("Invalid RFC 3339 duration")
2211
 
    # End token found
2212
 
    return value
2213
 
 
2214
 
 
2215
2096
def string_to_delta(interval):
2216
2097
    """Parse a string and return a datetime.timedelta
2217
2098
    
2228
2109
    >>> string_to_delta('5m 30s')
2229
2110
    datetime.timedelta(0, 330)
2230
2111
    """
2231
 
    
2232
 
    try:
2233
 
        return rfc3339_duration_to_delta(interval)
2234
 
    except ValueError:
2235
 
        pass
2236
 
    
2237
2112
    timevalue = datetime.timedelta(0)
2238
2113
    for s in interval.split():
2239
2114
        try:
2302
2177
                        help="Run self-test")
2303
2178
    parser.add_argument("--debug", action="store_true",
2304
2179
                        help="Debug mode; run in foreground and log"
2305
 
                        " to terminal", default=None)
 
2180
                        " to terminal")
2306
2181
    parser.add_argument("--debuglevel", metavar="LEVEL",
2307
2182
                        help="Debug level for stdout output")
2308
2183
    parser.add_argument("--priority", help="GnuTLS"
2315
2190
                        " files")
2316
2191
    parser.add_argument("--no-dbus", action="store_false",
2317
2192
                        dest="use_dbus", help="Do not provide D-Bus"
2318
 
                        " system bus interface", default=None)
 
2193
                        " system bus interface")
2319
2194
    parser.add_argument("--no-ipv6", action="store_false",
2320
 
                        dest="use_ipv6", help="Do not use IPv6",
2321
 
                        default=None)
 
2195
                        dest="use_ipv6", help="Do not use IPv6")
2322
2196
    parser.add_argument("--no-restore", action="store_false",
2323
2197
                        dest="restore", help="Do not restore stored"
2324
 
                        " state", default=None)
 
2198
                        " state")
2325
2199
    parser.add_argument("--socket", type=int,
2326
2200
                        help="Specify a file descriptor to a network"
2327
2201
                        " socket to use instead of creating one")
2328
2202
    parser.add_argument("--statedir", metavar="DIR",
2329
2203
                        help="Directory to save/restore state in")
2330
 
    parser.add_argument("--foreground", action="store_true",
2331
 
                        help="Run in foreground", default=None)
2332
2204
    
2333
2205
    options = parser.parse_args()
2334
2206
    
2343
2215
                        "port": "",
2344
2216
                        "debug": "False",
2345
2217
                        "priority":
2346
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:+SIGN-RSA-SHA224",
 
2218
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
2347
2219
                        "servicename": "Mandos",
2348
2220
                        "use_dbus": "True",
2349
2221
                        "use_ipv6": "True",
2350
2222
                        "debuglevel": "",
2351
2223
                        "restore": "True",
2352
2224
                        "socket": "",
2353
 
                        "statedir": "/var/lib/mandos",
2354
 
                        "foreground": "False",
 
2225
                        "statedir": "/var/lib/mandos"
2355
2226
                        }
2356
2227
    
2357
2228
    # Parse config file for server-global settings
2362
2233
    # Convert the SafeConfigParser object to a dict
2363
2234
    server_settings = server_config.defaults()
2364
2235
    # Use the appropriate methods on the non-string config options
2365
 
    for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
 
2236
    for option in ("debug", "use_dbus", "use_ipv6"):
2366
2237
        server_settings[option] = server_config.getboolean("DEFAULT",
2367
2238
                                                           option)
2368
2239
    if server_settings["port"]:
2384
2255
    for option in ("interface", "address", "port", "debug",
2385
2256
                   "priority", "servicename", "configdir",
2386
2257
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
2387
 
                   "statedir", "socket", "foreground"):
 
2258
                   "statedir", "socket"):
2388
2259
        value = getattr(options, option)
2389
2260
        if value is not None:
2390
2261
            server_settings[option] = value
2393
2264
    for option in server_settings.keys():
2394
2265
        if type(server_settings[option]) is str:
2395
2266
            server_settings[option] = unicode(server_settings[option])
2396
 
    # Force all boolean options to be boolean
2397
 
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
2398
 
                   "foreground"):
2399
 
        server_settings[option] = bool(server_settings[option])
2400
 
    # Debug implies foreground
2401
 
    if server_settings["debug"]:
2402
 
        server_settings["foreground"] = True
2403
2267
    # Now we have our good server settings in "server_settings"
2404
2268
    
2405
2269
    ##################################################################
2411
2275
    use_ipv6 = server_settings["use_ipv6"]
2412
2276
    stored_state_path = os.path.join(server_settings["statedir"],
2413
2277
                                     stored_state_file)
2414
 
    foreground = server_settings["foreground"]
2415
2278
    
2416
2279
    if debug:
2417
2280
        initlogger(debug, logging.DEBUG)
2449
2312
                              use_dbus=use_dbus,
2450
2313
                              socketfd=(server_settings["socket"]
2451
2314
                                        or None))
2452
 
    if not foreground:
2453
 
        pidfilename = "/run/mandos.pid"
2454
 
        pidfile = None
 
2315
    if not debug:
 
2316
        pidfilename = "/var/run/mandos.pid"
2455
2317
        try:
2456
2318
            pidfile = open(pidfilename, "w")
2457
2319
        except IOError as e:
2496
2358
            os.close(null)
2497
2359
    
2498
2360
    # Need to fork before connecting to D-Bus
2499
 
    if not foreground:
 
2361
    if not debug:
2500
2362
        # Close all input and output, do double fork, etc.
2501
2363
        daemon()
2502
2364
    
2542
2404
    old_client_settings = {}
2543
2405
    clients_data = {}
2544
2406
    
2545
 
    # This is used to redirect stdout and stderr for checker processes
2546
 
    global wnull
2547
 
    wnull = open(os.devnull, "w") # A writable /dev/null
2548
 
    # Only used if server is running in foreground but not in debug
2549
 
    # mode
2550
 
    if debug or not foreground:
2551
 
        wnull.close()
2552
 
    
2553
2407
    # Get client data and settings from last running state.
2554
2408
    if server_settings["restore"]:
2555
2409
        try:
2571
2425
    
2572
2426
    with PGPEngine() as pgp:
2573
2427
        for client_name, client in clients_data.iteritems():
2574
 
            # Skip removed clients
2575
 
            if client_name not in client_settings:
2576
 
                continue
2577
 
            
2578
2428
            # Decide which value to use after restoring saved state.
2579
2429
            # We have three different values: Old config file,
2580
2430
            # new config file, and saved state.
2642
2492
    # Create all client objects
2643
2493
    for client_name, client in clients_data.iteritems():
2644
2494
        tcp_server.clients[client_name] = client_class(
2645
 
            name = client_name, settings = client,
2646
 
            server_settings = server_settings)
 
2495
            name = client_name, settings = client)
2647
2496
    
2648
2497
    if not tcp_server.clients:
2649
2498
        logger.warning("No clients defined")
2650
2499
    
2651
 
    if not foreground:
2652
 
        if pidfile is not None:
2653
 
            try:
2654
 
                with pidfile:
2655
 
                    pid = os.getpid()
2656
 
                    pidfile.write(str(pid) + "\n".encode("utf-8"))
2657
 
            except IOError:
2658
 
                logger.error("Could not write to file %r with PID %d",
2659
 
                             pidfilename, pid)
2660
 
        del pidfile
 
2500
    if not debug:
 
2501
        try:
 
2502
            with pidfile:
 
2503
                pid = os.getpid()
 
2504
                pidfile.write(str(pid) + "\n".encode("utf-8"))
 
2505
            del pidfile
 
2506
        except IOError:
 
2507
            logger.error("Could not write to file %r with PID %d",
 
2508
                         pidfilename, pid)
 
2509
        except NameError:
 
2510
            # "pidfile" was never created
 
2511
            pass
2661
2512
        del pidfilename
2662
2513
    
2663
2514
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2732
2583
        service.cleanup()
2733
2584
        
2734
2585
        multiprocessing.active_children()
2735
 
        wnull.close()
2736
2586
        if not (tcp_server.clients or client_settings):
2737
2587
            return
2738
2588
        
2750
2600
                # A list of attributes that can not be pickled
2751
2601
                # + secret.
2752
2602
                exclude = set(("bus", "changedstate", "secret",
2753
 
                               "checker", "server_settings"))
 
2603
                               "checker"))
2754
2604
                for name, typ in (inspect.getmembers
2755
2605
                                  (dbus.service.Object)):
2756
2606
                    exclude.add(name)