/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-11-27 19:37:54 UTC
  • mto: (518.1.11 mandos-persistent)
  • mto: This revision was merged to the branch mainline in revision 524.
  • Revision ID: teddy@recompile.se-20111127193754-4366e18gmi11kew0
* mandos (_timedelta_to_milliseconds): Renamed to
                                       "timedelta_to_milliseconds";
                                       all callers changed.
  (Client._approved): Bug fix; renamed to "approved" without
                      underscore.  All users changed.
  (ProxyClient): Removed superfluous parens in "if" statements.
  (ClientHandler.handle): Bug fix: Emit NewRequest signal *after*
                          client is found, not before.

Show diffs side-by-side

added added

removed removed

Lines of Context:
85
85
    except ImportError:
86
86
        SO_BINDTODEVICE = None
87
87
 
 
88
 
88
89
version = "1.4.1"
89
90
stored_state_file = "clients.pickle"
90
91
 
127
128
    logger.setLevel(level)
128
129
 
129
130
 
130
 
class PGPError(Exception):
131
 
    """Exception if encryption/decryption fails"""
 
131
class CryptoError(Exception):
132
132
    pass
133
133
 
134
134
 
135
 
class PGPEngine(object):
 
135
class Crypto(object):
136
136
    """A simple class for OpenPGP symmetric encryption & decryption"""
137
137
    def __init__(self):
138
138
        self.gnupg = GnuPGInterface.GnuPG()
184
184
                    ciphertext = f.read()
185
185
                proc.wait()
186
186
            except IOError as e:
187
 
                raise PGPError(e)
 
187
                raise CryptoError(e)
188
188
        self.gnupg.passphrase = None
189
189
        return ciphertext
190
190
    
201
201
                    decrypted_plaintext = f.read()
202
202
                proc.wait()
203
203
            except IOError as e:
204
 
                raise PGPError(e)
 
204
                raise CryptoError(e)
205
205
        self.gnupg.passphrase = None
206
206
        return decrypted_plaintext
207
207
 
431
431
                          "created", "enabled", "fingerprint",
432
432
                          "host", "interval", "last_checked_ok",
433
433
                          "last_enabled", "name", "timeout")
434
 
    client_defaults = { "timeout": "5m",
435
 
                        "extended_timeout": "15m",
436
 
                        "interval": "2m",
437
 
                        "checker": "fping -q -- %%(host)s",
438
 
                        "host": "",
439
 
                        "approval_delay": "0s",
440
 
                        "approval_duration": "1s",
441
 
                        "approved_by_default": "True",
442
 
                        "enabled": "True",
443
 
                        }
444
434
    
445
435
    def timeout_milliseconds(self):
446
436
        "Return the 'timeout' attribute in milliseconds"
456
446
    
457
447
    def approval_delay_milliseconds(self):
458
448
        return timedelta_to_milliseconds(self.approval_delay)
459
 
 
460
 
    @staticmethod
461
 
    def config_parser(config):
462
 
        """ Construct a new dict of client settings of this form:
463
 
        { client_name: {setting_name: value, ...}, ...}
464
 
        with exceptions for any special settings as defined above"""
465
 
        settings = {}
466
 
        for client_name in config.sections():
467
 
            section = dict(config.items(client_name))
468
 
            client = settings[client_name] = {}
469
 
            
470
 
            client["host"] = section["host"]
471
 
            # Reformat values from string types to Python types
472
 
            client["approved_by_default"] = config.getboolean(
473
 
                client_name, "approved_by_default")
474
 
            client["enabled"] = config.getboolean(client_name, "enabled")
475
 
            
476
 
            client["fingerprint"] = (section["fingerprint"].upper()
477
 
                                     .replace(" ", ""))
478
 
            if "secret" in section:
479
 
                client["secret"] = section["secret"].decode("base64")
480
 
            elif "secfile" in section:
481
 
                with open(os.path.expanduser(os.path.expandvars
482
 
                                             (section["secfile"])),
483
 
                          "rb") as secfile:
484
 
                    client["secret"] = secfile.read()
485
 
            else:
486
 
                raise TypeError("No secret or secfile for section %s"
487
 
                                % section)
488
 
            client["timeout"] = string_to_delta(section["timeout"])
