/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-01-15 16:10:09 UTC
  • Revision ID: teddy@recompile.se-20120115161009-i07il979zpc3lb3s
* network-hooks.d/bridge: Bug fixes: Ignore bridge interface when
                          looking up interface name by hw address.
                          Say we need "/sbin/brctl" even if it does
                          not exist.  Sleep only integer seconds.

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
 
version = "1.4.1"
 
88
version = "1.5.2"
89
89
stored_state_file = "clients.pickle"
90
90
 
91
91
logger = logging.getLogger()
110
110
        return interface_index
111
111
 
112
112
 
113
 
def initlogger(level=logging.WARNING):
 
113
def initlogger(debug, level=logging.WARNING):
114
114
    """init logger and add loglevel"""
115
115
    
116
116
    syslogger.setFormatter(logging.Formatter
118
118
                            ' %(message)s'))
119
119
    logger.addHandler(syslogger)
120
120
    
121
 
    console = logging.StreamHandler()
122
 
    console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
123
 
                                           ' [%(process)d]:'
124
 
                                           ' %(levelname)s:'
125
 
                                           ' %(message)s'))
126
 
    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)
127
128
    logger.setLevel(level)
128
129
 
129
130
 
141
142
        self.gnupg.options.meta_interactive = False
142
143
        self.gnupg.options.homedir = self.tempdir
143
144
        self.gnupg.options.extra_args.extend(['--force-mdc',
144
 
                                              '--quiet'])
 
145
                                              '--quiet',
 
146
                                              '--no-use-agent'])
145
147
    
146
148
    def __enter__(self):
147
149
        return self
411
413
    interval:   datetime.timedelta(); How often to start a new checker
412
414
    last_approval_request: datetime.datetime(); (UTC) or None
413
415
    last_checked_ok: datetime.datetime(); (UTC) or None
414
 
 
415
416
    last_checker_status: integer between 0 and 255 reflecting exit
416
417
                         status of last checker. -1 reflects crashed
417
418
                         checker, or None.
421
422
    secret:     bytestring; sent verbatim (over TLS) to client
422
423
    timeout:    datetime.timedelta(); How long from last_checked_ok
423
424
                                      until this client is disabled
424
 
    extended_timeout:   extra long timeout when password has been sent
 
425
    extended_timeout:   extra long timeout when secret has been sent
425
426
    runtime_expansions: Allowed attributes for runtime expansion.
426
427
    expires:    datetime.datetime(); time (UTC) when a client will be
427
428
                disabled, or None
459
460
 
460
461
    @staticmethod
461
462
    def config_parser(config):
462
 
        """ Construct a new dict of client settings of this form:
 
463
        """Construct a new dict of client settings of this form:
463
464
        { client_name: {setting_name: value, ...}, ...}
464
 
        with exceptions for any special settings as defined above"""
 
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
        """
465
469
        settings = {}
466
470
        for client_name in config.sections():
467
471
            section = dict(config.items(client_name))
468
472
            client = settings[client_name] = {}
469
473
            
470
 
            # Default copying each value from config to new dict
471
 
            for setting, value in section.iteritems():
472
 
                client[setting] = value
473
 
            
 
474
            client["host"] = section["host"]
474
475
            # Reformat values from string types to Python types
475
476
            client["approved_by_default"] = config.getboolean(
476
477
                client_name, "approved_by_default")
477
 
            client["enabled"] = config.getboolean(client_name, "enabled")
 
478
            client["enabled"] = config.getboolean(client_name,
 
479
                                                  "enabled")
478
480
            
479
481
            client["fingerprint"] = (section["fingerprint"].upper()
480
482
                                     .replace(" ", ""))
496
498
                section["approval_delay"])
497
499
            client["approval_duration"] = string_to_delta(
498
500
                section["approval_duration"])
499
 
 
 
501
            client["checker_command"] = section["checker"]
 
502
            client["last_approval_request"] = None
 
503
            client["last_checked_ok"] = None
 
504
            client["last_checker_status"] = None
 
505
        
500
506
        return settings
501
507
        
