/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
  • Author(s): gregor herrmann
  • Date: 2012-02-21 21:20:12 UTC
  • mto: (237.4.29 release)
  • mto: This revision was merged to the branch mainline in revision 560.
  • Revision ID: teddy@recompile.se-20120221212012-vrbd180yikwm0qdv
* debian/changelog (1.5.3-1.1): New entry.
* debian/control (Source: mandos/Build-Depends): Added "locales-all".
* debian/rules (build-arch-stamp, build-indep-stamp): Add
                                                      LC_ALL=en_US.utf8
                                                      to
                                                      "dh_auto_build"
                                                      invocation.

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-2011 Teddy Hogeborn
15
 
# Copyright © 2008-2011 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
85
85
    except ImportError:
86
86
        SO_BINDTODEVICE = None
87
87
 
88
 
 
89
 
version = "1.4.1"
 
88
version = "1.5.3"
90
89
stored_state_file = "clients.pickle"
91
90
 
92
91
logger = logging.getLogger()
111
110
        return interface_index
112
111
 
113
112
 
114
 
def initlogger(level=logging.WARNING):
 
113
def initlogger(debug, level=logging.WARNING):
115
114
    """init logger and add loglevel"""
116
115
    
117
116
    syslogger.setFormatter(logging.Formatter
119
118
                            ' %(message)s'))
120
119
    logger.addHandler(syslogger)
121
120
    
122
 
    console = logging.StreamHandler()
123
 
    console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
124
 
                                           ' [%(process)d]:'
125
 
                                           ' %(levelname)s:'
126
 
                                           ' %(message)s'))
127
 
    logger.addHandler(console)
 
121
    if debug:
 
122
        console = logging.StreamHandler()
 
123
        console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
 
124
                                               ' [%(process)d]:'
 
125
                                               ' %(levelname)s:'
 
126
                                               ' %(message)s'))
 
127
        logger.addHandler(console)
128
128
    logger.setLevel(level)
129
129
 
130
130
 
142
142
        self.gnupg.options.meta_interactive = False
143
143
        self.gnupg.options.homedir = self.tempdir
144
144
        self.gnupg.options.extra_args.extend(['--force-mdc',
145
 
                                              '--quiet'])
 
145
                                              '--quiet',
 
146
                                              '--no-use-agent'])
146
147
    
147
148
    def __enter__(self):
148
149
        return self
412
413
    interval:   datetime.timedelta(); How often to start a new checker
413
414
    last_approval_request: datetime.datetime(); (UTC) or None
414
415
    last_checked_ok: datetime.datetime(); (UTC) or None
415
 
 
416
416
    last_checker_status: integer between 0 and 255 reflecting exit
417
417
                         status of last checker. -1 reflects crashed
418
 
                         checker, or None.
 
418
                         checker, -2 means no checker completed yet.
419
419
    last_enabled: datetime.datetime(); (UTC) or None
420
420
    name:       string; from the config file, used in log messages and
421
421
                        D-Bus identifiers
422
422
    secret:     bytestring; sent verbatim (over TLS) to client
423
423
    timeout:    datetime.timedelta(); How long from last_checked_ok
424
424
                                      until this client is disabled
425
 
    extended_timeout:   extra long timeout when password has been sent
 
425
    extended_timeout:   extra long timeout when secret has been sent
426
426
    runtime_expansions: Allowed attributes for runtime expansion.
427
427
    expires:    datetime.datetime(); time (UTC) when a client will be
428
428
                disabled, or None
432
432
                          "created", "enabled", "fingerprint",
433
433
                          "host", "interval", "last_checked_ok",
434
434
                          "last_enabled", "name", "timeout")
 
435
    client_defaults = { "timeout": "5m",
 
436
                        "extended_timeout": "15m",
 
437
                        "interval": "2m",
 
438
                        "checker": "fping -q -- %%(host)s",
 
439
                        "host": "",
 
440
                        "approval_delay": "0s",
 
441
                        "approval_duration": "1s",
 
442
                        "approved_by_default": "True",
 
443
                        "enabled": "True",
 
444
                        }
