/mandos/release

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/release

« back to all changes in this revision

Viewing changes to mandos

* mandos-ctl: Update copyright year to 2011.
* plugin-runner.c: - '' -
* plugins.d/password-prompt.c: - '' -

Show diffs side-by-side

added added

removed removed

Lines of Context:
63
63
import cPickle as pickle
64
64
import multiprocessing
65
65
import types
66
 
import hashlib
67
66
 
68
67
import dbus
69
68
import dbus.service
74
73
import ctypes.util
75
74
import xml.dom.minidom
76
75
import inspect
77
 
import Crypto.Cipher.AES
78
76
 
79
77
try:
80
78
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
85
83
        SO_BINDTODEVICE = None
86
84
 
87
85
 
88
 
version = "1.4.1"
89
 
 
90
 
logger = logging.getLogger()
91
 
stored_state_path = "/var/lib/mandos/clients.pickle"
92
 
 
 
86
version = "1.3.1"
 
87
 
 
88
#logger = logging.getLogger('mandos')
 
89
logger = logging.Logger('mandos')
93
90
syslogger = (logging.handlers.SysLogHandler
94
91
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
95
92
              address = str("/dev/log")))
99
96
logger.addHandler(syslogger)
100
97
 
101
98
console = logging.StreamHandler()
102
 
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
103
 
                                       ' [%(process)d]:'
 
99
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
104
100
                                       ' %(levelname)s:'
105
101
                                       ' %(message)s'))
106
102
logger.addHandler(console)
107
103
 
108
 
 
109
104
class AvahiError(Exception):
110
105
    def __init__(self, value, *args, **kwargs):
111
106
        self.value = value
169
164
                            .GetAlternativeServiceName(self.name))
170
165
        logger.info("Changing Zeroconf service name to %r ...",
171
166
                    self.name)
 
167
        syslogger.setFormatter(logging.Formatter
 
168
                               ('Mandos (%s) [%%(process)d]:'
 
169
                                ' %%(levelname)s: %%(message)s'
 
170
                                % self.name))
172
171
        self.remove()
173
172
        try:
174
173
            self.add()
194
193
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
195
194
        self.entry_group_state_changed_match = (
196
195
            self.group.connect_to_signal(
197
 
                'StateChanged', self.entry_group_state_changed))
 
196
                'StateChanged', self .entry_group_state_changed))
198
197
        logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
199
198
                     self.name, self.type)
200
199
        self.group.AddService(
266
265
                                 self.server_state_changed)
267
266
        self.server_state_changed(self.server.GetState())
268
267
 
269
 
class AvahiServiceToSyslog(AvahiService):
270
 
    def rename(self):
271
 
        """Add the new name to the syslog messages"""
272
 
        ret = AvahiService.rename(self)
273
 
        syslogger.setFormatter(logging.Formatter
274
 
                               ('Mandos (%s) [%%(process)d]:'
275
 
                                ' %%(levelname)s: %%(message)s'
276
 
                                % self.name))
277
 
        return ret
278
268
 
279
269
def _timedelta_to_milliseconds(td):
280
270
    "Convert a datetime.timedelta() to milliseconds"
299
289
                     instance %(name)s can be used in the command.
300
290
    checker_initiator_tag: a gobject event source tag, or None
301
291
    created:    datetime.datetime(); (UTC) object creation
302
 
    client_structure: Object describing what attributes a client has
303
 
                      and is used for storing the client at exit
304
292
    current_checker_command: string; current running checker_command
 
293
    disable_hook:  If set, called by disable() as disable_hook(self)
305
294
    disable_initiator_tag: a gobject event source tag, or None
306
295
    enabled:    bool()
307
296
    fingerprint: string (40 or 32 hexadecimal digits); used to
310
299
    interval:   datetime.timedelta(); How often to start a new checker
311
300
    last_approval_request: datetime.datetime(); (UTC) or None
312
301
    last_checked_ok: datetime.datetime(); (UTC) or None
313
 
    last_checker_status: integer between 0 and 255 reflecting exit status
314
 
                         of last checker. -1 reflect crashed checker,
315
 
                         or None.
316
302
    last_enabled: datetime.datetime(); (UTC)
317
303
    name:       string; from the config file, used in log messages and
318
304
                        D-Bus identifiers
345
331
    def approval_delay_milliseconds(self):
346
332
        return _timedelta_to_milliseconds(self.approval_delay)
347
333
    
348
 
    def __init__(self, name = None, config=None):
 
334
    def __init__(self, name = None, disable_hook=None, config=None):
349
335
        """Note: the 'checker' key in 'config' sets the
350
336
        'checker_command' attribute and *not* the 'checker'
351
337
        attribute."""
371
357
                            % self.name)
372
358
        self.host = config.get("host", "")
373
359
        self.created = datetime.datetime.utcnow()
374
 
        self.enabled = True
 
360
        self.enabled = False
375
361
        self.last_approval_request = None
376
 
        self.last_enabled = datetime.datetime.utcnow()
 
362
        self.last_enabled = None
377
363
        self.last_checked_ok = None
378
 
        self.last_checker_status = None
379
364
        self.timeout = string_to_delta(config["timeout"])
380
365
        self.extended_timeout = string_to_delta(config
381
366
                                                ["extended_timeout"])
382
367
        self.interval = string_to_delta(config["interval"])
 
368
        self.disable_hook = disable_hook
383
369
        self.checker = None
384
370
        self.checker_initiator_tag = None
385
371
        self.disable_initiator_tag = None
386
 
        self.expires = datetime.datetime.utcnow() + self.timeout
 
372
        self.expires = None
387
373
        self.checker_callback_tag = None
388
374
        self.checker_command = config["checker"]
389
375
        self.current_checker_command = None
 
376
        self.last_connect = None
390
377
        self._approved = None
391
378
        self.approved_by_default = config.get("approved_by_default",
392
379
                                              True)
398
385
        self.changedstate = (multiprocessing_manager
399
386
                             .Condition(multiprocessing_manager
400
387
                                        .Lock()))
401
 
        self.client_structure = [attr for attr in self.__dict__.iterkeys() if not attr.startswith("_")]
402
 
        self.client_structure.append("client_structure")
403
 
 
404
 
 
405
 
        for name, t in inspect.getmembers(type(self),
406
 
                                          lambda obj: isinstance(obj, property)):
407
 
            if not name.startswith("_"):
408
 
                self.client_structure.append(name)
409
388
    
410
 
    # Send notice to process children that client state has changed
411
389
    def send_changedstate(self):
412
 
        with self.changedstate:
413
 
            self.changedstate.notify_all()
 
390
        self.changedstate.acquire()
 
391
        self.changedstate.notify_all()
 
392
        self.changedstate.release()
414
393
    
415
394
    def enable(self):
416
395
        """Start this client's checker and timeout hooks"""
418
397
            # Already enabled
419
398
            return
420
399
        self.send_changedstate()
 
400
        # Schedule a new checker to be started an 'interval' from now,
 
401
        # and every interval from then on.
 
402
        self.checker_initiator_tag = (gobject.timeout_add
 
403
                                      (self.interval_milliseconds(),
 
404
                                       self.start_checker))
 
405
        # Schedule a disable() when 'timeout' has passed
421
406
        self.expires = datetime.datetime.utcnow() + self.timeout
 
407
        self.disable_initiator_tag = (gobject.timeout_add
 
408
                                   (self.timeout_milliseconds(),
 
409
                                    self.disable))
422
410
        self.enabled = True
423
411
        self.last_enabled = datetime.datetime.utcnow()
424
 
        self.init_checker()
 
412
        # Also start a new checker *right now*.
 
413
        self.start_checker()
425
414
    
426
415
    def disable(self, quiet=True):
427
416
        """Disable this client."""
439
428
            gobject.source_remove(self.checker_initiator_tag)
440
429
            self.checker_initiator_tag = None
441
430
        self.stop_checker()
 
431
        if self.disable_hook:
 
432
            self.disable_hook(self)
442
433
        self.enabled = False
