/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:
63
63
import cPickle as pickle
64
64
import multiprocessing
65
65
import types
66
 
import hashlib
 
66
import binascii
 
67
import tempfile
67
68
 
68
69
import dbus
69
70
import dbus.service
74
75
import ctypes.util
75
76
import xml.dom.minidom
76
77
import inspect
77
 
import Crypto.Cipher.AES
 
78
import GnuPGInterface
78
79
 
79
80
try:
80
81
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
86
87
 
87
88
 
88
89
version = "1.4.1"
 
90
stored_state_file = "clients.pickle"
89
91
 
90
92
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
 
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)
 
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
 
107
208
 
108
209
 
109
210
class AvahiError(Exception):
226
327
            try:
227
328
                self.group.Free()
228
329
            except (dbus.exceptions.UnknownMethodException,
229
 
                    dbus.exceptions.DBusException) as e:
 
330
                    dbus.exceptions.DBusException):
230
331
                pass
231
332
            self.group = None
232
333
        self.remove()
310
411
    interval:   datetime.timedelta(); How often to start a new checker
311
412
    last_approval_request: datetime.datetime(); (UTC) or None
312
413
    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.
 
414
 
 
415
    last_checker_status: integer between 0 and 255 reflecting exit
 
416
                         status of last checker. -1 reflects crashed
 
417
                         checker, or None.
316
418
    last_enabled: datetime.datetime(); (UTC)
317
419
    name:       string; from the config file, used in log messages and
318
420
                        D-Bus identifiers
398
500
        self.changedstate = (multiprocessing_manager
399
501
                             .Condition(multiprocessing_manager
400
502
                                        .Lock()))
401
 
        self.client_structure = [attr for attr in self.__dict__.iterkeys() if not attr.startswith("_")]
 
503
        self.client_structure = [attr for attr in
 
504
                                 self.__dict__.iterkeys()
 
505
                                 if not attr.startswith("_")]
402
506
        self.client_structure.append("client_structure")
403
 
 
404
 
 
 
507
        
405
508
        for name, t in inspect.getmembers(type(self),
406
 
                                          lambda obj: isinstance(obj, property)):
 
509
                                          lambda obj:
 
510
                                              isinstance(obj,
 
511
                                                         property)):
407
512
            if not name.startswith("_"):
408
513
                self.client_structure.append(name)
409
514
    
445
550
    
446
551
    def __del__(self):
447
552
        self.disable()
448
 
 
 
553
    
449
554
    def init_checker(self):
450
555
        # Schedule a new checker to be started an 'interval' from now,
451
556
        # and every interval from then on.
458
563
                                    self.disable))
459
564
        # Also start a new checker *right now*.
460
565
        self.start_checker()
461
 
 
462
 
        
 
566
    
463
567
    def checker_callback(self, pid, condition, command):
464
568
        """The checker has completed, so take appropriate actions."""
465
569
        self.checker_callback_tag = None
591
695
                raise
592
696
        self.checker = None
593
697
 
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
698
 
641
699
def dbus_service_property(dbus_interface, signature="v",
642
700
                          access="readwrite", byte_arrays=False):
760
818
        
761
819
        Note: Will not include properties with access="write".
762
820
        """
763
 
        all = {}
 
821
        properties = {}
764
822
        for name, prop in self._get_all_dbus_properties():
765
823
            if (interface_name
766
824
                and interface_name != prop._dbus_interface):
771
829
                continue
772
830
            value = prop()
773
831
            if not hasattr(value, "variant_level"):
774
 
                all[name] = value
 
832
                properties[name] = value
775
833
                continue
776
 
            all[name] = type(value)(value, variant_level=
777
 
                                    value.variant_level+1)
778
 
        return dbus.Dictionary(all, signature="sv")
 
834
            properties[name] = type(value)(value, variant_level=
 
835
                                           value.variant_level+1)
 
836
        return dbus.Dictionary(properties, signature="sv")
779
837
    
780
838
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
781
839
                         out_signature="s",
832
890
    return dbus.String(dt.isoformat(),
833
891
                       variant_level=variant_level)
834
892
 
 
893
 
835
894
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
836
895
                                  .__metaclass__):
837
896
    """Applied to an empty subclass of a D-Bus object, this metaclass