435
445
    
436
446
    def timeout_milliseconds(self):
437
447
        "Return the 'timeout' attribute in milliseconds"
447
457
    
448
458
    def approval_delay_milliseconds(self):
449
459
        return timedelta_to_milliseconds(self.approval_delay)
450
 
    
451
 
    def __init__(self, name = None, config=None):
 
460
 
 
461
    @staticmethod
 
462
    def config_parser(config):
 
463
        """Construct a new dict of client settings of this form:
 
464
        { client_name: {setting_name: value, ...}, ...}
 
465
        with exceptions for any special settings as defined above.
 
466
        NOTE: Must be a pure function. Must return the same result
 
467
        value given the same arguments.
 
468
        """
 
469
        settings = {}
 
470
        for client_name in config.sections():
 
471
            section = dict(config.items(client_name))
 
472
            client = settings[client_name] = {}
 
473
            
 
474
            client["host"] = section["host"]
 
475
            # Reformat values from string types to Python types
 
476
            client["approved_by_default"] = config.getboolean(
 
477
                client_name, "approved_by_default")
 
478
            client["enabled"] = config.getboolean(client_name,
 
479
                                                  "enabled")
 
480
            
 
481
            client["fingerprint"] = (section["fingerprint"].upper()
 
482
                                     .replace(" ", ""))
 
483
            if "secret" in section:
 
484
                client["secret"] = section["secret"].decode("base64")
 
485
            elif "secfile" in section:
 
486
                with open(os.path.expanduser(os.path.expandvars
 
487
                                             (section["secfile"])),
 
488
                          "rb") as secfile:
 
489
                    client["secret"] = secfile.read()
 
490
            else:
 
491
                raise TypeError("No secret or secfile for section %s"
 
492
                                % section)
 
493
            client["timeout"] = string_to_delta(section["timeout"])
 
494
            client["extended_timeout"] = string_to_delta(
 
495
                section["extended_timeout"])
 
496
            client["interval"] = string_to_delta(section["interval"])
 
497
            client["approval_delay"] = string_to_delta(
 
498
                section["approval_delay"])
 
499
            client["approval_duration"] = string_to_delta(
 
500
                section["approval_duration"])
 
501
            client["checker_command"] = section["checker"]
 
502
            client["last_approval_request"] = None
 
503
            client["last_checked_ok"] = None
 
504
            client["last_checker_status"] = -2
 
505
        
 
506
        return settings
 
507
        
 
508
        
 
509
    def __init__(self, settings, name = None):
452
510
        """Note: the 'checker' key in 'config' sets the
453
511
        'checker_command' attribute and *not* the 'checker'
454
512
        attribute."""
455
513
        self.name = name
456
 
        if config is None:
457
 
            config = {}
 
514
        # adding all client settings
 
515
        for setting, value in settings.iteritems():
 
516
            setattr(self, setting, value)
 
517
        
 
518
        if self.enabled:
 
519
            if not hasattr(self, "last_enabled"):
 
520
                self.last_enabled = datetime.datetime.utcnow()
 
521
            if not hasattr(self, "expires"):
 
522
                self.expires = (datetime.datetime.utcnow()
 
523
                                + self.timeout)
 
524
        else:
 
525
            self.last_enabled = None
 
526
            self.expires = None
 
527
       
458
528
        logger.debug("Creating client %r", self.name)
459
529
        # Uppercase and remove spaces from fingerprint for later
460
530
        # comparison purposes with return value from the fingerprint()
461
531
        # function
462
 
        self.fingerprint = (config["fingerprint"].upper()
463
 
                            .replace(" ", ""))
464
532
        logger.debug("  Fingerprint: %s", self.fingerprint)
465
 
        if "secret" in config:
466
 
            self.secret = config["secret"].decode("base64")
467
 
        elif "secfile" in config:
468
 
            with open(os.path.expanduser(os.path.expandvars
469
 
                                         (config["secfile"])),
470
 
                      "rb") as secfile:
471
 
                self.secret = secfile.read()
472
 
        else:
473
 
            raise TypeError("No secret or secfile for client %s"
474
 
                            % self.name)