443
434
        # Do not run this again if called by a gobject.timeout_add
444
435
        return False
445
436
    
446
437
    def __del__(self):
 
438
        self.disable_hook = None
447
439
        self.disable()
448
 
 
449
 
    def init_checker(self):
450
 
        # Schedule a new checker to be started an 'interval' from now,
451
 
        # and every interval from then on.
452
 
        self.checker_initiator_tag = (gobject.timeout_add
453
 
                                      (self.interval_milliseconds(),
454
 
                                       self.start_checker))
455
 
        # Schedule a disable() when 'timeout' has passed
456
 
        self.disable_initiator_tag = (gobject.timeout_add
457
 
                                   (self.timeout_milliseconds(),
458
 
                                    self.disable))
459
 
        # Also start a new checker *right now*.
460
 
        self.start_checker()
461
 
 
462
 
        
 
440
    
463
441
    def checker_callback(self, pid, condition, command):
464
442
        """The checker has completed, so take appropriate actions."""
465
443
        self.checker_callback_tag = None
466
444
        self.checker = None
467
445
        if os.WIFEXITED(condition):
468
 
            self.last_checker_status =  os.WEXITSTATUS(condition)
469
 
            if self.last_checker_status == 0:
 
446
            exitstatus = os.WEXITSTATUS(condition)
 
447
            if exitstatus == 0:
470
448
                logger.info("Checker for %(name)s succeeded",
471
449
                            vars(self))
472
450
                self.checked_ok()
474
452
                logger.info("Checker for %(name)s failed",
475
453
                            vars(self))
476
454
        else:
477
 
            self.last_checker_status = -1
478
455
            logger.warning("Checker for %(name)s crashed?",
479
456
                           vars(self))
480
457
    
487
464
        if timeout is None:
488
465
            timeout = self.timeout
489
466
        self.last_checked_ok = datetime.datetime.utcnow()
490
 
        if self.disable_initiator_tag is not None:
491
 
            gobject.source_remove(self.disable_initiator_tag)
492
 
        if getattr(self, "enabled", False):
493
 
            self.disable_initiator_tag = (gobject.timeout_add
494
 
                                          (_timedelta_to_milliseconds
495
 
                                           (timeout), self.disable))
496
 
            self.expires = datetime.datetime.utcnow() + timeout
 
467
        gobject.source_remove(self.disable_initiator_tag)
 
468
        self.expires = datetime.datetime.utcnow() + timeout
 
469
        self.disable_initiator_tag = (gobject.timeout_add
 
470
                                      (_timedelta_to_milliseconds
 
471
                                       (timeout), self.disable))
497
472
    
498
473
    def need_approval(self):
499
474
        self.last_approval_request = datetime.datetime.utcnow()
591
566
                raise
592
567
        self.checker = None
593
568
 
594
 
    # Encrypts a client secret and stores it in a varible encrypted_secret
595
 
    def encrypt_secret(self, key):
596
 
        # Encryption-key need to be of a specific size, so we hash inputed key
597
 
        hasheng = hashlib.sha256()
598
 
        hasheng.update(key)
599
 
        encryptionkey = hasheng.digest()
600
 
 
601
 
        # Create validation hash so we know at decryption if it was sucessful
602
 
        hasheng = hashlib.sha256()
603
 
        hasheng.update(self.secret)
604
 
        validationhash = hasheng.digest()
605
 
 
606
 
        # Encrypt secret
607
 
        iv = os.urandom(Crypto.Cipher.AES.block_size)
608
 
        ciphereng = Crypto.Cipher.AES.new(encryptionkey,
609
 
                                        Crypto.Cipher.AES.MODE_CFB, iv)
610
 
        ciphertext = ciphereng.encrypt(validationhash+self.secret)
611
 
        self.encrypted_secret = (ciphertext, iv)
612
 
 
613
 
    # Decrypt a encrypted client secret
614
 
    def decrypt_secret(self, key):
615
 
        # Decryption-key need to be of a specific size, so we hash inputed key
616
 
        hasheng = hashlib.sha256()
617
 
        hasheng.update(key)
