/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-26 22:22:20 UTC
  • mto: (518.1.8 mandos-persistent)
  • mto: This revision was merged to the branch mainline in revision 524.
  • Revision ID: teddy@recompile.se-20111126222220-1ubwjpb5ugqnrhec
Directory with persistent state can now be changed with the "statedir"
option.  The state directory /var/lib/mandos now gets created on
installation.  Added documentation about "restore" and "statedir"
options.

* Makefile (USER, GROUP, STATEDIR): New.
  (maintainer-clean): Also remove "statedir".
  (run-server): Replaced "--no-restore" with "--statedir=statedir".
  (statedir): New.
  (install-server): Make $(STATEDIR) directory.
* debian/mandos.dirs (var/lib/mandos): Added.
* debian/mandos.postinst: Fix ownership of /var/lib/mandos.
* mandos: New --statedir option.
  (stored_state_path): Not global anymore.
  (stored_state_file): New global.
* mandos.conf: Fix whitespace.
  (restore, statedir): Added.
* mandos.conf.xml (OPTIONS, EXAMPLE): Added "restore" and "statedir".
  mandos.xml (SYNOPSIS, OPTIONS): Added "--statedir".
  (FILES): Added "/var/lib/mandos".

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