/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: 2011-12-03 01:04:54 UTC
  • Revision ID: teddy@recompile.se-20111203010454-9g5cyo2x7xwnacc5
* mandos (ClientDBus.approval_delay, ClientDBus.approval_duration,
  ClientDBus.timeout, ClientDBus.extended_timeout,
  ClientDBus.interval): Bug fix: Cast to correct D-Bus type.
* mandos-ctl (string_to_delta): Interpret unadorned numbers as
                                milliseconds.

Show diffs side-by-side

added added

removed removed

Lines of Context:
63
63
import cPickle as pickle
64
64
import multiprocessing
65
65
import types
 
66
import hashlib
66
67
 
67
68
import dbus
68
69
import dbus.service
73
74
import ctypes.util
74
75
import xml.dom.minidom
75
76
import inspect
 
77
import Crypto.Cipher.AES
76
78
 
77
79
try:
78
80
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
83
85
        SO_BINDTODEVICE = None
84
86
 
85
87
 
86
 
version = "1.4.0"
87
 
 
88
 
#logger = logging.getLogger('mandos')
89
 
logger = logging.Logger('mandos')
 
88
version = "1.4.1"
 
89
 
 
90
logger = logging.getLogger()
 
91
stored_state_path = "/var/lib/mandos/clients.pickle"
 
92
 
90
93
syslogger = (logging.handlers.SysLogHandler
91
94
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
92
95
              address = str("/dev/log")))
96
99
logger.addHandler(syslogger)
97
100
 
98
101
console = logging.StreamHandler()
99
 
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
 
102
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
 
103
                                       ' [%(process)d]:'
100
104
                                       ' %(levelname)s:'
101
105
                                       ' %(message)s'))
102
106
logger.addHandler(console)
103
107
 
 
108
 
104
109
class AvahiError(Exception):
105
110
    def __init__(self, value, *args, **kwargs):
106
111
        self.value = value
164
169
                            .GetAlternativeServiceName(self.name))
165
170
        logger.info("Changing Zeroconf service name to %r ...",
166
171
                    self.name)
167
 
        syslogger.setFormatter(logging.Formatter
168
 
                               ('Mandos (%s) [%%(process)d]:'
169
 
                                ' %%(levelname)s: %%(message)s'
170
 
                                % self.name))
171
172
        self.remove()
172
173
        try:
173
174
            self.add()
193
194
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
194
195
        self.entry_group_state_changed_match = (
195
196
            self.group.connect_to_signal(
196
 
                'StateChanged', self .entry_group_state_changed))
 
197
                'StateChanged', self.entry_group_state_changed))
197
198
        logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
198
199
                     self.name, self.type)
199
200
        self.group.AddService(
265
266
                                 self.server_state_changed)
266
267
        self.server_state_changed(self.server.GetState())
267
268
 
 
269
class AvahiServiceToSyslog(AvahiService):
 
270
    def rename(self):
 
271
        """Add the new name to the syslog messages"""
 
272
        ret = AvahiService.rename(self)
 
273
        syslogger.setFormatter(logging.Formatter
 
274
                               ('Mandos (%s) [%%(process)d]:'
 
275
                                ' %%(levelname)s: %%(message)s'
 
276
                                % self.name))
 
277
        return ret
268
278
 
269
279
def _timedelta_to_milliseconds(td):
270
280
    "Convert a datetime.timedelta() to milliseconds"
289
299
                     instance %(name)s can be used in the command.
290
300
    checker_initiator_tag: a gobject event source tag, or None
291
301
    created:    datetime.datetime(); (UTC) object creation
 
302
    client_structure: Object describing what attributes a client has
 
303
                      and is used for storing the client at exit
292
304
    current_checker_command: string; current running checker_command
293
 
    disable_hook:  If set, called by disable() as disable_hook(self)
294
305
    disable_initiator_tag: a gobject event source tag, or None
295
306
    enabled:    bool()
296
307
    fingerprint: string (40 or 32 hexadecimal digits); used to
299
310
    interval:   datetime.timedelta(); How often to start a new checker
300
311
    last_approval_request: datetime.datetime(); (UTC) or None
301
312
    last_checked_ok: datetime.datetime(); (UTC) or None
 
313
    last_checker_status: integer between 0 and 255 reflecting exit
 
314
                         status of last checker. -1 reflect crashed
 
315
                         checker, or None.
302
316
    last_enabled: datetime.datetime(); (UTC)
303
317
    name:       string; from the config file, used in log messages and
304
318
                        D-Bus identifiers
331
345
    def approval_delay_milliseconds(self):
332
346
        return _timedelta_to_milliseconds(self.approval_delay)
333
347
    
334
 
    def __init__(self, name = None, disable_hook=None, config=None):
 
348
    def __init__(self, name = None, config=None):
335
349
        """Note: the 'checker' key in 'config' sets the
336
350
        'checker_command' attribute and *not* the 'checker'
337
351
        attribute."""
357
371
                            % self.name)
358
372
        self.host = config.get("host", "")
359
373
        self.created = datetime.datetime.utcnow()
360
 
        self.enabled = False
 
374
        self.enabled = True
361
375
        self.last_approval_request = None
362
 
        self.last_enabled = None
 
376
        self.last_enabled = datetime.datetime.utcnow()
363
377
        self.last_checked_ok = None
 
378
        self.last_checker_status = None
364
379
        self.timeout = string_to_delta(config["timeout"])
365
380
        self.extended_timeout = string_to_delta(config
366
381
                                                ["extended_timeout"])
367
382
        self.interval = string_to_delta(config["interval"])
368
 
        self.disable_hook = disable_hook
369
383
        self.checker = None
370
384
        self.checker_initiator_tag = None
371
385
        self.disable_initiator_tag = None
372
 
        self.expires = None
 
386
        self.expires = datetime.datetime.utcnow() + self.timeout
373
387
        self.checker_callback_tag = None
374
388
        self.checker_command = config["checker"]
375
389
        self.current_checker_command = None
376
 
        self.last_connect = None
377
390
        self._approved = None
378
391
        self.approved_by_default = config.get("approved_by_default",
379
392
                                              True)
385
398
        self.changedstate = (multiprocessing_manager
386
399
                             .Condition(multiprocessing_manager
387
400
                                        .Lock()))
 
401
        self.client_structure = [attr for attr
 
402
                                 in self.__dict__.iterkeys()
 
403
                                 if not attr.startswith("_")]
 
404
        self.client_structure.append("client_structure")
 
405
 
 
406
 
 
407
        for name, t in inspect.getmembers(type(self),
 
408
                                          lambda obj:
 
409
                                              isinstance(obj,
 
410
                                                         property)):
 
411
            if not name.startswith("_"):
 
412
                self.client_structure.append(name)
388
413
    
 
414
    # Send notice to process children that client state has changed
389
415
    def send_changedstate(self):
390
 
        self.changedstate.acquire()
391
 
        self.changedstate.notify_all()
392
 
        self.changedstate.release()
 
416
        with self.changedstate:
 
417
            self.changedstate.notify_all()
393
418
    
394
419
    def enable(self):
395
420
        """Start this client's checker and timeout hooks"""
397
422
            # Already enabled
398
423
            return
399
424
        self.send_changedstate()
400
 
        # Schedule a new checker to be started an 'interval' from now,
401
 
        # and every interval from then on.
402
 
        self.checker_initiator_tag = (gobject.timeout_add
403
 
                                      (self.interval_milliseconds(),
404
 
                                       self.start_checker))
405
 
        # Schedule a disable() when 'timeout' has passed
406
425
        self.expires = datetime.datetime.utcnow() + self.timeout
407
 
        self.disable_initiator_tag = (gobject.timeout_add
408
 
                                   (self.timeout_milliseconds(),
409
 
                                    self.disable))
410
426
        self.enabled = True
411
427
        self.last_enabled = datetime.datetime.utcnow()