618
 
        encryptionkey = hasheng.digest()
619
 
 
620
 
        # Decrypt encrypted secret
621
 
        ciphertext, iv = self.encrypted_secret
622
 
        ciphereng = Crypto.Cipher.AES.new(encryptionkey,
623
 
                                        Crypto.Cipher.AES.MODE_CFB, iv)
624
 
        plain = ciphereng.decrypt(ciphertext)
625
 
 
626
 
        # Validate decrypted secret to know if it was succesful
627
 
        hasheng = hashlib.sha256()
628
 
        validationhash = plain[:hasheng.digest_size]
629
 
        secret = plain[hasheng.digest_size:]
630
 
        hasheng.update(secret)
631
 
 
632
 
        # if validation fails, we use key as new secret. Otherwhise, we use
633
 
        # the decrypted secret
634
 
        if hasheng.digest() == validationhash:
635
 
            self.secret = secret
636
 
        else:
637
 
            self.secret = key
638
 
        del self.encrypted_secret
639
 
 
640
569
 
641
570
def dbus_service_property(dbus_interface, signature="v",
642
571
                          access="readwrite", byte_arrays=False):
943
872
    # dbus.service.Object doesn't use super(), so we can't either.
944
873
    
945
874
    def __init__(self, bus = None, *args, **kwargs):
 
875
        self._approvals_pending = 0
946
876
        self.bus = bus
947
877
        Client.__init__(self, *args, **kwargs)
948
 
 
949
 
        self._approvals_pending = 0
950
878
        # Only now, when this client is initialized, can it show up on
951
879
        # the D-Bus
