/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: Björn Påhlsson
  • Date: 2011-11-09 17:16:03 UTC
  • mfrom: (518.1.1 mandos-persistent)
  • Revision ID: belorn@fukt.bsnet.se-20111109171603-srz21uoclpldp5ve
merge persistent state

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 binascii
67
 
import tempfile
 
66
import hashlib
68
67
 
69
68
import dbus
70
69
import dbus.service
75
74
import ctypes.util
76
75
import xml.dom.minidom
77
76
import inspect
78
 
import GnuPGInterface
 
77
import Crypto.Cipher.AES
79
78
 
80
79
try:
81
80
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
87
86
 
88
87
 
89
88
version = "1.4.1"
90
 
stored_state_file = "clients.pickle"
91
89
 
92
90
logger = logging.getLogger()
 
91
stored_state_path = "/var/lib/mandos/clients.pickle"
 
92
 
93
93
syslogger = (logging.handlers.SysLogHandler
94
94
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
95
95
              address = str("/dev/log")))
96
 
 
97
 
try:
98
 
    if_nametoindex = (ctypes.cdll.LoadLibrary
99
 
                      (ctypes.util.find_library("c"))
100
 
                      .if_nametoindex)
101
 
except (OSError, AttributeError):
102
 
    def if_nametoindex(interface):
103
 
        "Get an interface index the hard way, i.e. using fcntl()"
104
 
        SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
105
 
        with contextlib.closing(socket.socket()) as s:
106
 
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
107
 
                                struct.pack(str("16s16x"),
108
 
                                            interface))
109
 
        interface_index = struct.unpack(str("I"),
110
 
                                        ifreq[16:20])[0]
111
 
        return interface_index
112
 
 
113
 
 
114
 
def initlogger(level=logging.WARNING):
115
 
    """init logger and add loglevel"""
116
 
    
117
 
    syslogger.setFormatter(logging.Formatter
118
 
                           ('Mandos [%(process)d]: %(levelname)s:'
119
 
                            ' %(message)s'))
120
 
    logger.addHandler(syslogger)
121
 
    
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
 
    logger.setLevel(level)
129
 
 
130
 
 
131
 
class CryptoError(Exception):
132
 
    pass
133
 
 
134
 
 
135
 
class Crypto(object):
136
 
    """A simple class for OpenPGP symmetric encryption & decryption"""
137
 
    def __init__(self):
138
 
        self.gnupg = GnuPGInterface.GnuPG()
139
 
        self.tempdir = tempfile.mkdtemp(prefix="mandos-")
140
 
        self.gnupg = GnuPGInterface.GnuPG()
141
 
        self.gnupg.options.meta_interactive = False
142
 
        self.gnupg.options.homedir = self.tempdir
143
 
        self.gnupg.options.extra_args.extend(['--force-mdc',
144
 
                                              '--quiet'])
145
 
    
146
 
    def __enter__(self):
147
 
        return self
148
 
    
149
 
    def __exit__ (self, exc_type, exc_value, traceback):
150
 
        self._cleanup()
151
 
        return False
152
 
    
153
 
    def __del__(self):
154
 
        self._cleanup()
155
 
    
156
 
    def _cleanup(self):
157
 
        if self.tempdir is not None:
158
 
            # Delete contents of tempdir
159
 
            for root, dirs, files in os.walk(self.tempdir,
160
 
                                             topdown = False):
161
 
                for filename in files:
162
 
                    os.remove(os.path.join(root, filename))
163
 
                for dirname in dirs:
164
 
                    os.rmdir(os.path.join(root, dirname))
165
 
            # Remove tempdir
166
 
            os.rmdir(self.tempdir)
167
 
            self.tempdir = None
168
 
    
169
 
    def password_encode(self, password):
170
 
        # Passphrase can not be empty and can not contain newlines or
171
 
        # NUL bytes.  So we prefix it and hex encode it.
172
 
        return b"mandos" + binascii.hexlify(password)
173
 
    
174
 
    def encrypt(self, data, password):
175
 
        self.gnupg.passphrase = self.password_encode(password)
176
 
        with open(os.devnull) as devnull:
177
 
            try:
178
 
                proc = self.gnupg.run(['--symmetric'],
179
 
                                      create_fhs=['stdin', 'stdout'],
180
 
                                      attach_fhs={'stderr': devnull})
181
 
                with contextlib.closing(proc.handles['stdin']) as f:
182
 
                    f.write(data)
183
 
                with contextlib.closing(proc.handles['stdout']) as f:
184
 
                    ciphertext = f.read()