412
 
        # Also start a new checker *right now*.
413
 
        self.start_checker()
 
428
        self.init_checker()
414
429
    
415
430
    def disable(self, quiet=True):
416
431
        """Disable this client."""
428
443
            gobject.source_remove(self.checker_initiator_tag)
429
444
            self.checker_initiator_tag = None
430
445
        self.stop_checker()
431
 
        if self.disable_hook:
432
 
            self.disable_hook(self)
433
446
        self.enabled = False
434
447
        # Do not run this again if called by a gobject.timeout_add
435
448
        return False
436
449
    
437
450
    def __del__(self):
438
 
        self.disable_hook = None
439
451
        self.disable()
440
 
    
 
452
 
 
453
    def init_checker(self):
 
454
        # Schedule a new checker to be started an 'interval' from now,
 
455
        # and every interval from then on.
 
456
        self.checker_initiator_tag = (gobject.timeout_add
 
457
                                      (self.interval_milliseconds(),
 
458
                                       self.start_checker))
 
459
        # Schedule a disable() when 'timeout' has passed
 
460
        self.disable_initiator_tag = (gobject.timeout_add
 
461
                                   (self.timeout_milliseconds(),
 
462
                                    self.disable))
 
463
        # Also start a new checker *right now*.
 
464
        self.start_checker()
 
465
 
 
466
        
441
467
    def checker_callback(self, pid, condition, command):
442
468
        """The checker has completed, so take appropriate actions."""
443
469
        self.checker_callback_tag = None
444
470
        self.checker = None
445
471
        if os.WIFEXITED(condition):
446
 
            exitstatus = os.WEXITSTATUS(condition)
447
 
            if exitstatus == 0:
 
472
            self.last_checker_status =  os.WEXITSTATUS(condition)
 
473
            if self.last_checker_status == 0:
448
474
                logger.info("Checker for %(name)s succeeded",
449
475
                            vars(self))
450
476
                self.checked_ok()
452
478
                logger.info("Checker for %(name)s failed",
453
479
                            vars(self))
454
480
        else:
 
481
            self.last_checker_status = -1
455
482
            logger.warning("Checker for %(name)s crashed?",
456
483
                           vars(self))
457
484
    
568
595
                raise
569
596
        self.checker = None
570
597
 
 
598
    # Encrypts a client secret and stores it in a varible
 
599
    # encrypted_secret
 
600
    def encrypt_secret(self, key):
 
601
        # Encryption-key need to be of a specific size, so we hash
 
602
        # supplied key
 
603
        hasheng = hashlib.sha256()
 
604
        hasheng.update(key)
 
605
        encryptionkey = hasheng.digest()
 
606
 
 
607
        # Create validation hash so we know at decryption if it was
 
608
        # sucessful
 
609
        hasheng = hashlib.sha256()
 
610
        hasheng.update(self.secret)
 
611
        validationhash = hasheng.digest()
 
612
 
 
613
        # Encrypt secret
 
614
        iv = os.urandom(Crypto.Cipher.AES.block_size)
 
615
        ciphereng = Crypto.Cipher.AES.new(encryptionkey,
 
616
                                        Crypto.Cipher.AES.MODE_CFB, iv)
 
617
        ciphertext = ciphereng.encrypt(validationhash+self.secret)
 
618
        self.encrypted_secret = (ciphertext, iv)
 
619
 
 
620
    # Decrypt a encrypted client secret
 
621
    def decrypt_secret(self, key):
 
622
        # Decryption-key need to be of a specific size, so we hash
 
623
        # supplied key
 
624
        hasheng = hashlib.sha256()
 
625
        hasheng.update(key)
 
626
        encryptionkey = hasheng.digest()
 
627
 
 
628
        # Decrypt encrypted secret
 
629
        ciphertext, iv = self.encrypted_secret
 
630
        ciphereng = Crypto.Cipher.AES.new(encryptionkey,
 
631
                                        Crypto.Cipher.AES.MODE_CFB, iv)
 