952
880
        client_object_name = unicode(self.name).translate(
963
891
        """ Modify a variable so that it's a property which announces
964
892
        its changes to DBus.
965
893
 
966
 
        transform_fun: Function that takes a value and a variant_level
967
 
                       and transforms it to a D-Bus type.
 
894
        transform_fun: Function that takes a value and transforms it
 
895
                       to a D-Bus type.
968
896
        dbus_name: D-Bus name of the variable
969
897
        type_func: Function that transform the value before sending it
970
898
                   to the D-Bus.  Default: no transform
977
905
                    type_func(getattr(self, attrname, None))
978
906
                    != type_func(value)):
979
907
                    dbus_value = transform_func(type_func(value),
980
 
                                                variant_level
981
 
                                                =variant_level)
 
908
                                                variant_level)
982
909
                    self.PropertyChanged(dbus.String(dbus_name),
983
910
                                         dbus_value)
984
911
            setattr(self, attrname, value)
1260
1187
        gobject.source_remove(self.disable_initiator_tag)
1261
1188
        self.disable_initiator_tag = None
1262
1189
        self.expires = None
1263
 
        time_to_die = _timedelta_to_milliseconds((self
1264
 
                                                  .last_checked_ok
1265
 
                                                  + self.timeout)
1266
 
                                                 - datetime.datetime
1267
 
                                                 .utcnow())
 
1190
        time_to_die = (self.
 
1191
                       _timedelta_to_milliseconds((self
 
1192
                                                   .last_checked_ok
 
1193
                                                   + self.timeout)
 
1194
                                                  - datetime.datetime
 
1195
                                                  .utcnow()))
1268
1196
        if time_to_die <= 0:
1269
1197
            # The timeout has passed
1270
1198
            self.disable()
1466
1394
                        return
1467
1395
                    
1468
1396
                    #wait until timeout or approved
 
1397
                    #x = float(client
 
1398
                    #          ._timedelta_to_milliseconds(delay))
1469
1399
                    time = datetime.datetime.now()
1470
1400
                    client.changedstate.acquire()
1471
1401
                    (client.changedstate.wait
1500
1430
                    sent_size += sent
1501
1431
                
1502
1432
                logger.info("Sending secret to %s", client.name)
1503
 
                # bump the timeout using extended_timeout
 
1433
                # bump the timeout as if seen
1504
1434
                client.checked_ok(client.extended_timeout)
1505
1435
                if self.server.use_dbus:
1506
1436
                    # Emit D-Bus signal
1586
1516
        except:
1587
1517
            self.handle_error(request, address)
1588
1518
        self.close_request(request)
1589
 
    
 
1519
            
1590
1520
    def process_request(self, request, address):
1591
1521
        """Start a new process to process the request."""
1592
 
        proc = multiprocessing.Process(target = self.sub_process_main,
1593
 
                                       args = (request,
1594
 
                                               address))
1595
 
        proc.start()
1596
 
        return proc
 
1522
        multiprocessing.Process(target = self.sub_process_main,
 
1523
                                args = (request, address)).start()
1597
1524
 
1598
1525
 
1599
1526
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1605
1532
        """
1606
1533
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
1607
1534
        
1608
 
        proc = MultiprocessingMixIn.process_request(self, request,
1609
 
                                                    client_address)
 
1535
        super(MultiprocessingMixInWithPipe,
 
1536
              self).process_request(request, client_address)
1610
1537
        self.child_pipe.close()
1611
 
        self.add_pipe(parent_pipe, proc)
 
1538
        self.add_pipe(parent_pipe)
1612
1539
    
1613
 
    def add_pipe(self, parent_pipe, proc):
 
1540
    def add_pipe(self, parent_pipe):
1614
1541
        """Dummy function; override as necessary"""
1615
1542
        raise NotImplementedError
1616
1543
 
1694
1621
        self.enabled = False
1695
1622
        self.clients = clients
1696
1623
        if self.clients is None:
1697
 
            self.clients = {}
 
1624
            self.clients = set()
1698
1625
        self.use_dbus = use_dbus
1699
1626
        self.gnutls_priority = gnutls_priority
1700
1627
        IPv6_TCPServer.__init__(self, server_address,
1704
1631
    def server_activate(self):
1705
1632
        if self.enabled:
1706
1633
            return socketserver.TCPServer.server_activate(self)
1707
 
    
1708
1634
    def enable(self):
1709
1635
        self.enabled = True
1710
 
    
1711
 
    def add_pipe(self, parent_pipe, proc):
 
1636
    def add_pipe(self, parent_pipe):
1712
1637
        # Call "handle_ipc" for both data and EOF events
1713
1638
        gobject.io_add_watch(parent_pipe.fileno(),
1714
1639
                             gobject.IO_IN | gobject.IO_HUP,
1715
1640
                             functools.partial(self.handle_ipc,
1716
1641
                                               parent_pipe =
1717
 
                                               parent_pipe,
1718
 
                                               proc = proc))
1719
 
    
 
1642
                                               parent_pipe))
 
1643
        
1720
1644
    def handle_ipc(self, source, condition, parent_pipe=None,
1721
 
                   proc = None, client_object=None):
 
1645
                   client_object=None):
1722
1646
        condition_names = {
1723
1647
            gobject.IO_IN: "IN",   # There is data to read.
1724
1648
            gobject.IO_OUT: "OUT", # Data can be written (without
1733
1657
                                       for cond, name in
1734
1658
                                       condition_names.iteritems()
1735
1659
                                       if cond & condition)
1736
 
        # error, or the other end of multiprocessing.Pipe has closed
 
1660
        # error or the other end of multiprocessing.Pipe has closed
1737
1661
        if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1738
 
            # Wait for other process to exit
1739
 
            proc.join()
1740
1662
            return False
1741
1663
        
1742
1664
        # Read a request from the child
1747
1669
            fpr = request[1]
1748
1670
            address = request[2]
1749
1671
            
1750
 
            for c in self.clients.itervalues():
 
1672
            for c in self.clients:
1751
1673
                if c.fingerprint == fpr:
1752
1674
                    client = c
1753
1675
                    break
1766
1688
                                 functools.partial(self.handle_ipc,
1767
1689
                                                   parent_pipe =
1768
1690
                                                   parent_pipe,
1769
 
                                                   proc = proc,
1770
1691
                                                   client_object =
1771
1692
                                                   client))
1772
1693
            parent_pipe.send(True)
1921
1842
                        " system bus interface")