475
 
        self.host = config.get("host", "")
476
 
        self.created = datetime.datetime.utcnow()
477
 
        self.enabled = config.get("enabled", True)
478
 
        self.last_approval_request = None
479
 
        if self.enabled:
480
 
            self.last_enabled = datetime.datetime.utcnow()
481
 
        else:
482
 
            self.last_enabled = None
483
 
        self.last_checked_ok = None
484
 
        self.last_checker_status = None
485
 
        self.timeout = string_to_delta(config["timeout"])
486
 
        self.extended_timeout = string_to_delta(config
487
 
                                                ["extended_timeout"])
488
 
        self.interval = string_to_delta(config["interval"])
 
533
        self.created = settings.get("created",
 
534
                                    datetime.datetime.utcnow())
 
535
 
 
536
        # attributes specific for this server instance
489
537
        self.checker = None
490
538
        self.checker_initiator_tag = None
491
539
        self.disable_initiator_tag = None
492
 
        if self.enabled:
493
 
            self.expires = datetime.datetime.utcnow() + self.timeout
494
 
        else:
495
 
            self.expires = None
496
540
        self.checker_callback_tag = None
497
 
        self.checker_command = config["checker"]
498
541
        self.current_checker_command = None
499
542
        self.approved = None
500
 
        self.approved_by_default = config.get("approved_by_default",
501
 
                                              True)
502
543
        self.approvals_pending = 0
503
 
        self.approval_delay = string_to_delta(
504
 
            config["approval_delay"])
505
 
        self.approval_duration = string_to_delta(
506
 
            config["approval_duration"])
507
544
        self.changedstate = (multiprocessing_manager
508
545
                             .Condition(multiprocessing_manager
509
546
                                        .Lock()))
589
626
            logger.warning("Checker for %(name)s crashed?",
590
627
                           vars(self))
591
628
    
592
 
    def checked_ok(self, timeout=None):
593
 
        """Bump up the timeout for this client.
594
 
        
595
 
        This should only be called when the client has been seen,
596
 
        alive and well.
597
 
        """
 
629
    def checked_ok(self):
 
630
        """Assert that the client has been seen, alive and well."""
 
631
        self.last_checked_ok = datetime.datetime.utcnow()
 
632
        self.last_checker_status = 0
 
633
        self.bump_timeout()
 
634
    
 
635
    def bump_timeout(self, timeout=None):
 
636
        """Bump up the timeout for this client."""
598
637
        if timeout is None:
599
638
            timeout = self.timeout
600
 
        self.last_checked_ok = datetime.datetime.utcnow()
601
639
        if self.disable_initiator_tag is not None:
602
640
            gobject.source_remove(self.disable_initiator_tag)
603
641
        if getattr(self, "enabled", False):
813
851
            # signatures other than "ay".
814
852
            if prop._dbus_signature != "ay":
815
853
                raise ValueError
816
 
            value = dbus.ByteArray(''.join(unichr(byte)
817
 
                                           for byte in value))
 
854
            value = dbus.ByteArray(b''.join(chr(byte)
 
855
                                            for byte in value))
818
856
        prop(value)
819
857
    
820
858
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
1012
1050
    def __init__(self, bus = None, *args, **kwargs):
1013
1051
        self.bus = bus
1014
1052
        Client.__init__(self, *args, **kwargs)
1015
 
        
1016
 
        self._approvals_pending = 0
1017
1053
        # Only now, when this client is initialized, can it show up on
1018
1054
        # the D-Bus
1019
1055
        client_object_name = unicode(self.name).translate(
1065
1101
                                       checker is not None)
1066
1102
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
1067
1103
                                           "LastCheckedOK")
 
1104
    last_checker_status = notifychangeproperty(dbus.Int16,
 
1105
                                               "LastCheckerStatus")
1068
1106
    last_approval_request = notifychangeproperty(
1069
1107
        datetime_to_dbus, "LastApprovalRequest")