185
 
                proc.wait()
186
 
            except IOError as e:
187
 
                raise CryptoError(e)
188
 
        self.gnupg.passphrase = None
189
 
        return ciphertext
190
 
    
191
 
    def decrypt(self, data, password):
192
 
        self.gnupg.passphrase = self.password_encode(password)
193
 
        with open(os.devnull) as devnull:
194
 
            try:
195
 
                proc = self.gnupg.run(['--decrypt'],
196
 
                                      create_fhs=['stdin', 'stdout'],
197
 
                                      attach_fhs={'stderr': devnull})
198
 
                with contextlib.closing(proc.handles['stdin'] ) as f:
199
 
                    f.write(data)
200
 
                with contextlib.closing(proc.handles['stdout']) as f:
201
 
                    decrypted_plaintext = f.read()
202
 
                proc.wait()
203
 
            except IOError as e:
204
 
                raise CryptoError(e)
205
 
        self.gnupg.passphrase = None
206
 
        return decrypted_plaintext
207
 
 
 
96
syslogger.setFormatter(logging.Formatter
 
97
                       ('Mandos [%(process)d]: %(levelname)s:'
 
98
                        ' %(message)s'))
 
99
logger.addHandler(syslogger)
 
100
 
 
101
console = logging.StreamHandler()
 
102
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
 
103
                                       ' [%(process)d]:'
 
104
                                       ' %(levelname)s:'
 
105
                                       ' %(message)s'))
 
106
logger.addHandler(console)
208
107
 
209
108
 
210
109
class AvahiError(Exception):
327
226
            try:
328
227
                self.group.Free()
329
228
            except (dbus.exceptions.UnknownMethodException,
330
 
                    dbus.exceptions.DBusException):
 
229
                    dbus.exceptions.DBusException) as e:
331
230
                pass
332
231
            self.group = None
333
232
        self.remove()
411
310
    interval:   datetime.timedelta(); How often to start a new checker
412
311
    last_approval_request: datetime.datetime(); (UTC) or None
413
312
    last_checked_ok: datetime.datetime(); (UTC) or None
414
 
 
415
 
    last_checker_status: integer between 0 and 255 reflecting exit
416
 
                         status of last checker. -1 reflects crashed
417
 
                         checker, 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.
418
316
    last_enabled: datetime.datetime(); (UTC)
419
317
    name:       string; from the config file, used in log messages and
420
318
                        D-Bus identifiers
500
398
        self.changedstate = (multiprocessing_manager
501
399
                             .Condition(multiprocessing_manager
502
400
                                        .Lock()))
503
 
        self.client_structure = [attr for attr in
504
 
                                 self.__dict__.iterkeys()
505
 
                                 if not attr.startswith("_")]
 
401
        self.client_structure = [attr for attr in self.__dict__.iterkeys() if not attr.startswith("_")]
506
402
        self.client_structure.append("client_structure")
507
 
        
 
403
 
 
404
 
508
405
        for name, t in inspect.getmembers(type(self),
509
 
                                          lambda obj:
510
 
                                              isinstance(obj,
511
 
                                                         property)):
 
406
                                          lambda obj: isinstance(obj, property)):
512
407
            if not name.startswith("_"):
513
408
                self.client_structure.append(name)
514
409
    
550
445
    
551
446
    def __del__(self):
552
447
        self.disable()
553
 
    
 
448
 
554
449
    def init_checker(self):
555
450
        # Schedule a new checker to be started an 'interval' from now,
556
451
        # and every interval from then on.
563
458
                                    self.disable))
564
459
        # Also start a new checker *right now*.
565
460
        self.start_checker()
566
 
    
 
461
 
 
462
        
567
463
    def checker_callback(self, pid, condition, command):
568
464
        """The checker has completed, so take appropriate actions."""
569
465
        self.checker_callback_tag = None
695
591
                raise
696
592
        self.checker = None
697
593
 
 
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
 
698
640
 
699
641
def dbus_service_property(dbus_interface, signature="v",
700
642
                          access="readwrite", byte_arrays=False):
818
760
        
819
761
        Note: Will not include properties with access="write".