1922
1843
    parser.add_argument("--no-ipv6", action="store_false",
1923
1844
                        dest="use_ipv6", help="Do not use IPv6")
1924
 
    parser.add_argument("--no-restore", action="store_false",
1925
 
                        dest="restore", help="Do not restore stored state",
1926
 
                        default=True)
1927
 
 
1928
1845
    options = parser.parse_args()
1929
1846
    
1930
1847
    if options.check:
1965
1882
    # options, if set.
1966
1883
    for option in ("interface", "address", "port", "debug",
1967
1884
                   "priority", "servicename", "configdir",
1968
 
                   "use_dbus", "use_ipv6", "debuglevel", "restore"):
 
1885
                   "use_dbus", "use_ipv6", "debuglevel"):
1969
1886
        value = getattr(options, option)
1970
1887
        if value is not None:
1971
1888
            server_settings[option] = value
2044
1961
            raise error
2045
1962
    
2046
1963
    if not debug and not debuglevel:
2047
 
        logger.setLevel(logging.WARNING)
 
1964
        syslogger.setLevel(logging.WARNING)
 
1965
        console.setLevel(logging.WARNING)
2048
1966
    if debuglevel:
2049
1967
        level = getattr(logging, debuglevel.upper())
2050
 
        logger.setLevel(level)
 
1968
        syslogger.setLevel(level)
 
1969
        console.setLevel(level)
2051
1970
    
2052
1971
    if debug:
2053
 
        logger.setLevel(logging.DEBUG)
2054
1972
        # Enable all possible GnuTLS debugging
2055
1973
        
2056
1974
        # "Use a log level over 10 to enable all debugging options."
2097
2015
            server_settings["use_dbus"] = False
2098
2016
            tcp_server.use_dbus = False
2099
2017
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2100
 
    service = AvahiServiceToSyslog(name =
2101
 
                                   server_settings["servicename"],
2102
 
                                   servicetype = "_mandos._tcp",
2103
 
                                   protocol = protocol, bus = bus)
 
2018
    service = AvahiService(name = server_settings["servicename"],
 
2019
                           servicetype = "_mandos._tcp",
 
2020
                           protocol = protocol, bus = bus)
2104
2021
    if server_settings["interface"]:
2105
2022
        service.interface = (if_nametoindex
2106
2023
                             (str(server_settings["interface"])))
2112
2029
    if use_dbus:
2113
2030
        client_class = functools.partial(ClientDBusTransitional,
2114
2031
                                         bus = bus)
2115
 
    
2116
 
    special_settings = {
2117
 
        # Some settings need to be accessd by special methods;
2118
 
        # booleans need .getboolean(), etc.  Here is a list of them:
2119
 
        "approved_by_default":
2120
 
            lambda section:
2121
 
            client_config.getboolean(section, "approved_by_default"),
2122
 
        }
2123
 
    # Construct a new dict of client settings of this form:
2124
 
    # { client_name: {setting_name: value, ...}, ...}
2125
 
    # with exceptions for any special settings as defined above
2126
 
    client_settings = dict((clientname,
2127
 
                           dict((setting,
2128
 
                                 (value if setting not in special_settings
2129
 
                                  else special_settings[setting](clientname)))
2130
 
                                for setting, value in client_config.items(clientname)))
2131
 
                          for clientname in client_config.sections())
2132
 
    
2133
 
    old_client_settings = {}
2134
 
    clients_data = []
2135
 
 
2136
 
    # Get client data and settings from last running state. 
2137
 
    if server_settings["restore"]:
2138
 
        try:
2139
 
            with open(stored_state_path, "rb") as stored_state:
2140
 
                clients_data, old_client_settings = pickle.load(stored_state)
2141
 
            os.remove(stored_state_path)
2142
 
        except IOError as e:
2143
 
            logger.warning("Could not load persistant state: {0}".format(e))
2144
 
            if e.errno != errno.ENOENT:
2145
 
                raise
2146
 
 
2147
 
    for client in clients_data:
2148
 
        client_name = client["name"]
2149
 
        
2150
 
        # Decide which value to use after restoring saved state.
2151
 
        # We have three different values: Old config file,