489
 
            client["extended_timeout"] = string_to_delta(
490
 
                section["extended_timeout"])
491
 
            client["interval"] = string_to_delta(section["interval"])
492
 
            client["approval_delay"] = string_to_delta(
493
 
                section["approval_delay"])
494
 
            client["approval_duration"] = string_to_delta(
495
 
                section["approval_duration"])
496
 
            client["checker_command"] = section["checker"]
497
 
            client["last_approval_request"] = None
498
 
            client["last_checked_ok"] = None
499
 
            client["last_checker_status"] = None
500
 
            if client["enabled"]:
501
 
                client["last_enabled"] = datetime.datetime.utcnow()
502
 
                client["expires"] = (datetime.datetime.utcnow()
503
 
                                     + client["timeout"])
504
 
            else:
505
 
                client["last_enabled"] = None
506
 
                client["expires"] = None
507
 
 
508
 
        return settings
509
 
        
510
 
        
511
 
    def __init__(self, settings, name = None):
 
449
    
 
450
    def __init__(self, name = None, config=None):
512
451
        """Note: the 'checker' key in 'config' sets the
513
452
        'checker_command' attribute and *not* the 'checker'
514
453
        attribute."""
515
454
        self.name = name
516
 
        # adding all client settings
517
 
        for setting, value in settings.iteritems():
518
 
            setattr(self, setting, value)
519
 
        
 
455
        if config is None:
 
456
            config = {}
520
457
        logger.debug("Creating client %r", self.name)
521
458
        # Uppercase and remove spaces from fingerprint for later
522
459
        # comparison purposes with return value from the fingerprint()
523
460
        # function
 
461
        self.fingerprint = (config["fingerprint"].upper()
 
462
                            .replace(" ", ""))
524
463
        logger.debug("  Fingerprint: %s", self.fingerprint)
525
 
        self.created = settings.get("created", datetime.datetime.utcnow())
526
 
 
527
 
        # attributes specific for this server instance
 
464
        if "secret" in config:
 
465
            self.secret = config["secret"].decode("base64")
 
466
        elif "secfile" in config:
 
467
            with open(os.path.expanduser(os.path.expandvars
 
468
                                         (config["secfile"])),
 
469
                      "rb") as secfile:
 
470
                self.secret = secfile.read()
 
471
        else:
 
472
            raise TypeError("No secret or secfile for client %s"
 
473
                            % self.name)
 
474
        self.host = config.get("host", "")
 
475
        self.created = datetime.datetime.utcnow()
 
476
        self.enabled = config.get("enabled", True)
 
477
        self.last_approval_request = None
 
478
        if self.enabled:
 
479
            self.last_enabled = datetime.datetime.utcnow()
 
480
        else:
 
481
            self.last_enabled = None
 
482
        self.last_checked_ok = None
 
483
        self.last_checker_status = None
 
484
        self.timeout = string_to_delta(config["timeout"])
 
485
        self.extended_timeout = string_to_delta(config
 
486
                                                ["extended_timeout"])
 
487
        self.interval = string_to_delta(config["interval"])
528
488
        self.checker = None
529
489
        self.checker_initiator_tag = None
530
490
        self.disable_initiator_tag = None
 
491
        if self.enabled:
 
492
            self.expires = datetime.datetime.utcnow() + self.timeout
 
493
        else:
 
494
            self.expires = None
531
495
        self.checker_callback_tag = None
 
496
        self.checker_command = config["checker"]
532
497
        self.current_checker_command = None
533
498
        self.approved = None
 
499
        self.approved_by_default = config.get("approved_by_default",
 
500
                                              True)
534
501
        self.approvals_pending = 0
 
502
        self.approval_delay = string_to_delta(
 
503
            config["approval_delay"])
 
504
        self.approval_duration = string_to_delta(
 
505
            config["approval_duration"])
535
506
        self.changedstate = (multiprocessing_manager
536
507
                             .Condition(multiprocessing_manager
537
508
                                        .Lock()))
604
575
        self.checker_callback_tag = None
605
576
        self.checker = None
606
577
        if os.WIFEXITED(condition):
607
 
            self.last_checker_status = os.WEXITSTATUS(condition)
 
578
            self.last_checker_status =  os.WEXITSTATUS(condition)