820
762
        """
821
 
        properties = {}
 
763
        all = {}
822
764
        for name, prop in self._get_all_dbus_properties():
823
765
            if (interface_name
824
766
                and interface_name != prop._dbus_interface):
829
771
                continue
830
772
            value = prop()
831
773
            if not hasattr(value, "variant_level"):
832
 
                properties[name] = value
 
774
                all[name] = value
833
775
                continue
834
 
            properties[name] = type(value)(value, variant_level=
835
 
                                           value.variant_level+1)
836
 
        return dbus.Dictionary(properties, signature="sv")
 
776
            all[name] = type(value)(value, variant_level=
 
777
                                    value.variant_level+1)
 
778
        return dbus.Dictionary(all, signature="sv")
837
779
    
838
780
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
839
781
                         out_signature="s",
890
832
    return dbus.String(dt.isoformat(),
891
833
                       variant_level=variant_level)
892
834
 
893
 
 
894
835
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
895
836
                                  .__metaclass__):
896
837
    """Applied to an empty subclass of a D-Bus object, this metaclass
988
929
                                        attribute.func_closure)))
989
930
        return type.__new__(mcs, name, bases, attr)
990
931
 
991
 
 
992
932
class ClientDBus(Client, DBusObjectWithProperties):
993
933
    """A Client class using D-Bus
994
934
    
1005
945
    def __init__(self, bus = None, *args, **kwargs):
1006
946
        self.bus = bus
1007
947
        Client.__init__(self, *args, **kwargs)
1008
 
        
 
948
 
1009
949
        self._approvals_pending = 0
1010
950
        # Only now, when this client is initialized, can it show up on
1011
951
        # the D-Bus
1022
962
                             variant_level=1):
1023
963
        """ Modify a variable so that it's a property which announces
1024
964
        its changes to DBus.
1025
 
        
 
965
 
1026
966
        transform_fun: Function that takes a value and a variant_level
1027
967
                       and transforms it to a D-Bus type.
1028
968
        dbus_name: D-Bus name of the variable
1182
1122
        "D-Bus signal"
1183
1123
        return self.need_approval()
1184
1124
    
1185
 
    # NeRwequest - signal
1186
 
    @dbus.service.signal(_interface, signature="s")
1187
 
    def NewRequest(self, ip):
1188
 
        """D-Bus signal
1189
 
        Is sent after a client request a password.
1190
 
        """
1191
 
        pass
1192
 
    
1193
1125
    ## Methods
1194
1126
    
1195
1127
    # Approve - method
1424
1356
            return super(ProxyClient, self).__setattr__(name, value)
1425
1357
        self._pipe.send(('setattr', name, value))
1426
1358
 
1427
 
 
1428
1359
class ClientDBusTransitional(ClientDBus):
1429
1360
    __metaclass__ = AlternateDBusNamesMetaclass
1430
1361
 
1431
 
 
1432
1362
class ClientHandler(socketserver.BaseRequestHandler, object):
1433
1363
    """A class to handle client connections.
1434
1364
    
1495
1425
                    logger.warning("Bad certificate: %s", error)
1496
1426
                    return
1497
1427
                logger.debug("Fingerprint: %s", fpr)
1498
 
                if self.server.use_dbus:
1499
 
                    # Emit D-Bus signal
1500
 
                    client.NewRequest(str(self.client_address))
1501
1428
                
1502
1429
                try:
1503
1430
                    client = ProxyClient(child_pipe, fpr,
1647
1574
        # Convert the buffer to a Python bytestring
1648
1575
        fpr = ctypes.string_at(buf, buf_len.value)
1649
1576
        # Convert the bytestring to hexadecimal notation
1650
 
        hex_fpr = binascii.hexlify(fpr).upper()
 
1577
        hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
1651
1578
        return hex_fpr
1652
1579
 
1653
1580
 
1910
1837
    return timevalue
1911
1838
 
1912
1839
 
 
1840
def if_nametoindex(interface):
 
1841
    """Call the C function if_nametoindex(), or equivalent
 
1842
    
 
1843
    Note: This function cannot accept a unicode string."""
 
1844
    global if_nametoindex
 
1845
    try:
 
1846
        if_nametoindex = (ctypes.cdll.LoadLibrary
 
1847
                          (ctypes.util.find_library("c"))
 
1848
                          .if_nametoindex)
 
1849
    except (OSError, AttributeError):
 
1850
        logger.warning("Doing if_nametoindex the hard way")
 
1851
        def if_nametoindex(interface):
 
1852
            "Get an interface index the hard way, i.e. using fcntl()"
 
1853
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
 
1854
            with contextlib.closing(socket.socket()) as s:
 
1855
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
 
1856
                                    struct.pack(str("16s16x"),
 
1857
                                                interface))
 
1858
            interface_index = struct.unpack(str("I"),
 
1859
                                            ifreq[16:20])[0]
 
1860
            return interface_index
 
1861
    return if_nametoindex(interface)
 
1862
 
 
1863
 
1913
1864
def daemon(nochdir = False, noclose = False):
1914
1865
    """See daemon(3).  Standard BSD Unix function.