632
        plain = ciphereng.decrypt(ciphertext)
 
633
 
 
634
        # Validate decrypted secret to know if it was succesful
 
635
        hasheng = hashlib.sha256()
 
636
        validationhash = plain[:hasheng.digest_size]
 
637
        secret = plain[hasheng.digest_size:]
 
638
        hasheng.update(secret)
 
639
 
 
640
        # If validation fails, we use key as new secret. Otherwise, we
 
641
        # use the decrypted secret
 
642
        if hasheng.digest() == validationhash:
 
643
            self.secret = secret
 
644
        else:
 
645
            self.secret = key
 
646
        del self.encrypted_secret
 
647
 
571
648
 
572
649
def dbus_service_property(dbus_interface, signature="v",
573
650
                          access="readwrite", byte_arrays=False):
874
951
    # dbus.service.Object doesn't use super(), so we can't either.
875
952
    
876
953
    def __init__(self, bus = None, *args, **kwargs):
 
954
        self.bus = bus
877
955
        self._approvals_pending = 0
878
 
        self.bus = bus
879
956
        Client.__init__(self, *args, **kwargs)
 
957
        self.add_to_dbus()
 
958
    
 
959
    def add_to_dbus(self):
880
960
        # Only now, when this client is initialized, can it show up on
881
961
        # the D-Bus
882
962
        client_object_name = unicode(self.name).translate(
932
1012
        datetime_to_dbus, "LastApprovalRequest")
933
1013
    approved_by_default = notifychangeproperty(dbus.Boolean,
934
1014
                                               "ApprovedByDefault")