1070
1108
    approved_by_default = notifychangeproperty(dbus.Boolean,
1189
1227
        "D-Bus signal"
1190
1228
        return self.need_approval()
1191
1229
    
1192
 
    # NeRwequest - signal
1193
 
    @dbus.service.signal(_interface, signature="s")
1194
 
    def NewRequest(self, ip):
1195
 
        """D-Bus signal
1196
 
        Is sent after a client request a password.
1197
 
        """
1198
 
        pass
1199
 
    
1200
1230
    ## Methods
1201
1231
    
1202
1232
    # Approve - method
1312
1342
            return
1313
1343
        return datetime_to_dbus(self.last_checked_ok)
1314
1344
    
 
1345
    # LastCheckerStatus - property
 
1346
    @dbus_service_property(_interface, signature="n",
 
1347
                           access="read")
 
1348
    def LastCheckerStatus_dbus_property(self):
 
1349
        return dbus.Int16(self.last_checker_status)
 
1350
    
1315
1351
    # Expires - property
1316
1352
    @dbus_service_property(_interface, signature="s", access="read")
1317
1353
    def Expires_dbus_property(self):
1329
1365
        if value is None:       # get
1330
1366
            return dbus.UInt64(self.timeout_milliseconds())
1331
1367
        self.timeout = datetime.timedelta(0, 0, 0, value)
1332
 
        if getattr(self, "disable_initiator_tag", None) is None:
1333
 
            return
1334
1368
        # Reschedule timeout
1335
 
        gobject.source_remove(self.disable_initiator_tag)
1336
 
        self.disable_initiator_tag = None
1337
 
        self.expires = None
1338
 
        time_to_die = timedelta_to_milliseconds((self
1339
 
                                                 .last_checked_ok
1340
 
                                                 + self.timeout)
1341
 
                                                - datetime.datetime
1342
 
                                                .utcnow())
1343
 
        if time_to_die <= 0:
1344
 
            # The timeout has passed
1345
 
            self.disable()
1346
 
        else:
1347
 
            self.expires = (datetime.datetime.utcnow()
1348
 
                            + datetime.timedelta(milliseconds =
1349
 
                                                 time_to_die))
1350
 
            self.disable_initiator_tag = (gobject.timeout_add
1351
 
                                          (time_to_die, self.disable))
 
1369
        if self.enabled:
 
1370
            now = datetime.datetime.utcnow()
 
1371
            time_to_die = timedelta_to_milliseconds(
 
1372
                (self.last_checked_ok + self.timeout) - now)
 
1373
            if time_to_die <= 0:
 
1374
                # The timeout has passed
 
1375
                self.disable()
 
1376
            else:
 
1377
                self.expires = (now +
 
1378
                                datetime.timedelta(milliseconds =
 
1379
                                                   time_to_die))
 
1380
                if (getattr(self, "disable_initiator_tag", None)
 
1381
                    is None):
 
1382
                    return
 
1383
                gobject.source_remove(self.disable_initiator_tag)
 
1384
                self.disable_initiator_tag = (gobject.timeout_add
 
1385
                                              (time_to_die,
 
1386
                                               self.disable))
1352
1387
    
1353
1388
    # ExtendedTimeout - property
1354
1389
    @dbus_service_property(_interface, signature="t",
1510
1545
                except KeyError:
1511
1546
                    return
1512
1547
                
1513
 
                if self.server.use_dbus:
1514
 
                    # Emit D-Bus signal
1515
 
                    client.NewRequest(str(self.client_address))
1516
 
                
1517
1548
                if client.approval_delay:
1518
1549
                    delay = client.approval_delay
1519
1550
                    client.approvals_pending += 1
1583
1614
                
1584
1615
                logger.info("Sending secret to %s", client.name)
1585
1616
                # bump the timeout using extended_timeout
1586
 
                client.checked_ok(client.extended_timeout)
 
1617
                client.bump_timeout(client.extended_timeout)
1587
1618
                if self.server.use_dbus:
1588
1619
                    # Emit D-Bus signal
1589
1620
                    client.GotSecret()
1665
1696
    def sub_process_main(self, request, address):
1666
1697
        try:
1667
1698
            self.finish_request(request, address)
1668
 
        except:
 
1699
        except Exception:
1669
1700
            self.handle_error(request, address)
1670
1701
        self.close_request(request)
1671
1702
    
2050
2081
                                     stored_state_file)
2051
2082
    
2052
2083
    if debug:
2053
 
        initlogger(logging.DEBUG)
 
2084
        initlogger(debug, logging.DEBUG)
2054
2085
    else:
2055
2086
        if not debuglevel:
2056
 
            initlogger()
 
2087
            initlogger(debug)
2057
2088
        else:
2058
2089
            level = getattr(logging, debuglevel.upper())
2059
 
            initlogger(level)
 
2090
            initlogger(debug, level)
2060
2091
    
2061
2092
    if server_settings["servicename"] != "Mandos":
2062
2093
        syslogger.setFormatter(logging.Formatter
2065
2096
                                % server_settings["servicename"]))