1915
1866
    
1971
1922
    parser.add_argument("--no-ipv6", action="store_false",
1972
1923
                        dest="use_ipv6", help="Do not use IPv6")
1973
1924
    parser.add_argument("--no-restore", action="store_false",
1974
 
                        dest="restore", help="Do not restore stored"
1975
 
                        " state")
1976
 
    parser.add_argument("--statedir", metavar="DIR",
1977
 
                        help="Directory to save/restore state in")
1978
 
    
 
1925
                        dest="restore", help="Do not restore stored state",
 
1926
                        default=True)
 
1927
 
1979
1928
    options = parser.parse_args()
1980
1929
    
1981
1930
    if options.check:
1994
1943
                        "use_dbus": "True",
1995
1944
                        "use_ipv6": "True",
1996
1945
                        "debuglevel": "",
1997
 
                        "restore": "True",
1998
 
                        "statedir": "/var/lib/mandos"
1999
1946
                        }
2000
1947
    
2001
1948
    # Parse config file for server-global settings
2018
1965
    # options, if set.
2019
1966
    for option in ("interface", "address", "port", "debug",
2020
1967
                   "priority", "servicename", "configdir",
2021
 
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
2022
 
                   "statedir"):
 
1968
                   "use_dbus", "use_ipv6", "debuglevel", "restore"):
2023
1969
        value = getattr(options, option)
2024
1970
        if value is not None:
2025
1971
            server_settings[option] = value
2037
1983
    debuglevel = server_settings["debuglevel"]
2038
1984
    use_dbus = server_settings["use_dbus"]
2039
1985
    use_ipv6 = server_settings["use_ipv6"]
2040
 
    stored_state_path = os.path.join(server_settings["statedir"],
2041
 
                                     stored_state_file)
2042
 
    
2043
 
    if debug:
2044
 
        initlogger(logging.DEBUG)
2045
 
    else:
2046
 
        if not debuglevel:
2047
 
            initlogger()
2048
 
        else:
2049
 
            level = getattr(logging, debuglevel.upper())
2050
 
            initlogger(level)
2051
1986
    
2052
1987
    if server_settings["servicename"] != "Mandos":
2053
1988
        syslogger.setFormatter(logging.Formatter
2108
2043
        if error[0] != errno.EPERM:
2109
2044
            raise error
2110
2045
    
 
2046
    if not debug and not debuglevel:
 
2047
        logger.setLevel(logging.WARNING)
 
2048
    if debuglevel:
 
2049
        level = getattr(logging, debuglevel.upper())
 
2050
        logger.setLevel(level)
 
2051
    
2111
2052
    if debug:
 
2053
        logger.setLevel(logging.DEBUG)
2112
2054
        # Enable all possible GnuTLS debugging
2113
2055
        
2114
2056
        # "Use a log level over 10 to enable all debugging options."
2183
2125
    # with exceptions for any special settings as defined above
2184
2126
    client_settings = dict((clientname,
2185
2127
                           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)))
 
2128
                                 (value if setting not in special_settings
 
2129
                                  else special_settings[setting](clientname)))
 
2130
                                for setting, value in client_config.items(clientname)))
2192
2131
                          for clientname in client_config.sections())
2193
2132
    
2194
2133
    old_client_settings = {}
2195
2134
    clients_data = []
2196
 
    
2197
 
    # Get client data and settings from last running state.
 
2135
 
 
2136
    # Get client data and settings from last running state. 
2198
2137
    if server_settings["restore"]:
2199
2138
        try:
2200
2139
            with open(stored_state_path, "rb") as stored_state:
2201
 
                clients_data, old_client_settings = (pickle.load
2202
 
                                                     (stored_state))
 
2140
                clients_data, old_client_settings = pickle.load(stored_state)
2203
2141
            os.remove(stored_state_path)
2204
2142
        except IOError as e:
2205
 
            logger.warning("Could not load persistent state: {0}"
2206
 
                           .format(e))
 
2143
            logger.warning("Could not load persistant state: {0}".format(e))
2207
2144
            if e.errno != errno.ENOENT:
2208
2145
                raise
2209
 
    
2210
 
    with Crypto() as crypt:
2211
 
        for client in clients_data:
2212
 
            client_name = client["name"]
2213
 
            
2214
 
            # Decide which value to use after restoring saved state.
2215
 
            # We have three different values: Old config file,
2216
 
            # new config file, and saved state.
2217
 
            # New config value takes precedence if it differs from old