935
 
    approval_delay = notifychangeproperty(dbus.UInt16,
 
1015
    approval_delay = notifychangeproperty(dbus.UInt64,
936
1016
                                          "ApprovalDelay",
937
1017
                                          type_func =
938
1018
                                          _timedelta_to_milliseconds)
939
1019
    approval_duration = notifychangeproperty(
940
 
        dbus.UInt16, "ApprovalDuration",
 
1020
        dbus.UInt64, "ApprovalDuration",
941
1021
        type_func = _timedelta_to_milliseconds)
942
1022
    host = notifychangeproperty(dbus.String, "Host")
943
 
    timeout = notifychangeproperty(dbus.UInt16, "Timeout",
 
1023
    timeout = notifychangeproperty(dbus.UInt64, "Timeout",
944
1024
                                   type_func =
945
1025
                                   _timedelta_to_milliseconds)
946
1026
    extended_timeout = notifychangeproperty(
947
 
        dbus.UInt16, "ExtendedTimeout",
 
1027
        dbus.UInt64, "ExtendedTimeout",
948
1028
        type_func = _timedelta_to_milliseconds)
949
 
    interval = notifychangeproperty(dbus.UInt16,
 
1029
    interval = notifychangeproperty(dbus.UInt64,
950
1030
                                    "Interval",
951
1031
                                    type_func =
952
1032
                                    _timedelta_to_milliseconds)
1624
1704
        self.enabled = False
1625
1705
        self.clients = clients
1626
1706
        if self.clients is None:
1627
 
            self.clients = set()
 
1707
            self.clients = {}
1628
1708
        self.use_dbus = use_dbus
1629
1709
        self.gnutls_priority = gnutls_priority
1630
1710
        IPv6_TCPServer.__init__(self, server_address,
1677
1757
            fpr = request[1]
1678
1758
            address = request[2]
1679
1759
            
1680
 
            for c in self.clients:
 
1760
            for c in self.clients.itervalues():
1681
1761
                if c.fingerprint == fpr:
1682
1762
                    client = c
1683
1763
                    break
1851
1931
                        " system bus interface")
1852
1932
    parser.add_argument("--no-ipv6", action="store_false",
1853
1933
                        dest="use_ipv6", help="Do not use IPv6")
 
1934
    parser.add_argument("--no-restore", action="store_false",
 
1935
                        dest="restore",
 
1936
                        help="Do not restore stored state",
 
1937
                        default=True)
 
1938
 
1854
1939
    options = parser.parse_args()
1855
1940
    
1856
1941
    if options.check:
1891
1976
    # options, if set.
1892
1977
    for option in ("interface", "address", "port", "debug",
1893
1978
                   "priority", "servicename", "configdir",
1894
 
                   "use_dbus", "use_ipv6", "debuglevel"):
 
1979
                   "use_dbus", "use_ipv6", "debuglevel", "restore"):
1895
1980
        value = getattr(options, option)
1896
1981
        if value is not None:
1897
1982
            server_settings[option] = value
1970
2055
            raise error
1971
2056
    
1972
2057
    if not debug and not debuglevel:
1973
 
        syslogger.setLevel(logging.WARNING)
1974
 
        console.setLevel(logging.WARNING)
 
2058
        logger.setLevel(logging.WARNING)
1975
2059
    if debuglevel:
1976
2060
        level = getattr(logging, debuglevel.upper())
1977
 
        syslogger.setLevel(level)
1978
 
        console.setLevel(level)
 
2061
        logger.setLevel(level)
1979
2062
    
1980
2063
    if debug:
 
2064
        logger.setLevel(logging.DEBUG)
1981
2065
        # Enable all possible GnuTLS debugging
1982
2066
        
1983
2067
        # "Use a log level over 10 to enable all debugging options."
2024
2108
            server_settings["use_dbus"] = False
2025
2109
            tcp_server.use_dbus = False
2026
2110
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2027
 
    service = AvahiService(name = server_settings["servicename"],
2028
 
                           servicetype = "_mandos._tcp",
2029
 
                           protocol = protocol, bus = bus)
 
2111
    service = AvahiServiceToSyslog(name =
 
2112
                                   server_settings["servicename"],
 
2113
                                   servicetype = "_mandos._tcp",
 
2114
                                   protocol = protocol, bus = bus)
2030
2115
    if server_settings["interface"]:
2031
2116
        service.interface = (if_nametoindex
2032
2117
                             (str(server_settings["interface"])))
2038
2123
    if use_dbus:
2039
2124
        client_class = functools.partial(ClientDBusTransitional,
2040
2125
                                         bus = bus)
2041
 
    def client_config_items(config, section):
2042
 
        special_settings = {
2043
 
            "approved_by_default":
2044
 
                lambda: config.getboolean(section,
2045
 
                                          "approved_by_default"),
2046
 
            }
2047
 
        for name, value in config.items(section):
 
2126
    
 
2127
    special_settings = {
 
2128
        # Some settings need to be accessd by special methods;
 
2129
        # booleans need .getboolean(), etc.  Here is a list of them:
 
2130
        "approved_by_default":
 
2131
            lambda section:
 
2132
            client_config.getboolean(section, "approved_by_default"),
 
2133
        }
 
2134
    # Construct a new dict of client settings of this form:
 
2135
    # { client_name: {setting_name: value, ...}, ...}
 
2136
    # with exceptions for any special settings as defined above
 
2137
    client_settings = dict((clientname,
 
2138
                           dict((setting,
 
2139
                                 (value if
 
2140
                                  setting not in special_settings
 
2141
                                  else special_settings[setting]
 
2142
                                  (clientname)))
 
2143
                                for setting, value
 
2144
                                in client_config.items(clientname)))
 
2145
                          for clientname in client_config.sections())
 
2146
    
 
2147
    old_client_settings = {}
 
2148
    clients_data = []
 
2149
 
 
2150
    # Get client data and settings from last running state. 
 
2151
    if server_settings["restore"]:
 
2152
        try:
 
2153
            with open(stored_state_path, "rb") as stored_state:
 
2154
                clients_data, old_client_settings = (
 
2155
                    pickle.load(stored_state))
 
2156
            os.remove(stored_state_path)
 
2157
        except IOError as e:
 
2158
            logger.warning("Could not load persistant state: {0}"
 
2159
                           .format(e))
 
2160
            if e.errno != errno.ENOENT:
 
2161
                raise
 
2162
 
 
2163
    for client in clients_data:
 
2164
        client_name = client["name"]
 
2165
        
 
2166
        # Decide which value to use after restoring saved state.
 
2167
        # We have three different values: Old config file,
 
2168
        # new config file, and saved state.
 
2169
        # New config value takes precedence if it differs from old
 
2170
        # config value, otherwise use saved state.
 
2171
        for name, value in client_settings[client_name].items():
2048
2172
            try:
2049
 
                yield (name, special_settings[name]())
 
2173
                # For each value in new config, check if it differs
 
2174
                # from the old config value (Except for the "secret"
 
2175
                # attribute)
 
2176
                if (name != "secret" and
 
2177
                    value != old_client_settings[client_name][name]):
 
2178
                    setattr(client, name, value)
2050
2179
            except KeyError:
2051
 
                yield (name, value)
 
2180
                pass
 
2181
 
 
2182
        # Clients who has passed its expire date, can still be enabled
 
2183
        # if its last checker was sucessful. Clients who checkers
 
2184
        # failed before we stored it state is asumed to had failed
 
2185
        # checker during downtime.
 
2186
        if client["enabled"] and client["last_checked_ok"]:
 
2187
            if ((datetime.datetime.utcnow()
 
2188
                 - client["last_checked_ok"]) > client["interval"]):
 
2189
                if client["last_checker_status"] != 0:
 
2190
                    client["enabled"] = False
 
2191
                else:
 
2192
                    client["expires"] = (datetime.datetime.utcnow()
 
2193
                                         + client["timeout"])
 
2194
 
 
2195
        client["changedstate"] = (multiprocessing_manager
 
2196
                                  .Condition(multiprocessing_manager
 
2197
                                             .Lock()))
 
2198
        if use_dbus:
 
2199
            new_client = ClientDBusTransitional.__new__(
 
2200
                ClientDBusTransitional)
 
2201
            tcp_server.clients[client_name] = new_client
 
2202
            new_client.bus = bus
 
2203
            for name, value in client.iteritems():
 
2204
                setattr(new_client, name, value)
 
2205
            new_client._approvals_pending = 0
 
2206
            new_client.add_to_dbus()
 
2207
        else:
 
2208
            tcp_server.clients[client_name] = Client.__new__(Client)
 
2209
            for name, value in client.iteritems():
 
2210
                setattr(tcp_server.clients[client_name], name, value)
 
2211
                
 
2212
        tcp_server.clients[client_name].decrypt_secret(
 
2213
            client_settings[client_name]["secret"])            
 
2214
        
 
2215
    # Create/remove clients based on new changes made to config
 
2216
    for clientname in set(old_client_settings) - set(client_settings):
 
2217
        del tcp_server.clients[clientname]
 
2218
    for clientname in set(client_settings) - set(old_client_settings):
 
2219
        tcp_server.clients[clientname] = client_class(name
 
2220
                                                      = clientname,
 
2221
                                                      config =
 
2222
                                                      client_settings
 
2223
                                                      [clientname])
2052
2224
    
2053
 
    tcp_server.clients.update(set(
2054
 
            client_class(name = section,
2055
 
                         config= dict(client_config_items(
2056
 
                        client_config, section)))
2057
 
            for section in client_config.sections()))
2058
2225
    if not tcp_server.clients:
2059
2226
        logger.warning("No clients defined")
2060
2227
        
2103
2270
            def GetAllClients(self):
2104
2271
                "D-Bus method"
2105
2272
                return dbus.Array(c.dbus_object_path
2106
 
                                  for c in tcp_server.clients)
 
2273
                                  for c in
 
2274
                                  tcp_server.clients.itervalues())
2107
2275
            
2108
2276
            @dbus.service.method(_interface,
2109
2277
                                 out_signature="a{oa{sv}}")
2111
2279
                "D-Bus method"
2112
2280
                return dbus.Dictionary(
2113
2281
                    ((c.dbus_object_path, c.GetAll(""))
2114
 
                     for c in tcp_server.clients),
 
2282
                     for c in tcp_server.clients.itervalues()),
2115
2283
                    signature="oa{sv}")