608
579
            if self.last_checker_status == 0:
609
580
                logger.info("Checker for %(name)s succeeded",
610
581
                            vars(self))
1097
1068
        datetime_to_dbus, "LastApprovalRequest")
1098
1069
    approved_by_default = notifychangeproperty(dbus.Boolean,
1099
1070
                                               "ApprovedByDefault")
1100
 
    approval_delay = notifychangeproperty(dbus.UInt64,
 
1071
    approval_delay = notifychangeproperty(dbus.UInt16,
1101
1072
                                          "ApprovalDelay",
1102
1073
                                          type_func =
1103
1074
                                          timedelta_to_milliseconds)
1104
1075
    approval_duration = notifychangeproperty(
1105
 
        dbus.UInt64, "ApprovalDuration",
 
1076
        dbus.UInt16, "ApprovalDuration",
1106
1077
        type_func = timedelta_to_milliseconds)
1107
1078
    host = notifychangeproperty(dbus.String, "Host")
1108
 
    timeout = notifychangeproperty(dbus.UInt64, "Timeout",
 
1079
    timeout = notifychangeproperty(dbus.UInt16, "Timeout",
1109
1080
                                   type_func =
1110
1081
                                   timedelta_to_milliseconds)
1111
1082
    extended_timeout = notifychangeproperty(
1112
 
        dbus.UInt64, "ExtendedTimeout",
 
1083
        dbus.UInt16, "ExtendedTimeout",
1113
1084
        type_func = timedelta_to_milliseconds)
1114
 
    interval = notifychangeproperty(dbus.UInt64,
 
1085
    interval = notifychangeproperty(dbus.UInt16,
1115
1086
                                    "Interval",
1116
1087
                                    type_func =
1117
1088
                                    timedelta_to_milliseconds)
1308
1279
    def Host_dbus_property(self, value=None):
1309
1280
        if value is None:       # get
1310
1281
            return dbus.String(self.host)
1311
 
        self.host = unicode(value)
 
1282
        self.host = value
1312
1283
    
1313
1284
    # Created - property
1314
1285
    @dbus_service_property(_interface, signature="s", access="read")
1408
1379
    def Checker_dbus_property(self, value=None):
1409
1380
        if value is None:       # get
1410
1381
            return dbus.String(self.checker_command)
1411
 
        self.checker_command = unicode(value)
 
1382
        self.checker_command = value
1412
1383
    
1413
1384
    # CheckerRunning - property
1414
1385
    @dbus_service_property(_interface, signature="b",
1693
1664
    def sub_process_main(self, request, address):
1694
1665
        try:
1695
1666
            self.finish_request(request, address)
1696
 
        except Exception:
 
1667
        except:
1697
1668
            self.handle_error(request, address)
1698
1669
        self.close_request(request)
1699
1670
    
2093
2064
                                % server_settings["servicename"]))
2094
2065
    
2095
2066
    # Parse config file with clients
2096
 
    client_config = configparser.SafeConfigParser(Client.client_defaults)
 
2067
    client_defaults = { "timeout": "5m",
 
2068
                        "extended_timeout": "15m",
 
2069
                        "interval": "2m",
 
2070
                        "checker": "fping -q -- %%(host)s",
 
2071
                        "host": "",
 
2072
                        "approval_delay": "0s",
 
2073
                        "approval_duration": "1s",
 
2074
                        }
 
2075
    client_config = configparser.SafeConfigParser(client_defaults)
2097
2076
    client_config.read(os.path.join(server_settings["configdir"],
2098
2077
                                    "clients.conf"))
2099
2078
    
2200
2179
        client_class = functools.partial(ClientDBusTransitional,
2201
2180
                                         bus = bus)
2202
2181
    
2203
 
    client_settings = Client.config_parser(client_config)
 
2182
    special_settings = {
 
2183
        # Some settings need to be accessd by special methods;
 
2184
        # booleans need .getboolean(), etc.  Here is a list of them:
 
2185
        "approved_by_default":
 
2186
            lambda section:
 
2187
            client_config.getboolean(section, "approved_by_default"),
 
2188
        "enabled":
 
2189
            lambda section:
 
2190
            client_config.getboolean(section, "enabled"),
 
2191
        }
 
2192
    # Construct a new dict of client settings of this form:
 
2193
    # { client_name: {setting_name: value, ...}, ...}
 
2194
    # with exceptions for any special settings as defined above
 
2195
    client_settings = dict((clientname,
 
2196
                           dict((setting,
 
2197
                                 (value
 
2198
                                  if setting not in special_settings
 
2199
                                  else special_settings[setting]
 
2200
                                  (clientname)))
 
2201
                                for setting, value in
 
2202
                                client_config.items(clientname)))
 
2203
                          for clientname in client_config.sections())
 
2204
    
2204
2205
    old_client_settings = {}
2205
 
    clients_data = {}
 
2206
    clients_data = []
2206
2207
    
2207
2208
    # Get client data and settings from last running state.
2208
2209
    if server_settings["restore"]:
2217
2218
            if e.errno != errno.ENOENT:
2218
2219
                raise
2219
2220
    
2220
 
    with PGPEngine() as pgp:
2221
 
        for client_name, client in clients_data.iteritems():
 
2221
    with Crypto() as crypt:
 
2222
        for client in clients_data:
 
2223
            client_name = client["name"]
 
2224
            
2222
2225
            # Decide which value to use after restoring saved state.
2223
2226
            # We have three different values: Old config file,
2224
2227
            # new config file, and saved state.
2232
2235
                    if (name != "secret" and
2233
2236
                        value != old_client_settings[client_name]
2234
2237
                        [name]):
2235
 
                        client[name] = value
 
2238
                        setattr(client, name, value)
2236
2239
                except KeyError:
2237
2240
                    pass
2238
2241
            
2239
2242
            # Clients who has passed its expire date can still be
2240
 
            # enabled if its last checker was successful.  Clients
 
2243
            # enabled if its last checker was sucessful.  Clients
2241
2244
            # whose checker failed before we stored its state is
2242
2245
            # assumed to have failed all checkers during downtime.
2243
 
            if client["enabled"]:
2244
 
                if datetime.datetime.utcnow() >= client["expires"]:
2245
 
                    if not client["last_checked_ok"]:
2246
 
                        logger.warning(
2247
 
                            "disabling client {0} - Client never "
2248
 
                            "performed a successfull checker"
2249
 
                            .format(client["name"]))
2250
 
                        client["enabled"] = False
2251
 
                    elif client["last_checker_status"] != 0:
2252
 
                        logger.warning(
2253
 
                            "disabling client {0} - Client "
2254
 
                            "last checker failed with error code {1}"
2255
 
                            .format(client["name"],
2256
 
                                    client["last_checker_status"]))
 
2246
            if client["enabled"] and client["last_checked_ok"]:
 
2247
                if ((datetime.datetime.utcnow()
 
2248
                     - client["last_checked_ok"])
 
2249
                    > client["interval"]):
 
2250
                    if client["last_checker_status"] != 0:
2257
2251
                        client["enabled"] = False
2258
2252
                    else:
2259
2253
                        client["expires"] = (datetime.datetime
2260
2254
                                             .utcnow()
2261
2255
                                             + client["timeout"])
2262
 
                    
 
2256
            
 
2257
            client["changedstate"] = (multiprocessing_manager
 
2258
                                      .Condition
 
2259
                                      (multiprocessing_manager
 
2260
                                       .Lock()))
 
2261
            if use_dbus:
 
2262
                new_client = (ClientDBusTransitional.__new__
 
2263
                              (ClientDBusTransitional))
 
2264
                tcp_server.clients[client_name] = new_client
 
2265
                new_client.bus = bus
 
2266
                for name, value in client.iteritems():
 
2267
                    setattr(new_client, name, value)
 
2268
                client_object_name = unicode(client_name).translate(
 
2269
                    {ord("."): ord("_"),
 
2270
                     ord("-"): ord("_")})
 
2271
                new_client.dbus_object_path = (dbus.ObjectPath
 
2272
                                               ("/clients/"
 
2273
                                                + client_object_name))
 
2274
                DBusObjectWithProperties.__init__(new_client,
 
2275
                                                  new_client.bus,
 
2276
                                                  new_client
 
2277
                                                  .dbus_object_path)
 
2278
            else:
 
2279
                tcp_server.clients[client_name] = (Client.__new__
 
2280
                                                   (Client))
 
2281
                for name, value in client.iteritems():
 
2282
                    setattr(tcp_server.clients[client_name],
 
2283
                            name, value)
 
2284
            
2263
2285
            try:
2264
 
                client["secret"] = (
2265
 
                    pgp.decrypt(client["encrypted_secret"],
2266
 
                                client_settings[client_name]
2267
 
                                ["secret"]))
2268
 
            except PGPError:
 
2286
                tcp_server.clients[client_name].secret = (
 
2287
                    crypt.decrypt(tcp_server.clients[client_name]
 
2288
                                  .encrypted_secret,
 
2289
                                  client_settings[client_name]
 
2290
                                  ["secret"]))
 
2291
            except CryptoError:
2269
2292
                # If decryption fails, we use secret from new settings
2270
 
                logger.debug("Failed to decrypt {0} old secret"
2271
 
                             .format(client_name))
2272
 
                client["secret"] = (
 
2293
                tcp_server.clients[client_name].secret = (
2273
2294
                    client_settings[client_name]["secret"])
2274
 
 
2275
2295
    
2276
 
    # Add/remove clients based on new changes made to config
2277
 
    for client_name in set(old_client_settings) - set(client_settings):
2278
 
        del clients_data[client_name]
2279
 
    for client_name in set(client_settings) - set(old_client_settings):
2280
 
        clients_data[client_name] = client_settings[client_name]
2281
 
 
2282
 
    # Create clients all clients
2283
 
    for client_name, client in clients_data.iteritems():
2284
 
        tcp_server.clients[client_name] = client_class(
2285
 
            name = client_name, settings = client)
 
2296
    # Create/remove clients based on new changes made to config
 
2297
    for clientname in set(old_client_settings) - set(client_settings):
 
2298
        del tcp_server.clients[clientname]
 
2299
    for clientname in set(client_settings) - set(old_client_settings):
 
2300
        tcp_server.clients[clientname] = (client_class(name
 
2301
                                                       = clientname,
 
2302
                                                       config =
 
2303
                                                       client_settings
 
2304
                                                       [clientname]))
2286
2305
    
2287
2306
    if not tcp_server.clients:
2288
2307
        logger.warning("No clients defined")
2300
2319
            # "pidfile" was never created
2301
2320
            pass
2302
2321
        del pidfilename
 
2322
        
2303
2323
        signal.signal(signal.SIGINT, signal.SIG_IGN)
2304
2324
    
2305
2325
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2374
2394
        # Store client before exiting. Secrets are encrypted with key
2375
2395
        # based on what config file has. If config file is
2376
2396
        # removed/edited, old secret will thus be unrecovable.
2377
 
        clients = {}
2378
 
        with PGPEngine() as pgp:
 
2397
        clients = []
 
2398
        with Crypto() as crypt:
2379
2399
            for client in tcp_server.clients.itervalues():
2380
2400
                key = client_settings[client.name]["secret"]
2381
 
                client.encrypted_secret = pgp.encrypt(client.secret,
2382
 
                                                      key)
 
2401
                client.encrypted_secret = crypt.encrypt(client.secret,
 
2402
                                                        key)
2383
2403
                client_dict = {}
2384
2404
                
2385
 
                # A list of attributes that can not be pickled
2386
 
                # + secret.
2387
 
                exclude = set(("bus", "changedstate", "secret",
2388
 
                               "checker"))
 
2405
                # A list of attributes that will not be stored when
 
2406
                # shutting down.
 
2407
                exclude = set(("bus", "changedstate", "secret"))
2389
2408
                for name, typ in (inspect.getmembers
2390
2409
                                  (dbus.service.Object)):
2391
2410
                    exclude.add(name)
2396
2415
                    if attr not in exclude:
2397
2416
                        client_dict[attr] = getattr(client, attr)
2398
2417
                
2399
 
                clients[client.name] = client_dict
 
2418
                clients.append(client_dict)
2400
2419
                del client_settings[client.name]["secret"]
2401
2420
        
2402
2421
        try:
2478
2497
    # Must run before the D-Bus bus name gets deregistered
2479
2498
    cleanup()
2480
2499
 
 
2500
 
2481
2501
if __name__ == '__main__':
2482
2502
    main()