502
508
        
503
 
    def __init__(self, config, name = None):
 
509
    def __init__(self, settings, name = None):
504
510
        """Note: the 'checker' key in 'config' sets the
505
511
        'checker_command' attribute and *not* the 'checker'
506
512
        attribute."""
507
513
        self.name = name
 
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
       
508
528
        logger.debug("Creating client %r", self.name)
509
529
        # Uppercase and remove spaces from fingerprint for later
510
530
        # comparison purposes with return value from the fingerprint()
511
531
        # function
512
 
        self.fingerprint = config["fingerprint"]
513
532
        logger.debug("  Fingerprint: %s", self.fingerprint)
514
 
        self.secret = config["secret"]
515
 
        self.host = config["host"]
516
 
        self.created = datetime.datetime.utcnow()
517
 
        self.enabled = config["enabled"]
518
 
        self.last_approval_request = None
519
 
        if self.enabled:
520
 
            self.last_enabled = datetime.datetime.utcnow()
521
 
        else:
522
 
            self.last_enabled = None
523
 
        self.last_checked_ok = None
524
 
        self.last_checker_status = None
525
 
        self.timeout = config["timeout"]
526
 
        self.extended_timeout = config["extended_timeout"]
527
 
        self.interval = config["interval"]
 
533
        self.created = settings.get("created",
 
534
                                    datetime.datetime.utcnow())
 
535
 
 
536
        # attributes specific for this server instance
528
537
        self.checker = None
529
538
        self.checker_initiator_tag = None
530
539
        self.disable_initiator_tag = None
531
 
        if self.enabled:
532
 
            self.expires = datetime.datetime.utcnow() + self.timeout
533
 
        else:
534
 
            self.expires = None
535
540
        self.checker_callback_tag = None
536
 
        self.checker_command = config["checker"]
537
541
        self.current_checker_command = None
538
542
        self.approved = None
539
 
        self.approved_by_default = config["approved_by_default"]
540
543
        self.approvals_pending = 0
541
 
        self.approval_delay = config["approval_delay"]
542
 
        self.approval_duration = config["approval_duration"]
543
544
        self.changedstate = (multiprocessing_manager
544
545
                             .Condition(multiprocessing_manager
545
546
                                        .Lock()))
849
850
            # signatures other than "ay".
850
851
            if prop._dbus_signature != "ay":
851
852
                raise ValueError
852
 
            value = dbus.ByteArray(''.join(unichr(byte)
853
 
                                           for byte in value))
 
853
            value = dbus.ByteArray(b''.join(chr(byte)
 
854
                                            for byte in value))
854
855
        prop(value)
855
856
    
856
857
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
1048
1049
    def __init__(self, bus = None, *args, **kwargs):
1049
1050
        self.bus = bus
1050
1051
        Client.__init__(self, *args, **kwargs)
1051
 
        
1052
 
        self._approvals_pending = 0
1053
1052
        # Only now, when this client is initialized, can it show up on
1054
1053
        # the D-Bus