2116
2284
            
2117
2285
            @dbus.service.method(_interface, in_signature="o")
2118
2286
            def RemoveClient(self, object_path):
2119
2287
                "D-Bus method"
2120
 
                for c in tcp_server.clients:
 
2288
                for c in tcp_server.clients.itervalues():
2121
2289
                    if c.dbus_object_path == object_path:
2122
 
                        tcp_server.clients.remove(c)
 
2290
                        del tcp_server.clients[c.name]
2123
2291
                        c.remove_from_connection()
2124
2292
                        # Don't signal anything except ClientRemoved
2125
2293
                        c.disable(quiet=True)
2139
2307
        service.cleanup()
2140
2308
        
2141
2309
        multiprocessing.active_children()
 
2310
        if not (tcp_server.clients or client_settings):
 
2311
            return
 
2312
 
 
2313
        # Store client before exiting. Secrets are encrypted with key
 
2314
        # based on what config file has. If config file is
 
2315
        # removed/edited, old secret will thus be unrecovable.
 
2316
        clients = []
 
2317
        for client in tcp_server.clients.itervalues():
 
2318
            client.encrypt_secret(
 
2319
                client_settings[client.name]["secret"])
 
2320
 
 
2321
            client_dict = {}
 
2322
 
 
2323
            # A list of attributes that will not be stored when
 
