/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-01 20:45:53 UTC
  • Revision ID: teddy@recompile.se-20120101204553-f1zlty873gswtf5n
* README: Hint that the intro(8mandos) manual page is in the server
          package.

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"
90
 
stored_state_path = "/var/lib/mandos/clients.pickle"
 
88
version = "1.5.0"
 
89
stored_state_file = "clients.pickle"
91
90
 
92
91
logger = logging.getLogger()
93
92
syslogger = (logging.handlers.SysLogHandler
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
 
131
 
class CryptoError(Exception):
 
131
class PGPError(Exception):
 
132
    """Exception if encryption/decryption fails"""
132
133
    pass
133
134
 
134
135
 
135
 
class Crypto(object):
 
136
class PGPEngine(object):
136
137
    """A simple class for OpenPGP symmetric encryption & decryption"""
137
138
    def __init__(self):
138
139
        self.gnupg = GnuPGInterface.GnuPG()
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
184
186
                    ciphertext = f.read()
185
187
                proc.wait()
186
188
            except IOError as e:
187
 
                raise CryptoError(e)
 
189
                raise PGPError(e)
188
190
        self.gnupg.passphrase = None
189
191
        return ciphertext
190
192
    
201
203
                    decrypted_plaintext = f.read()
202
204
                proc.wait()
203
205
            except IOError as e:
204
 
                raise CryptoError(e)
 
206
                raise PGPError(e)
205
207
        self.gnupg.passphrase = None
206
208
        return decrypted_plaintext
207
209
 
377
379
                                % self.name))
378
380
        return ret
379
381
 
380
 
def _timedelta_to_milliseconds(td):
 
382
def timedelta_to_milliseconds(td):
381
383
    "Convert a datetime.timedelta() to milliseconds"