2152
 
        # new config file, and saved state.
2153
 
        # New config value takes precedence if it differs from old
2154
 
        # config value, otherwise use saved state.
2155
 
        for name, value in client_settings[client_name].items():
 
2032
    def client_config_items(config, section):
 
2033
        special_settings = {
 
2034
            "approved_by_default":
 
2035
                lambda: config.getboolean(section,
 
2036
                                          "approved_by_default"),
 
2037
            }
 
2038
        for name, value in config.items(section):
2156
2039
            try:
2157
 
                # For each value in new config, check if it differs
2158
 
                # from the old config value (Except for the "secret"
2159
 
                # attribute)
2160
 
                if name != "secret" and value != old_client_settings[client_name][name]:
2161
 
                    setattr(client, name, value)
 
2040
                yield (name, special_settings[name]())
2162
2041
            except KeyError:
2163
 
                pass
2164
 
 
2165
 
        # Clients who has passed its expire date, can still be enabled if its
2166
 
        # last checker was sucessful. Clients who checkers failed before we
2167
 
        # stored it state is asumed to had failed checker during downtime.
2168
 
        if client["enabled"] and client["last_checked_ok"]:
2169
 
            if ((datetime.datetime.utcnow() - client["last_checked_ok"])
2170
 
                > client["interval"]):
2171
 
                if client["last_checker_status"] != 0:
2172
 
                    client["enabled"] = False
2173
 
                else:
2174
 
                    client["expires"] = datetime.datetime.utcnow() + client["timeout"]
2175
 
 
2176
 
        client["changedstate"] = (multiprocessing_manager
2177
 
                                  .Condition(multiprocessing_manager
2178
 
                                             .Lock()))
2179
 
        if use_dbus:
2180
 
            new_client = ClientDBusTransitional.__new__(ClientDBusTransitional)
2181
 
            tcp_server.clients[client_name] = new_client
2182
 
            new_client.bus = bus
2183
 
            for name, value in client.iteritems():
2184
 
                setattr(new_client, name, value)
2185
 
            client_object_name = unicode(client_name).translate(
2186
 
                {ord("."): ord("_"),
2187
 
                 ord("-"): ord("_")})
2188
 
            new_client.dbus_object_path = (dbus.ObjectPath
2189
 
                                     ("/clients/" + client_object_name))
2190
 
            DBusObjectWithProperties.__init__(new_client,
2191
 
                                              new_client.bus,
2192
 
                                              new_client.dbus_object_path)
2193
 
        else:
2194
 
            tcp_server.clients[client_name] = Client.__new__(Client)
2195
 
            for name, value in client.iteritems():
2196
 
                setattr(tcp_server.clients[client_name], name, value)
2197
 
                
2198
 
        tcp_server.clients[client_name].decrypt_secret(
2199
 
            client_settings[client_name]["secret"])            
2200
 
        
2201
 
    # Create/remove clients based on new changes made to config
2202
 
    for clientname in set(old_client_settings) - set(client_settings):
2203
 
        del tcp_server.clients[clientname]
2204
 
    for clientname in set(client_settings) - set(old_client_settings):
2205
 
        tcp_server.clients[clientname] = (client_class(name = clientname,
2206
 
                                                       config =
2207
 
                                                       client_settings
2208
 
                                                       [clientname]))
 
2042
                yield (name, value)
2209
2043
    
2210
 
 
 
2044
    tcp_server.clients.update(set(
 
2045
            client_class(name = section,
 
2046
                         config= dict(client_config_items(
 
2047
                        client_config, section)))
 
2048
            for section in client_config.sections()))
2211
2049
    if not tcp_server.clients:
2212
2050
        logger.warning("No clients defined")
2213
2051
        
2256
2094
            def GetAllClients(self):
2257
2095
                "D-Bus method"
2258
2096
                return dbus.Array(c.dbus_object_path
2259
 
                                  for c in
2260
 
                                  tcp_server.clients.itervalues())
 
2097
                                  for c in tcp_server.clients)
2261
2098
            