1055
1054
        client_object_name = unicode(self.name).translate(
1225
1224
        "D-Bus signal"
1226
1225
        return self.need_approval()
1227
1226
    
1228
 
    # NeRwequest - signal
1229
 
    @dbus.service.signal(_interface, signature="s")
1230
 
    def NewRequest(self, ip):
1231
 
        """D-Bus signal
1232
 
        Is sent after a client request a password.
1233
 
        """
1234
 
        pass
1235
 
    
1236
1227
    ## Methods
1237
1228
    
1238
1229
    # Approve - method
1365
1356
        if value is None:       # get
1366
1357
            return dbus.UInt64(self.timeout_milliseconds())
1367
1358
        self.timeout = datetime.timedelta(0, 0, 0, value)
1368
 
        if getattr(self, "disable_initiator_tag", None) is None:
1369
 
            return
1370
1359
        # Reschedule timeout
1371
 
        gobject.source_remove(self.disable_initiator_tag)
1372
 
        self.disable_initiator_tag = None
1373
 
        self.expires = None
1374
 
        time_to_die = timedelta_to_milliseconds((self
1375
 
                                                 .last_checked_ok
1376
 
                                                 + self.timeout)
1377
 
                                                - datetime.datetime
1378
 
                                                .utcnow())
1379
 
        if time_to_die <= 0:
1380
 
            # The timeout has passed
1381
 
            self.disable()
1382
 
        else:
1383
 
            self.expires = (datetime.datetime.utcnow()
1384
 
                            + datetime.timedelta(milliseconds =
1385
 
                                                 time_to_die))
1386
 
            self.disable_initiator_tag = (gobject.timeout_add
1387
 
                                          (time_to_die, self.disable))
 
1360
        if self.enabled:
 
1361
            now = datetime.datetime.utcnow()
 
1362
            time_to_die = timedelta_to_milliseconds(
 
1363
                (self.last_checked_ok + self.timeout) - now)
 
1364
            if time_to_die <= 0:
 
1365
                # The timeout has passed
 
1366
                self.disable()
 
1367
            else:
 
1368
                self.expires = (now +
 
1369
                                datetime.timedelta(milliseconds =
 
1370
                                                   time_to_die))
 
1371
                if (getattr(self, "disable_initiator_tag", None)
 
1372
                    is None):
 
1373
                    return
 
1374
                gobject.source_remove(self.disable_initiator_tag)
 
1375
                self.disable_initiator_tag = (gobject.timeout_add
 
1376
                                              (time_to_die,
 
1377
                                               self.disable))
1388
1378
    
1389
1379
    # ExtendedTimeout - property
1390
1380
    @dbus_service_property(_interface, signature="t",
1546
1536
                except KeyError:
1547
1537
                    return
1548
1538
                
1549
 
                if self.server.use_dbus:
1550
 
                    # Emit D-Bus signal
1551
 
                    client.NewRequest(str(self.client_address))
1552
 
                
1553
1539
                if client.approval_delay:
1554
1540
                    delay = client.approval_delay
1555
1541
                    client.approvals_pending += 1
2086
2072
                                     stored_state_file)
2087
2073
    
2088
2074
    if debug:
2089
 
        initlogger(logging.DEBUG)
 
2075
        initlogger(debug, logging.DEBUG)
2090
2076
    else:
2091
2077
        if not debuglevel:
2092
 
            initlogger()
 
2078
            initlogger(debug)
2093
2079
        else:
2094
2080
            level = getattr(logging, debuglevel.upper())
2095
 
            initlogger(level)
 
2081
            initlogger(debug, level)
2096
2082
    
2097
2083
    if server_settings["servicename"] != "Mandos":
2098
2084
        syslogger.setFormatter(logging.Formatter
2101
2087
                                % server_settings["servicename"]))
2102
2088
    
2103
2089
    # Parse config file with clients
2104
 
    client_config = configparser.SafeConfigParser(Client.client_defaults)
 
2090
    client_config = configparser.SafeConfigParser(Client
 
2091
                                                  .client_defaults)
2105
2092
    client_config.read(os.path.join(server_settings["configdir"],
2106
2093
                                    "clients.conf"))
2107
2094
    
2164
2151
        os.dup2(null, sys.stdin.fileno())
2165
2152
        if null > 2:
2166
2153
            os.close(null)
2167
 
    else:
2168
 
        # No console logging
2169
 
        logger.removeHandler(console)
2170
2154
    
2171
2155
    # Need to fork before connecting to D-Bus
2172
2156
    if not debug:
2173
2157
        # Close all input and output, do double fork, etc.
2174
2158
        daemon()
2175
2159
    
 
2160
    gobject.threads_init()
 
2161
    
2176
2162
    global main_loop
2177
2163
    # From the Avahi example code
2178
2164
    DBusGMainLoop(set_as_default=True )
2210
2196
    
2211
2197
    client_settings = Client.config_parser(client_config)
2212
2198
    old_client_settings = {}
2213
 
    clients_data = []
 
2199
    clients_data = {}
2214
2200
    
2215
2201
    # Get client data and settings from last running state.