2218
 
            # config value, otherwise use saved state.
2219
 
            for name, value in client_settings[client_name].items():
2220
 
                try:
2221
 
                    # For each value in new config, check if it
2222
 
                    # differs from the old config value (Except for
2223
 
                    # the "secret" attribute)
2224
 
                    if (name != "secret" and
2225
 
                        value != old_client_settings[client_name]
2226
 
                        [name]):
2227
 
                        setattr(client, name, value)
2228
 
                except KeyError:
2229
 
                    pass
2230
 
            
2231
 
            # Clients who has passed its expire date can still be
2232
 
            # enabled if its last checker was sucessful.  Clients
2233
 
            # whose checker failed before we stored its state is
2234
 
            # assumed to have failed all checkers during downtime.
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:
2240
 
                        client["enabled"] = False
2241
 
                    else:
2242
 
                        client["expires"] = (datetime.datetime
2243
 
                                             .utcnow()
2244
 
                                             + client["timeout"])
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
 
            
 
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():
2274
2156
            try:
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:
2281
 
                # If decryption fails, we use secret from new settings
2282
 
                tcp_server.clients[client_name].secret = (
2283
 
                    client_settings[client_name]["secret"])
2284
 
    
 
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)
 
2162
            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
        
2285
2201
    # Create/remove clients based on new changes made to config
2286
2202
    for clientname in set(old_client_settings) - set(client_settings):
2287
2203
        del tcp_server.clients[clientname]
2288
2204
    for clientname in set(client_settings) - set(old_client_settings):
2289
 
        tcp_server.clients[clientname] = (client_class(name
2290
 
                                                       = clientname,
 
2205
        tcp_server.clients[clientname] = (client_class(name = clientname,
2291
2206
                                                       config =
2292
2207
                                                       client_settings
2293
2208
                                                       [clientname]))
2294
2209
    
 
2210
 
2295
2211
    if not tcp_server.clients:
2296
2212
        logger.warning("No clients defined")
2297
2213
        
2379
2295
        multiprocessing.active_children()
2380
2296
        if not (tcp_server.clients or client_settings):
2381
2297
            return
2382
 
        
2383
 
        # Store client before exiting. Secrets are encrypted with key
2384
 
        # based on what config file has. If config file is
2385
 
        # removed/edited, old secret will thus be unrecovable.
 
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.
2386
2302
        clients = []
2387
 
        with Crypto() as crypt:
2388
 
            for client in tcp_server.clients.itervalues():
2389
 
                key = client_settings[client.name]["secret"]
2390
 
                client.encrypted_secret = crypt.encrypt(client.secret,
2391
 
                                                        key)
2392
 
                client_dict = {}
2393
 
                
2394
 
                # A list of attributes that will not be stored when
2395
 
                # shutting down.
2396
 
                exclude = set(("bus", "changedstate", "secret"))
2397
 
                for name, typ in (inspect.getmembers
2398
 
                                  (dbus.service.Object)):
2399
 
                    exclude.add(name)
2400
 
                
2401
 
                client_dict["encrypted_secret"] = (client
2402
 
                                                   .encrypted_secret)
2403
 
                for attr in client.client_structure:
2404
 
                    if attr not in exclude:
2405
 
                        client_dict[attr] = getattr(client, attr)
2406
 
                
2407
 
                clients.append(client_dict)
2408
 
                del client_settings[client.name]["secret"]
2409
 
        
 
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
            
2410
2321
        try:
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:
 
2322
            with os.fdopen(os.open(stored_state_path, os.O_CREAT|os.O_WRONLY|os.O_TRUNC, 0600), "wb") as stored_state:
2414
2323
                pickle.dump((clients, client_settings), stored_state)
2415
 
        except (IOError, OSError) as e:
2416
 
            logger.warning("Could not save persistent state: {0}"
2417
 
                           .format(e))
2418
 
            if e.errno not in (errno.ENOENT, errno.EACCES):
 
2324
        except IOError as e:
 
2325
            logger.warning("Could not save persistant state: {0}".format(e))
 
2326
            if e.errno != errno.ENOENT:
2419
2327
                raise
2420
 
        
 
2328
 
2421
2329
        # Delete all clients, and settings from config
2422
2330
        while tcp_server.clients:
2423
2331
            name, client = tcp_server.clients.popitem()
2441
2349
        # Need to initiate checking of clients
2442
2350
        if client.enabled:
2443
2351
            client.init_checker()
 
2352
 
2444
2353
    
2445
2354
    tcp_server.enable()
2446
2355
    tcp_server.server_activate()