2324
            # shutting down.
 
2325
            exclude = set(("bus", "changedstate", "secret"))
 
2326
            for name, typ in inspect.getmembers(dbus.service.Object):
 
2327
                exclude.add(name)
 
2328
                
 
2329
            client_dict["encrypted_secret"] = client.encrypted_secret
 
2330
            for attr in client.client_structure:
 
2331
                if attr not in exclude:
 
2332
                    client_dict[attr] = getattr(client, attr)
 
2333
 
 
2334
            clients.append(client_dict) 
 
2335
            del client_settings[client.name]["secret"]
 
2336
            
 
2337
        try:
 
2338
            with os.fdopen(os.open(stored_state_path,
 
2339
                                   os.O_CREAT|os.O_WRONLY|os.O_TRUNC,
 
2340
                                   stat.S_IRUSR | stat.S_IWUSR),
 
2341
                           "wb") as stored_state:
 
2342
                pickle.dump((clients, client_settings), stored_state)
 
2343
        except IOError as e:
 
2344
            logger.warning("Could not save persistant state: {0}"
 
2345
                           .format(e))
 
2346
            if e.errno != errno.ENOENT:
 
2347
                raise
 
2348
 
 
2349
        # Delete all clients, and settings from config
2142
2350
        while tcp_server.clients:
2143
 
            client = tcp_server.clients.pop()
 
2351
            name, client = tcp_server.clients.popitem()
2144
2352
            if use_dbus:
2145
2353
                client.remove_from_connection()
2146
 
            client.disable_hook = None
2147
2354
            # Don't signal anything except ClientRemoved
2148
2355
            client.disable(quiet=True)
2149
2356
            if use_dbus:
2151
2358
                mandos_dbus_service.ClientRemoved(client
2152
2359
                                                  .dbus_object_path,
2153
2360
                                                  client.name)
 
2361
        client_settings.clear()
2154
2362
    
2155
2363
    atexit.register(cleanup)
2156
2364
    
2157
 
    for client in tcp_server.clients:
 
2365
    for client in tcp_server.clients.itervalues():
2158
2366
        if use_dbus:
2159
2367
            # Emit D-Bus signal
2160
2368
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
2161
 
        client.enable()
 
2369
        # Need to initiate checking of clients
 
2370
        if client.enabled:
 
2371
            client.init_checker()
 
2372
 
2162
2373
    
2163
2374
    tcp_server.enable()
2164
2375
    tcp_server.server_activate()