2262
2099
            @dbus.service.method(_interface,
2263
2100
                                 out_signature="a{oa{sv}}")
2265
2102
                "D-Bus method"
2266
2103
                return dbus.Dictionary(
2267
2104
                    ((c.dbus_object_path, c.GetAll(""))
2268
 
                     for c in tcp_server.clients.itervalues()),
 
2105
                     for c in tcp_server.clients),
2269
2106
                    signature="oa{sv}")
2270
2107
            
2271
2108
            @dbus.service.method(_interface, in_signature="o")
2272
2109
            def RemoveClient(self, object_path):
2273
2110
                "D-Bus method"
2274
 
                for c in tcp_server.clients.itervalues():
 
2111
                for c in tcp_server.clients:
2275
2112
                    if c.dbus_object_path == object_path:
2276
 
                        del tcp_server.clients[c.name]
 
2113
                        tcp_server.clients.remove(c)
2277
2114
                        c.remove_from_connection()
2278
2115
                        # Don't signal anything except ClientRemoved
2279
2116
                        c.disable(quiet=True)
2292
2129
        "Cleanup function; run on exit"
2293
2130
        service.cleanup()
2294
2131
        
2295
 
        multiprocessing.active_children()
2296
 
        if not (tcp_server.clients or client_settings):
2297
 
            return
2298
 
 
2299
 
        # Store client before exiting. Secrets are encrypted with key based
2300
 
        # on what config file has. If config file is removed/edited, old
2301
 
        # secret will thus be unrecovable.
2302
 
        clients = []
2303
 
        for client in tcp_server.clients.itervalues():
2304
 
            client.encrypt_secret(client_settings[client.name]["secret"])
2305
 
 
2306
 
            client_dict = {}
2307
 
 
2308
 
            # A list of attributes that will not be stored when shuting down.
2309
 
            exclude = set(("bus", "changedstate", "secret"))            
2310
 
            for name, typ in inspect.getmembers(dbus.service.Object):
2311
 
                exclude.add(name)
2312
 
                
2313
 
            client_dict["encrypted_secret"] = client.encrypted_secret
2314
 
            for attr in client.client_structure:
2315
 
                if attr not in exclude:
2316
 
                    client_dict[attr] = getattr(client, attr)
2317
 
 
2318
 
            clients.append(client_dict) 
2319
 
            del client_settings[client.name]["secret"]
2320
 
            
2321
 
        try:
2322
 
            with os.fdopen(os.open(stored_state_path, os.O_CREAT|os.O_WRONLY|os.O_TRUNC, 0600), "wb") as stored_state:
2323
 
                pickle.dump((clients, client_settings), stored_state)
2324
 
        except IOError as e:
2325
 
            logger.warning("Could not save persistant state: {0}".format(e))
2326
 
            if e.errno != errno.ENOENT:
2327
 
                raise
2328
 
 
2329
 
        # Delete all clients, and settings from config
2330
2132
        while tcp_server.clients:
2331
 
            name, client = tcp_server.clients.popitem()
 
2133
            client = tcp_server.clients.pop()
2332
2134
            if use_dbus:
2333
2135
                client.remove_from_connection()
 
2136
            client.disable_hook = None
2334
2137
            # Don't signal anything except ClientRemoved
2335
2138
            client.disable(quiet=True)
2336
2139
            if use_dbus:
2338
2141
                mandos_dbus_service.ClientRemoved(client
2339
2142
                                                  .dbus_object_path,
2340
2143
                                                  client.name)
2341
 
        client_settings.clear()
2342
2144
    
2343
2145
    atexit.register(cleanup)
2344
2146
    
2345
 
    for client in tcp_server.clients.itervalues():
 
2147
    for client in tcp_server.clients:
2346
2148
        if use_dbus:
2347
2149
            # Emit D-Bus signal
2348
2150
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
2349
 
        # Need to initiate checking of clients
2350
 
        if client.enabled:
2351
 
            client.init_checker()
2352
 
 
 
2151
        client.enable()
2353
2152
    
2354
2153
    tcp_server.enable()
2355
2154
    tcp_server.server_activate()