929
988
                                        attribute.func_closure)))
930
989
        return type.__new__(mcs, name, bases, attr)
931
990
 
 
991
 
932
992
class ClientDBus(Client, DBusObjectWithProperties):
933
993
    """A Client class using D-Bus
934
994
    
945
1005
    def __init__(self, bus = None, *args, **kwargs):
946
1006
        self.bus = bus
947
1007
        Client.__init__(self, *args, **kwargs)
948
 
 
 
1008
        
949
1009
        self._approvals_pending = 0
950
1010
        # Only now, when this client is initialized, can it show up on
951
1011
        # the D-Bus
962
1022
                             variant_level=1):
963
1023
        """ Modify a variable so that it's a property which announces
964
1024
        its changes to DBus.
965
 
 
 
1025
        
966
1026
        transform_fun: Function that takes a value and a variant_level
967
1027
                       and transforms it to a D-Bus type.
968
1028
        dbus_name: D-Bus name of the variable
1122
1182
        "D-Bus signal"
1123
1183
        return self.need_approval()
1124
1184
    
 
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
    
1125
1193
    ## Methods
1126
1194
    
1127
1195
    # Approve - method
1356
1424
            return super(ProxyClient, self).__setattr__(name, value)
1357
1425
        self._pipe.send(('setattr', name, value))
1358
1426
 
 
1427
 
1359
1428
class ClientDBusTransitional(ClientDBus):
1360
1429
    __metaclass__ = AlternateDBusNamesMetaclass
1361
1430
 
 
1431
 
1362
1432
class ClientHandler(socketserver.BaseRequestHandler, object):
1363
1433
    """A class to handle client connections.
1364
1434
    
1425
1495
                    logger.warning("Bad certificate: %s", error)
1426
1496
                    return
1427
1497
                logger.debug("Fingerprint: %s", fpr)
 
1498
                if self.server.use_dbus:
 
1499
                    # Emit D-Bus signal
 
1500
                    client.NewRequest(str(self.client_address))
1428
1501
                
1429
1502
                try:
1430
1503
                    client = ProxyClient(child_pipe, fpr,
1574
1647
        # Convert the buffer to a Python bytestring
1575
1648
        fpr = ctypes.string_at(buf, buf_len.value)
1576
1649
        # Convert the bytestring to hexadecimal notation
1577
 
        hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
 
1650
        hex_fpr = binascii.hexlify(fpr).upper()
1578
1651
        return hex_fpr
1579
1652
 
1580
1653
 
1837
1910
    return timevalue
1838
1911
 
1839
1912
 
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
 
 
1864
1913
def daemon(nochdir = False, noclose = False):
1865
1914
    """See daemon(3).  Standard BSD Unix function.
1866
1915
    
1922
1971
    parser.add_argument("--no-ipv6", action="store_false",
1923
1972
                        dest="use_ipv6", help="Do not use IPv6")
1924
1973
    parser.add_argument("--no-restore", action="store_false",
1925
 
                        dest="restore", help="Do not restore stored state",
1926
 
                        default=True)
1927
 
 
 
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
    
1928
1979
    options = parser.parse_args()
1929
1980
    
1930
1981
    if options.check:
1943
1994
                        "use_dbus": "True",
1944
1995
                        "use_ipv6": "True",
1945
1996
                        "debuglevel": "",
 
1997
                        "restore": "True",
 
1998
                        "statedir": "/var/lib/mandos"
1946
1999
                        }
1947
2000
    
1948
2001
    # Parse config file for server-global settings