382
384
    return ((td.days * 24 * 60 * 60 * 1000)
383
385
            + (td.seconds * 1000)
387
389
    """A representation of a client host served by this server.
388
390
    
389
391
    Attributes:
390
 
    _approved:   bool(); 'None' if not yet approved/disapproved
 
392
    approved:   bool(); 'None' if not yet approved/disapproved
391
393
    approval_delay: datetime.timedelta(); Time to wait for approval
392
394
    approval_duration: datetime.timedelta(); Duration of one approval
393
395
    checker:    subprocess.Popen(); a running checker process used
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.
418
 
    last_enabled: datetime.datetime(); (UTC)
 
419
    last_enabled: datetime.datetime(); (UTC) or None
419
420
    name:       string; from the config file, used in log messages and
420
421
                        D-Bus identifiers
421
422
    secret:     bytestring; sent verbatim (over TLS) to client
431
432
                          "created", "enabled", "fingerprint",
432
433
                          "host", "interval", "last_checked_ok",
433
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
                        }
434
445
    
435
446
    def timeout_milliseconds(self):
436
447
        "Return the 'timeout' attribute in milliseconds"
437
 
        return _timedelta_to_milliseconds(self.timeout)
 
448
        return timedelta_to_milliseconds(self.timeout)
438
449
    
439
450
    def extended_timeout_milliseconds(self):
440
451
        "Return the 'extended_timeout' attribute in milliseconds"
441
 
        return _timedelta_to_milliseconds(self.extended_timeout)
 
452
        return timedelta_to_milliseconds(self.extended_timeout)
442
453
    
443
454
    def interval_milliseconds(self):
444
455
        "Return the 'interval' attribute in milliseconds"
445
 
        return _timedelta_to_milliseconds(self.interval)
 
456
        return timedelta_to_milliseconds(self.interval)
446
457
    
447
458
    def approval_delay_milliseconds(self):
448
 
        return _timedelta_to_milliseconds(self.approval_delay)
449
 
    
450
 
    def __init__(self, name = None, config=None):
 
459
        return timedelta_to_milliseconds(self.approval_delay)
 
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"] = None
 
505
        
 
506
        return settings
 
507
        
 
508
        
 
509
    def __init__(self, settings, name = None):
451
510
        """Note: the 'checker' key in 'config' sets the
452
511
        'checker_command' attribute and *not* the 'checker'
453
512
        attribute."""
454
513
        self.name = name
455
 
        if config is None:
456
 
            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
       
457
528
        logger.debug("Creating client %r", self.name)
458
529
        # Uppercase and remove spaces from fingerprint for later
459
530
        # comparison purposes with return value from the fingerprint()
460
531
        # function
461
 
        self.fingerprint = (config["fingerprint"].upper()
462
 
                            .replace(" ", ""))
463
532
        logger.debug("  Fingerprint: %s", self.fingerprint)
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 = True
477
 
        self.last_approval_request = None
478
 
        self.last_enabled = datetime.datetime.utcnow()
479
 
        self.last_checked_ok = None
480
 
        self.last_checker_status = None
481
 
        self.timeout = string_to_delta(config["timeout"])
482
 
        self.extended_timeout = string_to_delta(config
483
 
                                                ["extended_timeout"])
484
 
        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
485
537
        self.checker = None
486
538
        self.checker_initiator_tag = None
487
539
        self.disable_initiator_tag = None
488
 
        self.expires = datetime.datetime.utcnow() + self.timeout
489
540
        self.checker_callback_tag = None
490
 
        self.checker_command = config["checker"]
491
541
        self.current_checker_command = None
492
 
        self._approved = None
493
 
        self.approved_by_default = config.get("approved_by_default",
494
 
                                              True)
 
542
        self.approved = None
495
543
        self.approvals_pending = 0
496
 
        self.approval_delay = string_to_delta(
497
 
            config["approval_delay"])
498
 
        self.approval_duration = string_to_delta(
499
 
            config["approval_duration"])
500
544
        self.changedstate = (multiprocessing_manager
501
545
                             .Condition(multiprocessing_manager
502
546
                                        .Lock()))
569
613
        self.checker_callback_tag = None
570
614
        self.checker = None
571
615
        if os.WIFEXITED(condition):
572
 
            self.last_checker_status =  os.WEXITSTATUS(condition)
 
616
            self.last_checker_status = os.WEXITSTATUS(condition)
573
617
            if self.last_checker_status == 0:
574
618
                logger.info("Checker for %(name)s succeeded",
575
619
                            vars(self))
595
639
            gobject.source_remove(self.disable_initiator_tag)
596
640
        if getattr(self, "enabled", False):
597
641
            self.disable_initiator_tag = (gobject.timeout_add
598
 
                                          (_timedelta_to_milliseconds
 
642
                                          (timedelta_to_milliseconds
599
643
                                           (timeout), self.disable))
600
644
            self.expires = datetime.datetime.utcnow() + timeout
601
645
    
806
850
            # signatures other than "ay".
807
851
            if prop._dbus_signature != "ay":
808
852
                raise ValueError
809
 
            value = dbus.ByteArray(''.join(unichr(byte)
810
 
                                           for byte in value))
 
853
            value = dbus.ByteArray(b''.join(chr(byte)
 
854
                                            for byte in value))
811
855
        prop(value)
812
856
    
813
857
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
1005
1049
    def __init__(self, bus = None, *args, **kwargs):
1006
1050
        self.bus = bus
1007
1051
        Client.__init__(self, *args, **kwargs)
 
1052
        self._approvals_pending = 0
1008
1053
        
1009
1054
        self._approvals_pending = 0
1010
1055
        # Only now, when this client is initialized, can it show up on
1062
1107
        datetime_to_dbus, "LastApprovalRequest")
1063
1108
    approved_by_default = notifychangeproperty(dbus.Boolean,
1064
1109
                                               "ApprovedByDefault")
1065
 
    approval_delay = notifychangeproperty(dbus.UInt16,
 
1110
    approval_delay = notifychangeproperty(dbus.UInt64,
1066
1111
                                          "ApprovalDelay",
1067
1112
                                          type_func =
1068
 
                                          _timedelta_to_milliseconds)
 
1113
                                          timedelta_to_milliseconds)
1069
1114
    approval_duration = notifychangeproperty(
1070
 
        dbus.UInt16, "ApprovalDuration",
1071
 
        type_func = _timedelta_to_milliseconds)
 
1115
        dbus.UInt64, "ApprovalDuration",
 
1116
        type_func = timedelta_to_milliseconds)
1072
1117
    host = notifychangeproperty(dbus.String, "Host")
1073
 
    timeout = notifychangeproperty(dbus.UInt16, "Timeout",
 
1118
    timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1074
1119
                                   type_func =
1075
 
                                   _timedelta_to_milliseconds)
 
1120
                                   timedelta_to_milliseconds)
1076
1121
    extended_timeout = notifychangeproperty(
1077
 
        dbus.UInt16, "ExtendedTimeout",
1078
 
        type_func = _timedelta_to_milliseconds)
1079
 
    interval = notifychangeproperty(dbus.UInt16,
 
1122
        dbus.UInt64, "ExtendedTimeout",
 
1123
        type_func = timedelta_to_milliseconds)
 
1124
    interval = notifychangeproperty(dbus.UInt64,
1080
1125
                                    "Interval",
1081
1126
                                    type_func =
1082
 
                                    _timedelta_to_milliseconds)
 
1127
                                    timedelta_to_milliseconds)
1083
1128
    checker_command = notifychangeproperty(dbus.String, "Checker")
1084
1129
    
1085
1130
    del notifychangeproperty
1127
1172
        return r
1128
1173
    
1129
1174
    def _reset_approved(self):
1130
 
        self._approved = None
 
1175
        self.approved = None
1131
1176
        return False
1132
1177
    
1133
1178
    def approve(self, value=True):
1134
1179
        self.send_changedstate()
1135
 
        self._approved = value
1136
 
        gobject.timeout_add(_timedelta_to_milliseconds
 
1180
        self.approved = value
 
1181
        gobject.timeout_add(timedelta_to_milliseconds
1137
1182
                            (self.approval_duration),
1138
1183
                            self._reset_approved)
1139
1184
    
1253
1298
                           access="readwrite")
1254
1299
    def ApprovalDuration_dbus_property(self, value=None):
1255
1300
        if value is None:       # get
1256
 
            return dbus.UInt64(_timedelta_to_milliseconds(
 
1301
            return dbus.UInt64(timedelta_to_milliseconds(
1257
1302
                    self.approval_duration))
1258
1303
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
1259
1304
    
1273
1318
    def Host_dbus_property(self, value=None):
1274
1319
        if value is None:       # get
1275
1320
            return dbus.String(self.host)
1276
 
        self.host = value
 
1321
        self.host = unicode(value)
1277
1322
    
1278
1323
    # Created - property
1279
1324
    @dbus_service_property(_interface, signature="s", access="read")
1280
1325
    def Created_dbus_property(self):
1281
 
        return dbus.String(datetime_to_dbus(self.created))
 
1326
        return datetime_to_dbus(self.created)
1282
1327
    
1283
1328
    # LastEnabled - property
1284
1329
    @dbus_service_property(_interface, signature="s", access="read")
1322
1367
        if value is None:       # get
1323
1368
            return dbus.UInt64(self.timeout_milliseconds())
1324
1369
        self.timeout = datetime.timedelta(0, 0, 0, value)
1325
 
        if getattr(self, "disable_initiator_tag", None) is None:
1326
 
            return
1327
1370
        # Reschedule timeout
1328
 
        gobject.source_remove(self.disable_initiator_tag)
1329
 
        self.disable_initiator_tag = None
1330
 
        self.expires = None
1331
 
        time_to_die = _timedelta_to_milliseconds((self
1332
 
                                                  .last_checked_ok
1333
 
                                                  + self.timeout)
1334
 
                                                 - datetime.datetime
1335
 
                                                 .utcnow())
1336
 
        if time_to_die <= 0:
1337
 
            # The timeout has passed
1338
 
            self.disable()
1339
 
        else:
1340
 
            self.expires = (datetime.datetime.utcnow()
1341
 
                            + datetime.timedelta(milliseconds =
1342
 
                                                 time_to_die))
1343
 
            self.disable_initiator_tag = (gobject.timeout_add
1344
 
                                          (time_to_die, self.disable))
 
1371
        if self.enabled:
 
1372
            now = datetime.datetime.utcnow()
 
1373
            time_to_die = timedelta_to_milliseconds(
 
1374
                (self.last_checked_ok + self.timeout) - now)
 
1375
            if time_to_die <= 0:
 
1376
                # The timeout has passed
 
1377
                self.disable()
 
1378
            else:
 
1379
                self.expires = (now +
 
1380
                                datetime.timedelta(milliseconds =
 
1381
                                                   time_to_die))
 
1382
                if (getattr(self, "disable_initiator_tag", None)
 
1383
                    is None):
 
1384
                    return
 
1385
                gobject.source_remove(self.disable_initiator_tag)
 
1386
                self.disable_initiator_tag = (gobject.timeout_add
 
1387
                                              (time_to_die,
 
1388
                                               self.disable))
1345
1389
    
1346
1390
    # ExtendedTimeout - property
1347
1391
    @dbus_service_property(_interface, signature="t",
1360
1404
        self.interval = datetime.timedelta(0, 0, 0, value)
1361
1405
        if getattr(self, "checker_initiator_tag", None) is None:
1362
1406
            return
1363
 
        # Reschedule checker run
1364
 
        gobject.source_remove(self.checker_initiator_tag)
1365
 
        self.checker_initiator_tag = (gobject.timeout_add
1366
 
                                      (value, self.start_checker))
1367
 
        self.start_checker()    # Start one now, too
 
1407
        if self.enabled:
 
1408
            # Reschedule checker run
 
1409
            gobject.source_remove(self.checker_initiator_tag)
 
1410
            self.checker_initiator_tag = (gobject.timeout_add
 
1411
                                          (value, self.start_checker))
 
1412
            self.start_checker()    # Start one now, too
1368
1413
    
1369
1414
    # Checker - property
1370
1415
    @dbus_service_property(_interface, signature="s",
1372
1417
    def Checker_dbus_property(self, value=None):
1373
1418
        if value is None:       # get
1374
1419
            return dbus.String(self.checker_command)
1375
 
        self.checker_command = value
 
1420
        self.checker_command = unicode(value)
1376
1421
    
1377
1422
    # CheckerRunning - property
1378
1423
    @dbus_service_property(_interface, signature="b",
1407
1452
            raise KeyError()
1408
1453
    
1409
1454
    def __getattribute__(self, name):
1410
 
        if(name == '_pipe'):
 
1455
        if name == '_pipe':
1411
1456
            return super(ProxyClient, self).__getattribute__(name)
1412
1457
        self._pipe.send(('getattr', name))
1413
1458
        data = self._pipe.recv()
1420
1465
            return func
1421
1466
    
1422
1467
    def __setattr__(self, name, value):
1423
 
        if(name == '_pipe'):
 
1468
        if name == '_pipe':
1424
1469
            return super(ProxyClient, self).__setattr__(name, value)
1425
1470
        self._pipe.send(('setattr', name, value))
1426
1471
 
1495
1540
                    logger.warning("Bad certificate: %s", error)
1496
1541
                    return
1497
1542
                logger.debug("Fingerprint: %s", fpr)
1498
 
                if self.server.use_dbus:
1499
 
                    # Emit D-Bus signal
1500
 
                    client.NewRequest(str(self.client_address))
1501
1543
                
1502
1544
                try:
1503
1545
                    client = ProxyClient(child_pipe, fpr,
1505
1547
                except KeyError:
1506
1548
                    return
1507
1549
                
 
1550
                if self.server.use_dbus:
 
1551
                    # Emit D-Bus signal
 
1552
                    client.NewRequest(str(self.client_address))
 
1553
                
1508
1554
                if client.approval_delay:
1509
1555
                    delay = client.approval_delay
1510
1556
                    client.approvals_pending += 1
1519
1565
                            client.Rejected("Disabled")
1520
1566
                        return
1521
1567
                    
1522
 
                    if client._approved or not client.approval_delay:
 
1568
                    if client.approved or not client.approval_delay:
1523
1569
                        #We are approved or approval is disabled
1524
1570
                        break
1525
 
                    elif client._approved is None:
 
1571
                    elif client.approved is None:
1526
1572
                        logger.info("Client %s needs approval",
1527
1573
                                    client.name)
1528
1574
                        if self.server.use_dbus:
1542
1588
                    time = datetime.datetime.now()
1543
1589
                    client.changedstate.acquire()
1544
1590
                    (client.changedstate.wait
1545
 
                     (float(client._timedelta_to_milliseconds(delay)
 
1591
                     (float(client.timedelta_to_milliseconds(delay)
1546
1592
                            / 1000)))
1547
1593
                    client.changedstate.release()
1548
1594
                    time2 = datetime.datetime.now()
1656
1702
    def sub_process_main(self, request, address):
1657
1703
        try:
1658
1704
            self.finish_request(request, address)
1659
 
        except:
 
1705
        except Exception:
1660
1706
            self.handle_error(request, address)
1661
1707
        self.close_request(request)
1662
1708
    
1972
2018
                        dest="use_ipv6", help="Do not use IPv6")
1973
2019
    parser.add_argument("--no-restore", action="store_false",
1974
2020
                        dest="restore", help="Do not restore stored"
1975
 
                        " state", default=True)
 
2021
                        " state")
 
2022
    parser.add_argument("--statedir", metavar="DIR",
 
2023
                        help="Directory to save/restore state in")
1976
2024
    
1977
2025
    options = parser.parse_args()
1978
2026
    
1992
2040
                        "use_dbus": "True",
1993
2041
                        "use_ipv6": "True",
1994
2042
                        "debuglevel": "",
 
2043
                        "restore": "True",
 
2044
                        "statedir": "/var/lib/mandos"
1995
2045
                        }
1996
2046
    
1997
2047
    # Parse config file for server-global settings
2014
2064
    # options, if set.
2015
2065
    for option in ("interface", "address", "port", "debug",
2016
2066
                   "priority", "servicename", "configdir",
2017
 
                   "use_dbus", "use_ipv6", "debuglevel", "restore"):
 
2067
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
 
2068
                   "statedir"):
2018
2069
        value = getattr(options, option)
2019
2070
        if value is not None:
2020
2071
            server_settings[option] = value
2032
2083
    debuglevel = server_settings["debuglevel"]
2033
2084
    use_dbus = server_settings["use_dbus"]
2034
2085
    use_ipv6 = server_settings["use_ipv6"]
 
2086
    stored_state_path = os.path.join(server_settings["statedir"],
 
2087
                                     stored_state_file)
2035
2088
    
2036
2089
    if debug:
2037
 
        initlogger(logging.DEBUG)
 
2090
        initlogger(debug, logging.DEBUG)
2038
2091
    else:
2039
2092
        if not debuglevel:
2040
 
            initlogger()
 
2093
            initlogger(debug)
2041
2094
        else:
2042
2095
            level = getattr(logging, debuglevel.upper())
2043
 
            initlogger(level)
 
2096
            initlogger(debug, level)
2044
2097
    
2045
2098
    if server_settings["servicename"] != "Mandos":
2046
2099
        syslogger.setFormatter(logging.Formatter
2049
2102
                                % server_settings["servicename"]))
2050
2103
    
2051
2104
    # Parse config file with clients
2052
 
    client_defaults = { "timeout": "5m",
2053
 
                        "extended_timeout": "15m",
2054
 
                        "interval": "2m",
2055
 
                        "checker": "fping -q -- %%(host)s",
2056
 
                        "host": "",
2057
 
                        "approval_delay": "0s",
2058
 
                        "approval_duration": "1s",
2059
 
                        }
2060
 
    client_config = configparser.SafeConfigParser(client_defaults)
 
2105
    client_config = configparser.SafeConfigParser(Client
 
2106
                                                  .client_defaults)
2061
2107
    client_config.read(os.path.join(server_settings["configdir"],
2062
2108
                                    "clients.conf"))
2063
2109
    
2120
2166
        os.dup2(null, sys.stdin.fileno())
2121
2167
        if null > 2:
2122
2168
            os.close(null)
2123
 
    else:
2124
 
        # No console logging
2125
 
        logger.removeHandler(console)
2126
2169
    
2127
2170
    # Need to fork before connecting to D-Bus
2128
2171
    if not debug:
2129
2172
        # Close all input and output, do double fork, etc.
2130
2173
        daemon()
2131
2174
    
 
2175
    gobject.threads_init()
 
2176
    
2132
2177
    global main_loop
2133
2178
    # From the Avahi example code
2134
2179
    DBusGMainLoop(set_as_default=True )
2164
2209
        client_class = functools.partial(ClientDBusTransitional,
2165
2210
                                         bus = bus)
2166
2211
    
2167
 
    special_settings = {
2168
 
        # Some settings need to be accessd by special methods;
2169
 
        # booleans need .getboolean(), etc.  Here is a list of them:
2170
 
        "approved_by_default":
2171
 
            lambda section:
2172
 
            client_config.getboolean(section, "approved_by_default"),
2173
 
        }
2174
 
    # Construct a new dict of client settings of this form:
2175
 
    # { client_name: {setting_name: value, ...}, ...}
2176
 
    # with exceptions for any special settings as defined above
2177
 
    client_settings = dict((clientname,
2178
 
                           dict((setting,
2179
 
                                 (value
2180
 
                                  if setting not in special_settings
2181
 
                                  else special_settings[setting]
2182
 
                                  (clientname)))
2183
 
                                for setting, value in
2184
 
                                client_config.items(clientname)))
2185
 
                          for clientname in client_config.sections())
2186
 
    
 
2212
    client_settings = Client.config_parser(client_config)
2187
2213
    old_client_settings = {}
2188
 
    clients_data = []
 
2214
    clients_data = {}
2189
2215
    
2190
2216
    # Get client data and settings from last running state.
2191
2217
    if server_settings["restore"]:
2199
2225
                           .format(e))
2200
2226
            if e.errno != errno.ENOENT:
2201
2227
                raise
 
2228
        except EOFError as e:
 
2229
            logger.warning("Could not load persistent state: "
 
2230
                           "EOFError: {0}".format(e))
2202
2231
    
2203
 
    with Crypto() as crypt:
2204
 
        for client in clients_data:
2205
 
            client_name = client["name"]
2206
 
            
 
2232
    with PGPEngine() as pgp:
 
2233
        for client_name, client in clients_data.iteritems():
2207
2234
            # Decide which value to use after restoring saved state.
2208
2235
            # We have three different values: Old config file,
2209
2236
            # new config file, and saved state.
2217
2244
                    if (name != "secret" and
2218
2245
                        value != old_client_settings[client_name]
2219
2246
                        [name]):
2220
 
                        setattr(client, name, value)
 
2247
                        client[name] = value
2221
2248
                except KeyError:
2222
2249
                    pass
2223
2250
            
2224
2251
            # Clients who has passed its expire date can still be
2225
 
            # enabled if its last checker was sucessful.  Clients
 
2252
            # enabled if its last checker was successful.  Clients
2226
2253
            # whose checker failed before we stored its state is
2227
2254
            # assumed to have failed all checkers during downtime.
2228
 
            if client["enabled"] and client["last_checked_ok"]:
2229
 
                if ((datetime.datetime.utcnow()
2230
 
                     - client["last_checked_ok"])
2231
 
                    > client["interval"]):
2232
 
                    if client["last_checker_status"] != 0:
 
2255
            if client["enabled"]:
 
2256
                if datetime.datetime.utcnow() >= client["expires"]:
 
2257
                    if not client["last_checked_ok"]:
 
2258
                        logger.warning(
 
2259
                            "disabling client {0} - Client never "
 
2260
                            "performed a successfull checker"
 
2261
                            .format(client["name"]))
 
2262
                        client["enabled"] = False
 
2263
                    elif client["last_checker_status"] != 0:
 
2264
                        logger.warning(
 
2265
                            "disabling client {0} - Client "
 
2266
                            "last checker failed with error code {1}"
 
2267
                            .format(client["name"],
 
2268
                                    client["last_checker_status"]))
2233
2269
                        client["enabled"] = False
2234
2270
                    else:
2235
2271
                        client["expires"] = (datetime.datetime
2236
2272
                                             .utcnow()
2237
2273
                                             + client["timeout"])
2238
 
            
2239
 
            client["changedstate"] = (multiprocessing_manager
2240
 
                                      .Condition
2241
 
                                      (multiprocessing_manager
2242
 
                                       .Lock()))
2243
 
            if use_dbus:
2244
 
                new_client = (ClientDBusTransitional.__new__
2245
 
                              (ClientDBusTransitional))
2246
 
                tcp_server.clients[client_name] = new_client
2247
 
                new_client.bus = bus
2248
 
                for name, value in client.iteritems():
2249
 
                    setattr(new_client, name, value)
2250
 
                client_object_name = unicode(client_name).translate(
2251
 
                    {ord("."): ord("_"),
2252
 
                     ord("-"): ord("_")})
2253
 
                new_client.dbus_object_path = (dbus.ObjectPath
2254
 
                                               ("/clients/"
2255
 
                                                + client_object_name))
2256
 
                DBusObjectWithProperties.__init__(new_client,
2257
 
                                                  new_client.bus,
2258
 
                                                  new_client
2259
 
                                                  .dbus_object_path)
2260
 
            else:
2261
 
                tcp_server.clients[client_name] = (Client.__new__
2262
 
                                                   (Client))
2263
 
                for name, value in client.iteritems():
2264
 
                    setattr(tcp_server.clients[client_name],
2265
 
                            name, value)
2266
 
            
 
2274
                        logger.debug("Last checker succeeded,"
 
2275
                                     " keeping {0} enabled"
 
2276
                                     .format(client["name"]))
2267
2277
            try:
2268
 
                tcp_server.clients[client_name].secret = (
2269
 
                    crypt.decrypt(tcp_server.clients[client_name]
2270
 
                                  .encrypted_secret,
2271
 
                                  client_settings[client_name]
2272
 
                                  ["secret"]))
2273
 
            except CryptoError:
 
2278
                client["secret"] = (
 
2279
                    pgp.decrypt(client["encrypted_secret"],
 
2280
                                client_settings[client_name]
 
2281
                                ["secret"]))
 
2282
            except PGPError:
2274
2283
                # If decryption fails, we use secret from new settings
2275
 
                tcp_server.clients[client_name].secret = (
 
2284
                logger.debug("Failed to decrypt {0} old secret"
 
2285
                             .format(client_name))
 
2286
                client["secret"] = (
2276
2287
                    client_settings[client_name]["secret"])
 
2288
 
2277
2289
    
2278
 
    # Create/remove clients based on new changes made to config
2279
 
    for clientname in set(old_client_settings) - set(client_settings):
2280
 
        del tcp_server.clients[clientname]
2281
 
    for clientname in set(client_settings) - set(old_client_settings):
2282
 
        tcp_server.clients[clientname] = (client_class(name
2283
 
                                                       = clientname,
2284
 
                                                       config =
2285
 
                                                       client_settings
2286
 
                                                       [clientname]))
 
2290
    # Add/remove clients based on new changes made to config
 
2291
    for client_name in (set(old_client_settings)
 
2292
                        - set(client_settings)):
 
2293
        del clients_data[client_name]
 
2294
    for client_name in (set(client_settings)
 
2295
                        - set(old_client_settings)):
 
2296
        clients_data[client_name] = client_settings[client_name]
 
2297
 
 
2298
    # Create clients all clients
 
2299
    for client_name, client in clients_data.iteritems():
 
2300
        tcp_server.clients[client_name] = client_class(
 
2301
            name = client_name, settings = client)
2287
2302
    
2288
2303
    if not tcp_server.clients:
2289
2304
        logger.warning("No clients defined")
2301
2316
            # "pidfile" was never created
2302
2317
            pass
2303
2318
        del pidfilename
2304
 
        
2305
2319
        signal.signal(signal.SIGINT, signal.SIG_IGN)
2306
2320
    
2307
2321
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2376
2390
        # Store client before exiting. Secrets are encrypted with key
2377
2391
        # based on what config file has. If config file is
2378
2392
        # removed/edited, old secret will thus be unrecovable.
2379
 
        clients = []
2380
 
        with Crypto() as crypt:
 
2393
        clients = {}
 
2394
        with PGPEngine() as pgp:
2381
2395
            for client in tcp_server.clients.itervalues():
2382
2396
                key = client_settings[client.name]["secret"]
2383
 
                client.encrypted_secret = crypt.encrypt(client.secret,
2384
 
                                                        key)
 
2397
                client.encrypted_secret = pgp.encrypt(client.secret,
 
2398
                                                      key)
2385
2399
                client_dict = {}
2386
2400
                
2387
 
                # A list of attributes that will not be stored when
2388
 
                # shutting down.
2389
 
                exclude = set(("bus", "changedstate", "secret"))
 
2401
                # A list of attributes that can not be pickled
 
2402
                # + secret.
 
2403
                exclude = set(("bus", "changedstate", "secret",
 
2404
                               "checker"))
2390
2405
                for name, typ in (inspect.getmembers
2391
2406
                                  (dbus.service.Object)):
2392
2407
                    exclude.add(name)
2397
2412
                    if attr not in exclude:
2398
2413
                        client_dict[attr] = getattr(client, attr)
2399
2414
                
2400
 
                clients.append(client_dict)
 
2415
                clients[client.name] = client_dict
2401
2416
                del client_settings[client.name]["secret"]
2402
2417
        
2403
2418
        try:
2404
 
            with os.fdopen(os.open(stored_state_path,
2405
 
                                   os.O_CREAT|os.O_WRONLY|os.O_TRUNC,
2406
 
                                   0600), "wb") as stored_state:
 
2419
            tempfd, tempname = tempfile.mkstemp(suffix=".pickle",
 
2420
                                                prefix="clients-",
 
2421
                                                dir=os.path.dirname
 
2422
                                                (stored_state_path))
 
2423
            with os.fdopen(tempfd, "wb") as stored_state:
2407
2424
                pickle.dump((clients, client_settings), stored_state)
 
2425
            os.rename(tempname, stored_state_path)
2408
2426
        except (IOError, OSError) as e:
2409
2427
            logger.warning("Could not save persistent state: {0}"
2410
2428
                           .format(e))
2411
 
            if e.errno not in (errno.ENOENT, errno.EACCES):
2412
 
                raise
 
2429
            if not debug:
 
2430
                try:
 
2431
                    os.remove(tempname)
 
2432
                except NameError:
 
2433
                    pass
 
2434
            if e.errno not in set((errno.ENOENT, errno.EACCES,
 
2435
                                   errno.EEXIST)):
 
2436
                raise e
2413
2437
        
2414
2438
        # Delete all clients, and settings from config
2415
2439
        while tcp_server.clients:
2479
2503
    # Must run before the D-Bus bus name gets deregistered
2480
2504
    cleanup()
2481
2505
 
2482
 
 
2483
2506
if __name__ == '__main__':
2484
2507
    main()