2066
2097
    
2067
2098
    # Parse config file with clients
2068
 
    client_defaults = { "timeout": "5m",
2069
 
                        "extended_timeout": "15m",
2070
 
                        "interval": "2m",
2071
 
                        "checker": "fping -q -- %%(host)s",
2072
 
                        "host": "",
2073
 
                        "approval_delay": "0s",
2074
 
                        "approval_duration": "1s",
2075
 
                        }
2076
 
    client_config = configparser.SafeConfigParser(client_defaults)
 
2099
    client_config = configparser.SafeConfigParser(Client
 
2100
                                                  .client_defaults)
2077
2101
    client_config.read(os.path.join(server_settings["configdir"],
2078
2102
                                    "clients.conf"))
2079
2103
    
2136
2160
        os.dup2(null, sys.stdin.fileno())
2137
2161
        if null > 2:
2138
2162
            os.close(null)
2139
 
    else:
2140
 
        # No console logging
2141
 
        logger.removeHandler(console)
2142
2163
    
2143
2164
    # Need to fork before connecting to D-Bus
2144
2165
    if not debug:
2182
2203
        client_class = functools.partial(ClientDBusTransitional,
2183
2204
                                         bus = bus)
2184
2205
    
2185
 
    special_settings = {
2186
 
        # Some settings need to be accessd by special methods;
2187
 
        # booleans need .getboolean(), etc.  Here is a list of them:
2188
 
        "approved_by_default":
2189
 
            lambda section:
2190
 
            client_config.getboolean(section, "approved_by_default"),
2191
 
        "enabled":
2192
 
            lambda section:
2193
 
            client_config.getboolean(section, "enabled"),
2194
 
        }
2195
 
    # Construct a new dict of client settings of this form:
2196
 
    # { client_name: {setting_name: value, ...}, ...}
2197
 
    # with exceptions for any special settings as defined above
2198
 
    client_settings = dict((clientname,
2199
 
                           dict((setting,
2200
 
                                 (value
2201
 
                                  if setting not in special_settings
2202
 
                                  else special_settings[setting]
2203
 
                                  (clientname)))
2204
 
                                for setting, value in
2205
 
                                client_config.items(clientname)))
2206
 
                          for clientname in client_config.sections())
2207
 
    
 
2206
    client_settings = Client.config_parser(client_config)
2208
2207
    old_client_settings = {}
2209
 
    clients_data = []
 
2208
    clients_data = {}
2210
2209
    
2211
2210
    # Get client data and settings from last running state.
2212
2211
    if server_settings["restore"]:
2220
2219
                           .format(e))
2221
2220
            if e.errno != errno.ENOENT:
2222
2221
                raise
 
2222
        except EOFError as e:
 
2223
            logger.warning("Could not load persistent state: "
 
2224
                           "EOFError: {0}".format(e))
2223
2225
    
2224
2226
    with PGPEngine() as pgp:
2225
 
        for client in clients_data:
2226
 
            client_name = client["name"]
2227
 
            
 
2227
        for client_name, client in clients_data.iteritems():
2228
2228
            # Decide which value to use after restoring saved state.
2229
2229
            # We have three different values: Old config file,
2230
2230
            # new config file, and saved state.
2243
2243
                    pass
2244
2244
            
2245
2245
            # Clients who has passed its expire date can still be
2246
 
            # enabled if its last checker was sucessful.  Clients
2247
 
            # whose checker failed before we stored its state is
2248
 
            # assumed to have failed all checkers during downtime.
 
2246
            # enabled if its last checker was successful.  Clients
 
2247
            # whose checker succeeded before we stored its state is
 
2248
            # assumed to have successfully run all checkers during
 
2249
            # downtime.
2249
2250
            if client["enabled"]:
2250
 
                if client["expires"] <= (datetime.datetime
2251
 
                                         .utcnow()):
2252
 
                    # Client has expired
2253
 
                    if client["last_checker_status"] != 0:
 
2251
                if datetime.datetime.utcnow() >= client["expires"]:
 
2252
                    if not client["last_checked_ok"]:
 
2253
                        logger.warning(
 
2254
                            "disabling client {0} - Client never "
 
2255
                            "performed a successful checker"
 
2256
                            .format(client_name))
 
2257
                        client["enabled"] = False
 
2258
                    elif client["last_checker_status"] != 0:
 
2259
                        logger.warning(
 
2260
                            "disabling client {0} - Client "
 
2261
                            "last checker failed with error code {1}"
 
2262
                            .format(client_name,
 
2263
                                    client["last_checker_status"]))
2254
2264
                        client["enabled"] = False
2255
2265
                    else:
2256
2266
                        client["expires"] = (datetime.datetime
2257
2267
                                             .utcnow()
2258
2268
                                             + client["timeout"])
2259
 
            
2260
 
            client["changedstate"] = (multiprocessing_manager
2261
 
                                      .Condition
2262
 
                                      (multiprocessing_manager
2263
 
                                       .Lock()))
2264
 
            if use_dbus:
2265
 
                new_client = (ClientDBusTransitional.__new__
2266
 
                              (ClientDBusTransitional))
2267
 
                tcp_server.clients[client_name] = new_client
2268
 
                new_client.bus = bus
2269
 
                for name, value in client.iteritems():
2270
 
                    setattr(new_client, name, value)
2271
 
                client_object_name = unicode(client_name).translate(
2272
 
                    {ord("."): ord("_"),
2273
 
                     ord("-"): ord("_")})
2274
 
                new_client.dbus_object_path = (dbus.ObjectPath
2275
 
                                               ("/clients/"
2276
 
                                                + client_object_name))
2277
 
                DBusObjectWithProperties.__init__(new_client,
2278
 
                                                  new_client.bus,
2279
 
                                                  new_client
2280
 
                                                  .dbus_object_path)
2281
 
            else:
2282
 
                tcp_server.clients[client_name] = (Client.__new__
2283
 
                                                   (Client))
2284
 
                for name, value in client.iteritems():
2285
 
                    setattr(tcp_server.clients[client_name],
2286
 
                            name, value)
2287
 
            
 
2269
                        logger.debug("Last checker succeeded,"
 
2270
                                     " keeping {0} enabled"
 
2271
                                     .format(client_name))
2288
2272
            try:
2289
 
                tcp_server.clients[client_name].secret = (
2290
 
                    pgp.decrypt(tcp_server.clients[client_name]
2291
 
                                .encrypted_secret,
 
2273
                client["secret"] = (
 
2274
                    pgp.decrypt(client["encrypted_secret"],
2292
2275
                                client_settings[client_name]
2293
2276
                                ["secret"]))
2294
2277
            except PGPError:
2295
2278
                # If decryption fails, we use secret from new settings
2296
2279
                logger.debug("Failed to decrypt {0} old secret"
2297
2280
                             .format(client_name))
2298
 
                tcp_server.clients[client_name].secret = (
 
2281
                client["secret"] = (
2299
2282
                    client_settings[client_name]["secret"])
 
2283
 
2300
2284
    
2301
 
    # Create/remove clients based on new changes made to config
2302
 
    for clientname in set(old_client_settings) - set(client_settings):
2303
 
        del tcp_server.clients[clientname]
2304
 
    for clientname in set(client_settings) - set(old_client_settings):
2305
 
        tcp_server.clients[clientname] = (client_class(name
2306
 
                                                       = clientname,
2307
 
                                                       config =
2308
 
                                                       client_settings
2309
 
                                                       [clientname]))
 
2285
    # Add/remove clients based on new changes made to config
 
2286
    for client_name in (set(old_client_settings)
 
2287
                        - set(client_settings)):
 
2288
        del clients_data[client_name]
 
2289
    for client_name in (set(client_settings)
 
2290
                        - set(old_client_settings)):
 
2291
        clients_data[client_name] = client_settings[client_name]
 
2292
 
 
2293
    # Create all client objects
 
2294
    for client_name, client in clients_data.iteritems():
 
2295
        tcp_server.clients[client_name] = client_class(
 
2296
            name = client_name, settings = client)
2310
2297
    
2311
2298
    if not tcp_server.clients:
2312
2299
        logger.warning("No clients defined")
2324
2311
            # "pidfile" was never created
2325
2312
            pass
2326
2313
        del pidfilename
2327
 
        
2328
2314
        signal.signal(signal.SIGINT, signal.SIG_IGN)
2329
2315
    
2330
2316
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2399
2385
        # Store client before exiting. Secrets are encrypted with key
2400
2386
        # based on what config file has. If config file is
2401
2387
        # removed/edited, old secret will thus be unrecovable.
2402
 
        clients = []
 
2388
        clients = {}
2403
2389
        with PGPEngine() as pgp:
2404
2390
            for client in tcp_server.clients.itervalues():
2405
2391
                key = client_settings[client.name]["secret"]
2407
2393
                                                      key)
2408
2394
                client_dict = {}
2409
2395
                
2410
 
                # A list of attributes that will not be stored when
2411
 
                # shutting down.
2412
 
                exclude = set(("bus", "changedstate", "secret"))
 
2396
                # A list of attributes that can not be pickled
 
2397
                # + secret.
 
2398
                exclude = set(("bus", "changedstate", "secret",
 
2399
                               "checker"))
2413
2400
                for name, typ in (inspect.getmembers
2414
2401
                                  (dbus.service.Object)):
2415
2402
                    exclude.add(name)
2420
2407
                    if attr not in exclude:
2421
2408
                        client_dict[attr] = getattr(client, attr)
2422
2409
                
2423
 
                clients.append(client_dict)
 
2410
                clients[client.name] = client_dict
2424
2411
                del client_settings[client.name]["secret"]
2425
2412
        
2426
2413
        try:
2427
 
            with os.fdopen(os.open(stored_state_path,
2428
 
                                   os.O_CREAT|os.O_WRONLY|os.O_TRUNC,
2429
 
                                   0600), "wb") as stored_state:
 
2414
            tempfd, tempname = tempfile.mkstemp(suffix=".pickle",
 
2415
                                                prefix="clients-",
 
2416
                                                dir=os.path.dirname
 
2417
                                                (stored_state_path))
 
2418
            with os.fdopen(tempfd, "wb") as stored_state:
2430
2419
                pickle.dump((clients, client_settings), stored_state)
 
2420
            os.rename(tempname, stored_state_path)
2431
2421
        except (IOError, OSError) as e:
2432
2422
            logger.warning("Could not save persistent state: {0}"
2433
2423
                           .format(e))
2434
 
            if e.errno not in (errno.ENOENT, errno.EACCES):
2435
 
                raise
 
2424
            if not debug:
 
2425
                try:
 
2426
                    os.remove(tempname)
 
2427
                except NameError:
 
2428
                    pass
 
2429
            if e.errno not in set((errno.ENOENT, errno.EACCES,
 
2430
                                   errno.EEXIST)):
 
2431
                raise e
2436
2432
        
2437
2433
        # Delete all clients, and settings from config
2438
2434
        while tcp_server.clients:
2502
2498
    # Must run before the D-Bus bus name gets deregistered
2503
2499
    cleanup()
2504
2500
 
2505
 
 
2506
2501
if __name__ == '__main__':
2507
2502
    main()