1965
2018
    # options, if set.
1966
2019
    for option in ("interface", "address", "port", "debug",
1967
2020
                   "priority", "servicename", "configdir",
1968
 
                   "use_dbus", "use_ipv6", "debuglevel", "restore"):
 
2021
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
 
2022
                   "statedir"):
1969
2023
        value = getattr(options, option)
1970
2024
        if value is not None:
1971
2025
            server_settings[option] = value
1983
2037
    debuglevel = server_settings["debuglevel"]
1984
2038
    use_dbus = server_settings["use_dbus"]
1985
2039
    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)
1986
2051
    
1987
2052
    if server_settings["servicename"] != "Mandos":
1988
2053
        syslogger.setFormatter(logging.Formatter
2043
2108
        if error[0] != errno.EPERM:
2044
2109
            raise error
2045
2110
    
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
 
    
2052
2111
    if debug:
2053
 
        logger.setLevel(logging.DEBUG)
2054
2112
        # Enable all possible GnuTLS debugging
2055
2113
        
2056
2114
        # "Use a log level over 10 to enable all debugging options."
2125
2183
    # with exceptions for any special settings as defined above
2126
2184
    client_settings = dict((clientname,
2127
2185
                           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)))
 
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)))
2131
2192
                          for clientname in client_config.sections())
2132
2193
    
2133
2194
    old_client_settings = {}
2134
2195
    clients_data = []
2135
 
 
2136
 
    # Get client data and settings from last running state. 
 
2196
    
 
2197
    # Get client data and settings from last running state.
2137
2198
    if server_settings["restore"]:
2138
2199
        try:
2139
2200
            with open(stored_state_path, "rb") as stored_state:
2140
 
                clients_data, old_client_settings = pickle.load(stored_state)
 
2201
                clients_data, old_client_settings = (pickle.load
 
2202
                                                     (stored_state))
2141
2203
            os.remove(stored_state_path)
2142
2204
        except IOError as e:
2143
 
            logger.warning("Could not load persistant state: {0}".format(e))
 
2205
            logger.warning("Could not load persistent state: {0}"
 
2206
                           .format(e))
2144
2207
            if e.errno != errno.ENOENT:
2145
2208
                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():
 
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
            
2156
2274
            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)
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
 
        
 
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
    
2201
2285
    # Create/remove clients based on new changes made to config
2202
2286
    for clientname in set(old_client_settings) - set(client_settings):
2203
2287
        del tcp_server.clients[clientname]
2204
2288
    for clientname in set(client_settings) - set(old_client_settings):
2205
 
        tcp_server.clients[clientname] = (client_class(name = clientname,
 
2289
        tcp_server.clients[clientname] = (client_class(name
 
2290
                                                       = clientname,
2206
2291
                                                       config =
2207
2292
                                                       client_settings
2208
2293
                                                       [clientname]))
2209
2294
    
2210
 
 
2211
2295
    if not tcp_server.clients:
2212
2296
        logger.warning("No clients defined")
2213
2297
        
2295
2379
        multiprocessing.active_children()
2296
2380
        if not (tcp_server.clients or client_settings):
2297
2381
            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.
 
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.
2302
2386
        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
 
            
 
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
        
2321
2410
        try:
2322
 
            with os.fdopen(os.open(stored_state_path, os.O_CREAT|os.O_WRONLY|os.O_TRUNC, 0600), "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:
2323
2414
                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:
 
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):
2327
2419
                raise
2328
 
 
 
2420
        
2329
2421
        # Delete all clients, and settings from config
2330
2422
        while tcp_server.clients:
2331
2423
            name, client = tcp_server.clients.popitem()
2349
2441
        # Need to initiate checking of clients
2350
2442
        if client.enabled:
2351
2443
            client.init_checker()
2352
 
 
2353
2444
    
2354
2445
    tcp_server.enable()
2355
2446
    tcp_server.server_activate()