2216
2202
    if server_settings["restore"]:
2224
2210
                           .format(e))
2225
2211
            if e.errno != errno.ENOENT:
2226
2212
                raise
 
2213
        except EOFError as e:
 
2214
            logger.warning("Could not load persistent state: "
 
2215
                           "EOFError: {0}".format(e))
2227
2216
    
2228
2217
    with PGPEngine() as pgp:
2229
 
        for client in clients_data:
2230
 
            client_name = client["name"]
2231
 
            
 
2218
        for client_name, client in clients_data.iteritems():
2232
2219
            # Decide which value to use after restoring saved state.
2233
2220
            # We have three different values: Old config file,
2234
2221
            # new config file, and saved state.
2248
2235
            
2249
2236
            # Clients who has passed its expire date can still be
2250
2237
            # enabled if its last checker was successful.  Clients
2251
 
            # whose checker failed before we stored its state is
2252
 
            # assumed to have failed all checkers during downtime.
 
2238
            # whose checker succeeded before we stored its state is
 
2239
            # assumed to have successfully run all checkers during
 
2240
            # downtime.
2253
2241
            if client["enabled"]:
2254
2242
                if datetime.datetime.utcnow() >= client["expires"]:
2255
2243
                    if not client["last_checked_ok"]:
2256
2244
                        logger.warning(
2257
2245
                            "disabling client {0} - Client never "
2258
 
                            "performed a successfull checker"
2259
 
                            .format(client["name"]))
 
2246
                            "performed a successful checker"
 
2247
                            .format(client_name))
2260
2248
                        client["enabled"] = False
2261
2249
                    elif client["last_checker_status"] != 0:
2262
2250
                        logger.warning(
2263
2251
                            "disabling client {0} - Client "
2264
2252
                            "last checker failed with error code {1}"
2265
 
                            .format(client["name"],
 
2253
                            .format(client_name,
2266
2254
                                    client["last_checker_status"]))
2267
2255
                        client["enabled"] = False
2268
2256
                    else:
2269
2257
                        client["expires"] = (datetime.datetime
2270
2258
                                             .utcnow()
2271
2259
                                             + client["timeout"])
2272
 
            
2273
 
            client["changedstate"] = (multiprocessing_manager
2274
 
                                      .Condition
2275
 
                                      (multiprocessing_manager
2276
 
                                       .Lock()))
2277
 
            client["checker"] = None
2278
 
            if use_dbus:
2279
 
                new_client = (ClientDBusTransitional.__new__
2280
 
                              (ClientDBusTransitional))
2281
 
                tcp_server.clients[client_name] = new_client
2282
 
                new_client.bus = bus
2283
 
                for name, value in client.iteritems():
2284
 
                    setattr(new_client, name, value)
2285
 
                client_object_name = unicode(client_name).translate(
2286
 
                    {ord("."): ord("_"),
2287
 
                     ord("-"): ord("_")})
2288
 
                new_client.dbus_object_path = (dbus.ObjectPath
2289
 
                                               ("/clients/"
2290
 
                                                + client_object_name))
2291
 
                DBusObjectWithProperties.__init__(new_client,
2292
 
                                                  new_client.bus,
2293
 
                                                  new_client
2294
 
                                                  .dbus_object_path)
2295
 
            else:
2296
 
                tcp_server.clients[client_name] = (Client.__new__
2297
 
                                                   (Client))
2298
 
                for name, value in client.iteritems():
2299
 
                    setattr(tcp_server.clients[client_name],
2300
 
                            name, value)
2301
 
            
 
2260
                        logger.debug("Last checker succeeded,"
 
2261
                                     " keeping {0} enabled"
 
2262
                                     .format(client_name))
2302
2263
            try:
2303
 
                tcp_server.clients[client_name].secret = (
2304
 
                    pgp.decrypt(tcp_server.clients[client_name]
2305
 
                                .encrypted_secret,
 
2264
                client["secret"] = (
 
2265
                    pgp.decrypt(client["encrypted_secret"],
2306
2266
                                client_settings[client_name]
2307
2267
                                ["secret"]))
2308
2268
            except PGPError:
2309
2269
                # If decryption fails, we use secret from new settings
2310
2270
                logger.debug("Failed to decrypt {0} old secret"
2311
2271
                             .format(client_name))
2312
 
                tcp_server.clients[client_name].secret = (
 
2272
                client["secret"] = (
2313
2273
                    client_settings[client_name]["secret"])
 
2274
 
2314
2275
    
2315
 
    # Create/remove clients based on new changes made to config
2316
 
    for clientname in set(old_client_settings) - set(client_settings):
2317
 
        del tcp_server.clients[clientname]
2318
 
    for clientname in set(client_settings) - set(old_client_settings):
2319
 
        tcp_server.clients[clientname] = (client_class(name = clientname,
2320
 
                                                       config =
2321
 
                                                       client_settings
2322
 
                                                       [clientname]))
 
2276
    # Add/remove clients based on new changes made to config
 
2277
    for client_name in (set(old_client_settings)
 
2278
                        - set(client_settings)):
 
2279
        del clients_data[client_name]
 
2280
    for client_name in (set(client_settings)
 
2281
                        - set(old_client_settings)):
 
2282
        clients_data[client_name] = client_settings[client_name]
 
2283
 
 
2284
    # Create all client objects
 
2285
    for client_name, client in clients_data.iteritems():
 
2286
        tcp_server.clients[client_name] = client_class(
 
2287
            name = client_name, settings = client)
2323
2288
    
2324
2289
    if not tcp_server.clients:
2325
2290
        logger.warning("No clients defined")
2411
2376
        # Store client before exiting. Secrets are encrypted with key
2412
2377
        # based on what config file has. If config file is
2413
2378
        # removed/edited, old secret will thus be unrecovable.
2414
 
        clients = []
 
2379
        clients = {}
2415
2380
        with PGPEngine() as pgp:
2416
2381
            for client in tcp_server.clients.itervalues():
2417
2382
                key = client_settings[client.name]["secret"]
2419
2384
                                                      key)
2420
2385
                client_dict = {}
2421
2386
                
2422
 
                # A list of attributes that will not be stored when
2423
 
                # shutting down.
 
2387
                # A list of attributes that can not be pickled
 
2388
                # + secret.
2424
2389
                exclude = set(("bus", "changedstate", "secret",
2425
2390
                               "checker"))
2426
2391
                for name, typ in (inspect.getmembers
2433
2398
                    if attr not in exclude:
2434
2399
                        client_dict[attr] = getattr(client, attr)
2435
2400
                
2436
 
                clients.append(client_dict)
 
2401
                clients[client.name] = client_dict
2437
2402
                del client_settings[client.name]["secret"]
2438
2403
        
2439
2404
        try:
2440
 
            with os.fdopen(os.open(stored_state_path,
2441
 
                                   os.O_CREAT|os.O_WRONLY|os.O_TRUNC,
2442
 
                                   0600), "wb") as stored_state:
 
2405
            tempfd, tempname = tempfile.mkstemp(suffix=".pickle",
 
2406
                                                prefix="clients-",
 
2407
                                                dir=os.path.dirname
 
2408
                                                (stored_state_path))
 
2409
            with os.fdopen(tempfd, "wb") as stored_state:
2443
2410
                pickle.dump((clients, client_settings), stored_state)
 
2411
            os.rename(tempname, stored_state_path)
2444
2412
        except (IOError, OSError) as e:
2445
2413
            logger.warning("Could not save persistent state: {0}"
2446
2414
                           .format(e))
2447
 
            if e.errno not in (errno.ENOENT, errno.EACCES):
2448
 
                raise
 
2415
            if not debug:
 
2416
                try:
 
2417
                    os.remove(tempname)
 
2418
                except NameError:
 
2419
                    pass
 
2420
            if e.errno not in set((errno.ENOENT, errno.EACCES,
 
2421
                                   errno.EEXIST)):
 
2422
                raise e
2449
2423
        
2450
2424
        # Delete all clients, and settings from config
2451
2425
        while tcp_server.clients: