/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

working new feature: network-hooks - Enables user-scripts to take up
                     interfaces during bootup

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-2012 Teddy Hogeborn
15
 
# Copyright © 2008-2012 Björn Påhlsson
 
14
# Copyright © 2008-2011 Teddy Hogeborn
 
15
# Copyright © 2008-2011 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.5.2"
 
88
version = "1.4.1"
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(debug, level=logging.WARNING):
 
113
def initlogger(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
 
    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)
 
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)
128
127
    logger.setLevel(level)
129
128
 
130
129
 
142
141
        self.gnupg.options.meta_interactive = False
143
142
        self.gnupg.options.homedir = self.tempdir
144
143
        self.gnupg.options.extra_args.extend(['--force-mdc',
145
 
                                              '--quiet',
146
 
                                              '--no-use-agent'])
 
144
                                              '--quiet'])
147
145
    
148
146
    def __enter__(self):
149
147
        return self
415
413
    last_checked_ok: datetime.datetime(); (UTC) or None
416
414
    last_checker_status: integer between 0 and 255 reflecting exit
417
415
                         status of last checker. -1 reflects crashed
418
 
                         checker, -2 means no checker completed yet.
 
416
                         checker, or None.
419
417
    last_enabled: datetime.datetime(); (UTC) or None
420
418
    name:       string; from the config file, used in log messages and
421
419
                        D-Bus identifiers
422
420
    secret:     bytestring; sent verbatim (over TLS) to client
423
421
    timeout:    datetime.timedelta(); How long from last_checked_ok
424
422
                                      until this client is disabled
425
 
    extended_timeout:   extra long timeout when secret has been sent
 
423
    extended_timeout:   extra long timeout when password has been sent
426
424
    runtime_expansions: Allowed attributes for runtime expansion.
427
425
    expires:    datetime.datetime(); time (UTC) when a client will be
428
426
                disabled, or None
460
458
 
461
459
    @staticmethod
462
460
    def config_parser(config):
463
 
        """Construct a new dict of client settings of this form:
 
461
        """ Construct a new dict of client settings of this form:
464
462
        { 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
 
        """
 
463
        with exceptions for any special settings as defined above"""
469
464
        settings = {}
470
465
        for client_name in config.sections():
471
466
            section = dict(config.items(client_name))
475
470
            # Reformat values from string types to Python types
476
471
            client["approved_by_default"] = config.getboolean(
477
472
                client_name, "approved_by_default")
478
 
            client["enabled"] = config.getboolean(client_name,
479
 
                                                  "enabled")
 
473
            client["enabled"] = config.getboolean(client_name, "enabled")
480
474
            
481
475
            client["fingerprint"] = (section["fingerprint"].upper()
482
476
                                     .replace(" ", ""))
501
495
            client["checker_command"] = section["checker"]
502
496
            client["last_approval_request"] = None
503
497
            client["last_checked_ok"] = None
504
 
            client["last_checker_status"] = -2
505
 
        
 
498
            client["last_checker_status"] = None
 
499
            if client["enabled"]:
 
500
                client["last_enabled"] = datetime.datetime.utcnow()
 
501
                client["expires"] = (datetime.datetime.utcnow()
 
502
                                     + client["timeout"])
 
503
            else:
 
504
                client["last_enabled"] = None
 
505
                client["expires"] = None
 
506
 
506
507
        return settings
507
508
        
508
509
        
515
516
        for setting, value in settings.iteritems():
516
517
            setattr(self, setting, value)
517
518
        
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
 
       
528
519
        logger.debug("Creating client %r", self.name)
529
520
        # Uppercase and remove spaces from fingerprint for later
530
521
        # comparison purposes with return value from the fingerprint()
531
522
        # function
532
523
        logger.debug("  Fingerprint: %s", self.fingerprint)
533
 
        self.created = settings.get("created",
534
 
                                    datetime.datetime.utcnow())
 
524
        self.created = settings.get("created", datetime.datetime.utcnow())
535
525
 
536
526
        # attributes specific for this server instance
537
527
        self.checker = None
626
616
            logger.warning("Checker for %(name)s crashed?",
627
617
                           vars(self))
628
618
    
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."""
 
619
    def checked_ok(self, timeout=None):
 
620
        """Bump up the timeout for this client.
 
621
        
 
622
        This should only be called when the client has been seen,
 
623
        alive and well.
 
624
        """
637
625
        if timeout is None:
638
626
            timeout = self.timeout
 
627
        self.last_checked_ok = datetime.datetime.utcnow()
639
628
        if self.disable_initiator_tag is not None:
640
629
            gobject.source_remove(self.disable_initiator_tag)
641
630
        if getattr(self, "enabled", False):
851
840
            # signatures other than "ay".
852
841
            if prop._dbus_signature != "ay":
853
842
                raise ValueError
854
 
            value = dbus.ByteArray(b''.join(chr(byte)
855
 
                                            for byte in value))
 
843
            value = dbus.ByteArray(''.join(unichr(byte)
 
844
                                           for byte in value))
856
845
        prop(value)
857
846
    
858
847
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
1050
1039
    def __init__(self, bus = None, *args, **kwargs):
1051
1040
        self.bus = bus
1052
1041
        Client.__init__(self, *args, **kwargs)
 
1042
        self._approvals_pending = 0
 
1043
        
 
1044
        self._approvals_pending = 0
1053
1045
        # Only now, when this client is initialized, can it show up on
1054
1046
        # the D-Bus
1055
1047
        client_object_name = unicode(self.name).translate(
1101
1093
                                       checker is not None)
1102
1094
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
1103
1095
                                           "LastCheckedOK")
1104
 
    last_checker_status = notifychangeproperty(dbus.Int16,
1105
 
                                               "LastCheckerStatus")
1106
1096
    last_approval_request = notifychangeproperty(
1107
1097
        datetime_to_dbus, "LastApprovalRequest")
1108
1098
    approved_by_default = notifychangeproperty(dbus.Boolean,
1227
1217
        "D-Bus signal"
1228
1218
        return self.need_approval()
1229
1219
    
 
1220
    # NeRwequest - signal
 
1221
    @dbus.service.signal(_interface, signature="s")
 
1222
    def NewRequest(self, ip):
 
1223
        """D-Bus signal
 
1224
        Is sent after a client request a password.
 
1225
        """
 
1226
        pass
 
1227
    
1230
1228
    ## Methods
1231
1229
    
1232
1230
    # Approve - method
1342
1340
            return
1343
1341
        return datetime_to_dbus(self.last_checked_ok)
1344
1342
    
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
 
    
1351
1343
    # Expires - property
1352
1344
    @dbus_service_property(_interface, signature="s", access="read")
1353
1345
    def Expires_dbus_property(self):
1365
1357
        if value is None:       # get
1366
1358
            return dbus.UInt64(self.timeout_milliseconds())
1367
1359
        self.timeout = datetime.timedelta(0, 0, 0, value)
 
1360
        if getattr(self, "disable_initiator_tag", None) is None:
 
1361
            return
1368
1362
        # Reschedule timeout
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))
 
1363
        gobject.source_remove(self.disable_initiator_tag)
 
1364
        self.disable_initiator_tag = None
 
1365
        self.expires = None
 
1366
        time_to_die = timedelta_to_milliseconds((self
 
1367
                                                 .last_checked_ok
 
1368
                                                 + self.timeout)
 
1369
                                                - datetime.datetime
 
1370
                                                .utcnow())
 
1371
        if time_to_die <= 0:
 
1372
            # The timeout has passed
 
1373
            self.disable()
 
1374
        else:
 
1375
            self.expires = (datetime.datetime.utcnow()
 
1376
                            + datetime.timedelta(milliseconds =
 
1377
                                                 time_to_die))
 
1378
            self.disable_initiator_tag = (gobject.timeout_add
 
1379
                                          (time_to_die, self.disable))
1387
1380
    
1388
1381
    # ExtendedTimeout - property
1389
1382
    @dbus_service_property(_interface, signature="t",
1545
1538
                except KeyError:
1546
1539
                    return
1547
1540
                
 
1541
                if self.server.use_dbus:
 
1542
                    # Emit D-Bus signal
 
1543
                    client.NewRequest(str(self.client_address))
 
1544
                
1548
1545
                if client.approval_delay:
1549
1546
                    delay = client.approval_delay
1550
1547
                    client.approvals_pending += 1
1614
1611
                
1615
1612
                logger.info("Sending secret to %s", client.name)
1616
1613
                # bump the timeout using extended_timeout
1617
 
                client.bump_timeout(client.extended_timeout)
 
1614
                client.checked_ok(client.extended_timeout)
1618
1615
                if self.server.use_dbus:
1619
1616
                    # Emit D-Bus signal
1620
1617
                    client.GotSecret()
2081
2078
                                     stored_state_file)
2082
2079
    
2083
2080
    if debug:
2084
 
        initlogger(debug, logging.DEBUG)
 
2081
        initlogger(logging.DEBUG)
2085
2082
    else:
2086
2083
        if not debuglevel:
2087
 
            initlogger(debug)
 
2084
            initlogger()
2088
2085
        else:
2089
2086
            level = getattr(logging, debuglevel.upper())
2090
 
            initlogger(debug, level)
 
2087
            initlogger(level)
2091
2088
    
2092
2089
    if server_settings["servicename"] != "Mandos":
2093
2090
        syslogger.setFormatter(logging.Formatter
2096
2093
                                % server_settings["servicename"]))
2097
2094
    
2098
2095
    # Parse config file with clients
2099
 
    client_config = configparser.SafeConfigParser(Client
2100
 
                                                  .client_defaults)
 
2096
    client_config = configparser.SafeConfigParser(Client.client_defaults)
2101
2097
    client_config.read(os.path.join(server_settings["configdir"],
2102
2098
                                    "clients.conf"))
2103
2099
    
2160
2156
        os.dup2(null, sys.stdin.fileno())
2161
2157
        if null > 2:
2162
2158
            os.close(null)
 
2159
    else:
 
2160
        # No console logging
 
2161
        logger.removeHandler(console)
2163
2162
    
2164
2163
    # Need to fork before connecting to D-Bus
2165
2164
    if not debug:
2166
2165
        # Close all input and output, do double fork, etc.
2167
2166
        daemon()
2168
2167
    
2169
 
    gobject.threads_init()
2170
 
    
2171
2168
    global main_loop
2172
2169
    # From the Avahi example code
2173
2170
    DBusGMainLoop(set_as_default=True )
2219
2216
                           .format(e))
2220
2217
            if e.errno != errno.ENOENT:
2221
2218
                raise
2222
 
        except EOFError as e:
2223
 
            logger.warning("Could not load persistent state: "
2224
 
                           "EOFError: {0}".format(e))
2225
2219
    
2226
2220
    with PGPEngine() as pgp:
2227
2221
        for client_name, client in clients_data.iteritems():
2244
2238
            
2245
2239
            # Clients who has passed its expire date can still be
2246
2240
            # 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.
 
2241
            # whose checker failed before we stored its state is
 
2242
            # assumed to have failed all checkers during downtime.
2250
2243
            if client["enabled"]:
2251
2244
                if datetime.datetime.utcnow() >= client["expires"]:
2252
2245
                    if not client["last_checked_ok"]:
2253
2246
                        logger.warning(
2254
2247
                            "disabling client {0} - Client never "
2255
 
                            "performed a successful checker"
2256
 
                            .format(client_name))
 
2248
                            "performed a successfull checker"
 
2249
                            .format(client["name"]))
2257
2250
                        client["enabled"] = False
2258
2251
                    elif client["last_checker_status"] != 0:
2259
2252
                        logger.warning(
2260
2253
                            "disabling client {0} - Client "
2261
2254
                            "last checker failed with error code {1}"
2262
 
                            .format(client_name,
 
2255
                            .format(client["name"],
2263
2256
                                    client["last_checker_status"]))
2264
2257
                        client["enabled"] = False
2265
2258
                    else:
2266
2259
                        client["expires"] = (datetime.datetime
2267
2260
                                             .utcnow()
2268
2261
                                             + client["timeout"])
2269
 
                        logger.debug("Last checker succeeded,"
2270
 
                                     " keeping {0} enabled"
2271
 
                                     .format(client_name))
 
2262
                    
2272
2263
            try:
2273
2264
                client["secret"] = (
2274
2265
                    pgp.decrypt(client["encrypted_secret"],
2283
2274
 
2284
2275
    
2285
2276
    # Add/remove clients based on new changes made to config
2286
 
    for client_name in (set(old_client_settings)
2287
 
                        - set(client_settings)):
 
2277
    for client_name in set(old_client_settings) - set(client_settings):
2288
2278
        del clients_data[client_name]
2289
 
    for client_name in (set(client_settings)
2290
 
                        - set(old_client_settings)):
 
2279
    for client_name in set(client_settings) - set(old_client_settings):
2291
2280
        clients_data[client_name] = client_settings[client_name]
2292
2281
 
2293
 
    # Create all client objects
 
2282
    # Create clients all clients
2294
2283
    for client_name, client in clients_data.iteritems():
2295
2284
        tcp_server.clients[client_name] = client_class(
2296
2285
            name = client_name, settings = client)
2411
2400
                del client_settings[client.name]["secret"]
2412
2401
        
2413
2402
        try:
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:
 
2403
            with os.fdopen(os.open(stored_state_path,
 
2404
                                   os.O_CREAT|os.O_WRONLY|os.O_TRUNC,
 
2405
                                   0600), "wb") as stored_state:
2419
2406
                pickle.dump((clients, client_settings), stored_state)
2420
 
            os.rename(tempname, stored_state_path)
2421
2407
        except (IOError, OSError) as e:
2422
2408
            logger.warning("Could not save persistent state: {0}"
2423
2409
                           .format(e))
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
 
2410
            if e.errno not in (errno.ENOENT, errno.EACCES):
 
2411
                raise
2432
2412
        
2433
2413
        # Delete all clients, and settings from config
2434
2414
        while tcp_server.clients: