/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-27 19:51:00 UTC
  • mto: (518.2.5 persistent-state-gpgme)
  • mto: This revision was merged to the branch mainline in revision 524.
  • Revision ID: belorn@recompile.se-20111127195100-fx0mpeia9xihvpmd
renamed variables

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"
89
 
stored_state_path = "/var/lib/mandos/clients.pickle"
 
90
stored_state_file = "clients.pickle"
90
91
 
91
92
logger = logging.getLogger()
92
93
syslogger = (logging.handlers.SysLogHandler
93
94
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
94
95
              address = str("/dev/log")))
95
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
 
96
114
def initlogger(level=logging.WARNING):
97
115
    """init logger and add loglevel"""
98
116
    
110
128
    logger.setLevel(level)
111
129
 
112
130
 
 
131
class PGPError(Exception):
 
132
    """Exception if encryption/decryption fails"""
 
133
    pass
 
134
 
 
135
 
 
136
class PGPEngine(object):
 
137
    """A simple class for OpenPGP symmetric encryption & decryption"""
 
138
    def __init__(self):
 
139
        self.gnupg = GnuPGInterface.GnuPG()
 
140
        self.tempdir = tempfile.mkdtemp(prefix="mandos-")
 
141
        self.gnupg = GnuPGInterface.GnuPG()
 
142
        self.gnupg.options.meta_interactive = False
 
143
        self.gnupg.options.homedir = self.tempdir
 
144
        self.gnupg.options.extra_args.extend(['--force-mdc',
 
145
                                              '--quiet'])
 
146
    
 
147
    def __enter__(self):
 
148
        return self
 
149
    
 
150
    def __exit__ (self, exc_type, exc_value, traceback):
 
151
        self._cleanup()
 
152
        return False
 
153
    
 
154
    def __del__(self):
 
155
        self._cleanup()
 
156
    
 
157
    def _cleanup(self):
 
158
        if self.tempdir is not None:
 
159
            # Delete contents of tempdir
 
160
            for root, dirs, files in os.walk(self.tempdir,
 
161
                                             topdown = False):
 
162
                for filename in files:
 
163
                    os.remove(os.path.join(root, filename))
 
164
                for dirname in dirs:
 
165
                    os.rmdir(os.path.join(root, dirname))
 
166
            # Remove tempdir
 
167
            os.rmdir(self.tempdir)
 
168
            self.tempdir = None
 
169
    
 
170
    def password_encode(self, password):
 
171
        # Passphrase can not be empty and can not contain newlines or
 
172
        # NUL bytes.  So we prefix it and hex encode it.
 
173
        return b"mandos" + binascii.hexlify(password)
 
174
    
 
175
    def encrypt(self, data, password):
 
176
        self.gnupg.passphrase = self.password_encode(password)
 
177
        with open(os.devnull) as devnull:
 
178
            try:
 
179
                proc = self.gnupg.run(['--symmetric'],
 
180
                                      create_fhs=['stdin', 'stdout'],
 
181
                                      attach_fhs={'stderr': devnull})
 
182
                with contextlib.closing(proc.handles['stdin']) as f:
 
183
                    f.write(data)
 
184
                with contextlib.closing(proc.handles['stdout']) as f:
 
185
                    ciphertext = f.read()
 
186
                proc.wait()
 
187
            except IOError as e:
 
188
                raise PGPError(e)
 
189
        self.gnupg.passphrase = None
 
190
        return ciphertext
 
191
    
 
192
    def decrypt(self, data, password):
 
193
        self.gnupg.passphrase = self.password_encode(password)
 
194
        with open(os.devnull) as devnull:
 
195
            try:
 
196
                proc = self.gnupg.run(['--decrypt'],
 
197
                                      create_fhs=['stdin', 'stdout'],
 
198
                                      attach_fhs={'stderr': devnull})
 
199
                with contextlib.closing(proc.handles['stdin'] ) as f:
 
200
                    f.write(data)
 
201
                with contextlib.closing(proc.handles['stdout']) as f:
 
202
                    decrypted_plaintext = f.read()
 
203
                proc.wait()
 
204
            except IOError as e:
 
205
                raise PGPError(e)
 
206
        self.gnupg.passphrase = None
 
207
        return decrypted_plaintext
 
208
 
 
209
 
 
210
 
113
211
class AvahiError(Exception):
114
212
    def __init__(self, value, *args, **kwargs):
115
213
        self.value = value
230
328
            try:
231
329
                self.group.Free()
232
330
            except (dbus.exceptions.UnknownMethodException,
233
 
                    dbus.exceptions.DBusException) as e:
 
331
                    dbus.exceptions.DBusException):
234
332
                pass
235
333
            self.group = None
236
334
        self.remove()
314
412
    interval:   datetime.timedelta(); How often to start a new checker
315
413
    last_approval_request: datetime.datetime(); (UTC) or None
316
414
    last_checked_ok: datetime.datetime(); (UTC) or None
317
 
    last_checker_status: integer between 0 and 255 reflecting exit status
318
 
                         of last checker. -1 reflect crashed checker,
319
 
                         or None.
320
 
    last_enabled: datetime.datetime(); (UTC)
 
415
 
 
416
    last_checker_status: integer between 0 and 255 reflecting exit
 
417
                         status of last checker. -1 reflects crashed
 
418
                         checker, or None.
 
419
    last_enabled: datetime.datetime(); (UTC) or None
321
420
    name:       string; from the config file, used in log messages and
322
421
                        D-Bus identifiers
323
422
    secret:     bytestring; sent verbatim (over TLS) to client
375
474
                            % self.name)
376
475
        self.host = config.get("host", "")
377
476
        self.created = datetime.datetime.utcnow()
378
 
        self.enabled = True
 
477
        self.enabled = config.get("enabled", True)
379
478
        self.last_approval_request = None
380
 
        self.last_enabled = datetime.datetime.utcnow()
 
479
        if self.enabled:
 
480
            self.last_enabled = datetime.datetime.utcnow()
 
481
        else:
 
482
            self.last_enabled = None
381
483
        self.last_checked_ok = None
382
484
        self.last_checker_status = None
383
485
        self.timeout = string_to_delta(config["timeout"])
387
489
        self.checker = None
388
490
        self.checker_initiator_tag = None
389
491
        self.disable_initiator_tag = None
390
 
        self.expires = datetime.datetime.utcnow() + self.timeout
 
492
        if self.enabled:
 
493
            self.expires = datetime.datetime.utcnow() + self.timeout
 
494
        else:
 
495
            self.expires = None
391
496
        self.checker_callback_tag = None
392
497
        self.checker_command = config["checker"]
393
498
        self.current_checker_command = None
402
507
        self.changedstate = (multiprocessing_manager
403
508
                             .Condition(multiprocessing_manager
404
509
                                        .Lock()))
405
 
        self.client_structure = [attr for attr in self.__dict__.iterkeys() if not attr.startswith("_")]
 
510
        self.client_structure = [attr for attr in
 
511
                                 self.__dict__.iterkeys()
 
512
                                 if not attr.startswith("_")]
406
513
        self.client_structure.append("client_structure")
407
 
 
408
 
 
 
514
        
409
515
        for name, t in inspect.getmembers(type(self),
410
 
                                          lambda obj: isinstance(obj, property)):
 
516
                                          lambda obj:
 
517
                                              isinstance(obj,
 
518
                                                         property)):
411
519
            if not name.startswith("_"):
412
520
                self.client_structure.append(name)
413
521
    
449
557
    
450
558
    def __del__(self):
451
559
        self.disable()
452
 
 
 
560
    
453
561
    def init_checker(self):
454
562
        # Schedule a new checker to be started an 'interval' from now,
455
563
        # and every interval from then on.
462
570
                                    self.disable))
463
571
        # Also start a new checker *right now*.
464
572
        self.start_checker()
465
 
 
466
 
        
 
573
    
467
574
    def checker_callback(self, pid, condition, command):
468
575
        """The checker has completed, so take appropriate actions."""
469
576
        self.checker_callback_tag = None
595
702
                raise
596
703
        self.checker = None
597
704
 
598
 
    # Encrypts a client secret and stores it in a varible encrypted_secret
599
 
    def encrypt_secret(self, key):
600
 
        # Encryption-key need to be of a specific size, so we hash inputed key
601
 
        hasheng = hashlib.sha256()
602
 
        hasheng.update(key)
603
 
        encryptionkey = hasheng.digest()
604
 
 
605
 
        # Create validation hash so we know at decryption if it was sucessful
606
 
        hasheng = hashlib.sha256()
607
 
        hasheng.update(self.secret)
608
 
        validationhash = hasheng.digest()
609
 
 
610
 
        # Encrypt secret
611
 
        iv = os.urandom(Crypto.Cipher.AES.block_size)
612
 
        ciphereng = Crypto.Cipher.AES.new(encryptionkey,
613
 
                                        Crypto.Cipher.AES.MODE_CFB, iv)
614
 
        ciphertext = ciphereng.encrypt(validationhash+self.secret)
615
 
        self.encrypted_secret = (ciphertext, iv)
616
 
 
617
 
    # Decrypt a encrypted client secret
618
 
    def decrypt_secret(self, key):
619
 
        # Decryption-key need to be of a specific size, so we hash inputed key
620
 
        hasheng = hashlib.sha256()
621
 
        hasheng.update(key)
622
 
        encryptionkey = hasheng.digest()
623
 
 
624
 
        # Decrypt encrypted secret
625
 
        ciphertext, iv = self.encrypted_secret
626
 
        ciphereng = Crypto.Cipher.AES.new(encryptionkey,
627
 
                                        Crypto.Cipher.AES.MODE_CFB, iv)
628
 
        plain = ciphereng.decrypt(ciphertext)
629
 
 
630
 
        # Validate decrypted secret to know if it was succesful
631
 
        hasheng = hashlib.sha256()
632
 
        validationhash = plain[:hasheng.digest_size]
633
 
        secret = plain[hasheng.digest_size:]
634
 
        hasheng.update(secret)
635
 
 
636
 
        # if validation fails, we use key as new secret. Otherwhise, we use
637
 
        # the decrypted secret
638
 
        if hasheng.digest() == validationhash:
639
 
            self.secret = secret
640
 
        else:
641
 
            self.secret = key
642
 
        del self.encrypted_secret
643
 
 
644
705
 
645
706
def dbus_service_property(dbus_interface, signature="v",
646
707
                          access="readwrite", byte_arrays=False):
764
825
        
765
826
        Note: Will not include properties with access="write".
766
827
        """
767
 
        all = {}
 
828
        properties = {}
768
829
        for name, prop in self._get_all_dbus_properties():
769
830
            if (interface_name
770
831
                and interface_name != prop._dbus_interface):
775
836
                continue
776
837
            value = prop()
777
838
            if not hasattr(value, "variant_level"):
778
 
                all[name] = value
 
839
                properties[name] = value
779
840
                continue
780
 
            all[name] = type(value)(value, variant_level=
781
 
                                    value.variant_level+1)
782
 
        return dbus.Dictionary(all, signature="sv")
 
841
            properties[name] = type(value)(value, variant_level=
 
842
                                           value.variant_level+1)
 
843
        return dbus.Dictionary(properties, signature="sv")
783
844
    
784
845
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
785
846
                         out_signature="s",
836
897
    return dbus.String(dt.isoformat(),
837
898
                       variant_level=variant_level)
838
899
 
 
900
 
839
901
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
840
902
                                  .__metaclass__):
841
903
    """Applied to an empty subclass of a D-Bus object, this metaclass
933
995
                                        attribute.func_closure)))
934
996
        return type.__new__(mcs, name, bases, attr)
935
997
 
 
998
 
936
999
class ClientDBus(Client, DBusObjectWithProperties):
937
1000
    """A Client class using D-Bus
938
1001
    
949
1012
    def __init__(self, bus = None, *args, **kwargs):
950
1013
        self.bus = bus
951
1014
        Client.__init__(self, *args, **kwargs)
952
 
 
 
1015
        
953
1016
        self._approvals_pending = 0
954
1017
        # Only now, when this client is initialized, can it show up on
955
1018
        # the D-Bus
966
1029
                             variant_level=1):
967
1030
        """ Modify a variable so that it's a property which announces
968
1031
        its changes to DBus.
969
 
 
 
1032
        
970
1033
        transform_fun: Function that takes a value and a variant_level
971
1034
                       and transforms it to a D-Bus type.
972
1035
        dbus_name: D-Bus name of the variable
1133
1196
        Is sent after a client request a password.
1134
1197
        """
1135
1198
        pass
1136
 
 
 
1199
    
1137
1200
    ## Methods
1138
1201
    
1139
1202
    # Approve - method
1222
1285
    # Created - property
1223
1286
    @dbus_service_property(_interface, signature="s", access="read")
1224
1287
    def Created_dbus_property(self):
1225
 
        return dbus.String(datetime_to_dbus(self.created))
 
1288
        return datetime_to_dbus(self.created)
1226
1289
    
1227
1290
    # LastEnabled - property
1228
1291
    @dbus_service_property(_interface, signature="s", access="read")
1304
1367
        self.interval = datetime.timedelta(0, 0, 0, value)
1305
1368
        if getattr(self, "checker_initiator_tag", None) is None:
1306
1369
            return
1307
 
        # Reschedule checker run
1308
 
        gobject.source_remove(self.checker_initiator_tag)
1309
 
        self.checker_initiator_tag = (gobject.timeout_add
1310
 
                                      (value, self.start_checker))
1311
 
        self.start_checker()    # Start one now, too
 
1370
        if self.enabled:
 
1371
            # Reschedule checker run
 
1372
            gobject.source_remove(self.checker_initiator_tag)
 
1373
            self.checker_initiator_tag = (gobject.timeout_add
 
1374
                                          (value, self.start_checker))
 
1375
            self.start_checker()    # Start one now, too
1312
1376
    
1313
1377
    # Checker - property
1314
1378
    @dbus_service_property(_interface, signature="s",
1368
1432
            return super(ProxyClient, self).__setattr__(name, value)
1369
1433
        self._pipe.send(('setattr', name, value))
1370
1434
 
 
1435
 
1371
1436
class ClientDBusTransitional(ClientDBus):
1372
1437
    __metaclass__ = AlternateDBusNamesMetaclass
1373
1438
 
 
1439
 
1374
1440
class ClientHandler(socketserver.BaseRequestHandler, object):
1375
1441
    """A class to handle client connections.
1376
1442
    
1589
1655
        # Convert the buffer to a Python bytestring
1590
1656
        fpr = ctypes.string_at(buf, buf_len.value)
1591
1657
        # Convert the bytestring to hexadecimal notation
1592
 
        hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
 
1658
        hex_fpr = binascii.hexlify(fpr).upper()
1593
1659
        return hex_fpr
1594
1660
 
1595
1661
 
1852
1918
    return timevalue
1853
1919
 
1854
1920
 
1855
 
def if_nametoindex(interface):
1856
 
    """Call the C function if_nametoindex(), or equivalent
1857
 
    
1858
 
    Note: This function cannot accept a unicode string."""
1859
 
    global if_nametoindex
1860
 
    try:
1861
 
        if_nametoindex = (ctypes.cdll.LoadLibrary
1862
 
                          (ctypes.util.find_library("c"))
1863
 
                          .if_nametoindex)
1864
 
    except (OSError, AttributeError):
1865
 
        logger.warning("Doing if_nametoindex the hard way")
1866
 
        def if_nametoindex(interface):
1867
 
            "Get an interface index the hard way, i.e. using fcntl()"
1868
 
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1869
 
            with contextlib.closing(socket.socket()) as s:
1870
 
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1871
 
                                    struct.pack(str("16s16x"),
1872
 
                                                interface))
1873
 
            interface_index = struct.unpack(str("I"),
1874
 
                                            ifreq[16:20])[0]
1875
 
            return interface_index
1876
 
    return if_nametoindex(interface)
1877
 
 
1878
 
 
1879
1921
def daemon(nochdir = False, noclose = False):
1880
1922
    """See daemon(3).  Standard BSD Unix function.
1881
1923
    
1937
1979
    parser.add_argument("--no-ipv6", action="store_false",
1938
1980
                        dest="use_ipv6", help="Do not use IPv6")
1939
1981
    parser.add_argument("--no-restore", action="store_false",
1940
 
                        dest="restore", help="Do not restore stored state",
1941
 
                        default=True)
1942
 
 
 
1982
                        dest="restore", help="Do not restore stored"
 
1983
                        " state")
 
1984
    parser.add_argument("--statedir", metavar="DIR",
 
1985
                        help="Directory to save/restore state in")
 
1986
    
1943
1987
    options = parser.parse_args()
1944
1988
    
1945
1989
    if options.check:
1958
2002
                        "use_dbus": "True",
1959
2003
                        "use_ipv6": "True",
1960
2004
                        "debuglevel": "",
 
2005
                        "restore": "True",
 
2006
                        "statedir": "/var/lib/mandos"
1961
2007
                        }
1962
2008
    
1963
2009
    # Parse config file for server-global settings
1980
2026
    # options, if set.
1981
2027
    for option in ("interface", "address", "port", "debug",
1982
2028
                   "priority", "servicename", "configdir",
1983
 
                   "use_dbus", "use_ipv6", "debuglevel", "restore"):
 
2029
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
 
2030
                   "statedir"):
1984
2031
        value = getattr(options, option)
1985
2032
        if value is not None:
1986
2033
            server_settings[option] = value
1998
2045
    debuglevel = server_settings["debuglevel"]
1999
2046
    use_dbus = server_settings["use_dbus"]
2000
2047
    use_ipv6 = server_settings["use_ipv6"]
 
2048
    stored_state_path = os.path.join(server_settings["statedir"],
 
2049
                                     stored_state_file)
2001
2050
    
2002
2051
    if debug:
2003
2052
        initlogger(logging.DEBUG)
2006
2055
            initlogger()
2007
2056
        else:
2008
2057
            level = getattr(logging, debuglevel.upper())
2009
 
            initlogger(level)    
 
2058
            initlogger(level)
2010
2059
    
2011
2060
    if server_settings["servicename"] != "Mandos":
2012
2061
        syslogger.setFormatter(logging.Formatter
2136
2185
        "approved_by_default":
2137
2186
            lambda section:
2138
2187
            client_config.getboolean(section, "approved_by_default"),
 
2188
        "enabled":
 
2189
            lambda section:
 
2190
            client_config.getboolean(section, "enabled"),
2139
2191
        }
2140
2192
    # Construct a new dict of client settings of this form:
2141
2193
    # { client_name: {setting_name: value, ...}, ...}
2142
2194
    # with exceptions for any special settings as defined above
2143
2195
    client_settings = dict((clientname,
2144
2196
                           dict((setting,
2145
 
                                 (value if setting not in special_settings
2146
 
                                  else special_settings[setting](clientname)))
2147
 
                                for setting, value in client_config.items(clientname)))
 
2197
                                 (value
 
2198
                                  if setting not in special_settings
 
2199
                                  else special_settings[setting]
 
2200
                                  (clientname)))
 
2201
                                for setting, value in
 
2202
                                client_config.items(clientname)))
2148
2203
                          for clientname in client_config.sections())
2149
2204
    
2150
2205
    old_client_settings = {}
2151
2206
    clients_data = []
2152
 
 
2153
 
    # Get client data and settings from last running state. 
 
2207
    
 
2208
    # Get client data and settings from last running state.
2154
2209
    if server_settings["restore"]:
2155
2210
        try:
2156
2211
            with open(stored_state_path, "rb") as stored_state:
2157
 
                clients_data, old_client_settings = pickle.load(stored_state)
 
2212
                clients_data, old_client_settings = (pickle.load
 
2213
                                                     (stored_state))
2158
2214
            os.remove(stored_state_path)
2159
2215
        except IOError as e:
2160
 
            logger.warning("Could not load persistant state: {0}".format(e))
 
2216
            logger.warning("Could not load persistent state: {0}"
 
2217
                           .format(e))
2161
2218
            if e.errno != errno.ENOENT:
2162
2219
                raise
2163
 
 
2164
 
    for client in clients_data:
2165
 
        client_name = client["name"]
2166
 
        
2167
 
        # Decide which value to use after restoring saved state.
2168
 
        # We have three different values: Old config file,
2169
 
        # new config file, and saved state.
2170
 
        # New config value takes precedence if it differs from old
2171
 
        # config value, otherwise use saved state.
2172
 
        for name, value in client_settings[client_name].items():
 
2220
    
 
2221
    with PGPEngine() as pgp:
 
2222
        for client in clients_data:
 
2223
            client_name = client["name"]
 
2224
            
 
2225
            # Decide which value to use after restoring saved state.
 
2226
            # We have three different values: Old config file,
 
2227
            # new config file, and saved state.
 
2228
            # New config value takes precedence if it differs from old
 
2229
            # config value, otherwise use saved state.
 
2230
            for name, value in client_settings[client_name].items():
 
2231
                try:
 
2232
                    # For each value in new config, check if it
 
2233
                    # differs from the old config value (Except for
 
2234
                    # the "secret" attribute)
 
2235
                    if (name != "secret" and
 
2236
                        value != old_client_settings[client_name]
 
2237
                        [name]):
 
2238
                        setattr(client, name, value)
 
2239
                except KeyError:
 
2240
                    pass
 
2241
            
 
2242
            # Clients who has passed its expire date can still be
 
2243
            # enabled if its last checker was sucessful.  Clients
 
2244
            # whose checker failed before we stored its state is
 
2245
            # assumed to have failed all checkers during downtime.
 
2246
            if client["enabled"] and client["last_checked_ok"]:
 
2247
                if ((datetime.datetime.utcnow()
 
2248
                     - client["last_checked_ok"])
 
2249
                    > client["interval"]):
 
2250
                    if client["last_checker_status"] != 0:
 
2251
                        client["enabled"] = False
 
2252
                    else:
 
2253
                        client["expires"] = (datetime.datetime
 
2254
                                             .utcnow()
 
2255
                                             + client["timeout"])
 
2256
            
 
2257
            client["changedstate"] = (multiprocessing_manager
 
2258
                                      .Condition
 
2259
                                      (multiprocessing_manager
 
2260
                                       .Lock()))
 
2261
            if use_dbus:
 
2262
                new_client = (ClientDBusTransitional.__new__
 
2263
                              (ClientDBusTransitional))
 
2264
                tcp_server.clients[client_name] = new_client
 
2265
                new_client.bus = bus
 
2266
                for name, value in client.iteritems():
 
2267
                    setattr(new_client, name, value)
 
2268
                client_object_name = unicode(client_name).translate(
 
2269
                    {ord("."): ord("_"),
 
2270
                     ord("-"): ord("_")})
 
2271
                new_client.dbus_object_path = (dbus.ObjectPath
 
2272
                                               ("/clients/"
 
2273
                                                + client_object_name))
 
2274
                DBusObjectWithProperties.__init__(new_client,
 
2275
                                                  new_client.bus,
 
2276
                                                  new_client
 
2277
                                                  .dbus_object_path)
 
2278
            else:
 
2279
                tcp_server.clients[client_name] = (Client.__new__
 
2280
                                                   (Client))
 
2281
                for name, value in client.iteritems():
 
2282
                    setattr(tcp_server.clients[client_name],
 
2283
                            name, value)
 
2284
            
2173
2285
            try:
2174
 
                # For each value in new config, check if it differs
2175
 
                # from the old config value (Except for the "secret"
2176
 
                # attribute)
2177
 
                if name != "secret" and value != old_client_settings[client_name][name]:
2178
 
                    setattr(client, name, value)
2179
 
            except KeyError:
2180
 
                pass
2181
 
 
2182
 
        # Clients who has passed its expire date, can still be enabled if its
2183
 
        # last checker was sucessful. Clients who checkers failed before we
2184
 
        # stored it state is asumed to had failed checker during downtime.
2185
 
        if client["enabled"] and client["last_checked_ok"]:
2186
 
            if ((datetime.datetime.utcnow() - client["last_checked_ok"])
2187
 
                > client["interval"]):
2188
 
                if client["last_checker_status"] != 0:
2189
 
                    client["enabled"] = False
2190
 
                else:
2191
 
                    client["expires"] = datetime.datetime.utcnow() + client["timeout"]
2192
 
 
2193
 
        client["changedstate"] = (multiprocessing_manager
2194
 
                                  .Condition(multiprocessing_manager
2195
 
                                             .Lock()))
2196
 
        if use_dbus:
2197
 
            new_client = ClientDBusTransitional.__new__(ClientDBusTransitional)
2198
 
            tcp_server.clients[client_name] = new_client
2199
 
            new_client.bus = bus
2200
 
            for name, value in client.iteritems():
2201
 
                setattr(new_client, name, value)
2202
 
            client_object_name = unicode(client_name).translate(
2203
 
                {ord("."): ord("_"),
2204
 
                 ord("-"): ord("_")})
2205
 
            new_client.dbus_object_path = (dbus.ObjectPath
2206
 
                                     ("/clients/" + client_object_name))
2207
 
            DBusObjectWithProperties.__init__(new_client,
2208
 
                                              new_client.bus,
2209
 
                                              new_client.dbus_object_path)
2210
 
        else:
2211
 
            tcp_server.clients[client_name] = Client.__new__(Client)
2212
 
            for name, value in client.iteritems():
2213
 
                setattr(tcp_server.clients[client_name], name, value)
2214
 
                
2215
 
        tcp_server.clients[client_name].decrypt_secret(
2216
 
            client_settings[client_name]["secret"])            
2217
 
        
 
2286
                tcp_server.clients[client_name].secret = (
 
2287
                    pgp.decrypt(tcp_server.clients[client_name]
 
2288
                                .encrypted_secret,
 
2289
                                client_settings[client_name]
 
2290
                                ["secret"]))
 
2291
            except PGPError:
 
2292
                # If decryption fails, we use secret from new settings
 
2293
                tcp_server.clients[client_name].secret = (
 
2294
                    client_settings[client_name]["secret"])
 
2295
    
2218
2296
    # Create/remove clients based on new changes made to config
2219
2297
    for clientname in set(old_client_settings) - set(client_settings):
2220
2298
        del tcp_server.clients[clientname]
2221
2299
    for clientname in set(client_settings) - set(old_client_settings):
2222
 
        tcp_server.clients[clientname] = (client_class(name = clientname,
 
2300
        tcp_server.clients[clientname] = (client_class(name
 
2301
                                                       = clientname,
2223
2302
                                                       config =
2224
2303
                                                       client_settings
2225
2304
                                                       [clientname]))
2226
2305
    
2227
 
 
2228
2306
    if not tcp_server.clients:
2229
2307
        logger.warning("No clients defined")
2230
2308
        
2312
2390
        multiprocessing.active_children()
2313
2391
        if not (tcp_server.clients or client_settings):
2314
2392
            return
2315
 
 
2316
 
        # Store client before exiting. Secrets are encrypted with key based
2317
 
        # on what config file has. If config file is removed/edited, old
2318
 
        # secret will thus be unrecovable.
 
2393
        
 
2394
        # Store client before exiting. Secrets are encrypted with key
 
2395
        # based on what config file has. If config file is
 
2396
        # removed/edited, old secret will thus be unrecovable.
2319
2397
        clients = []
2320
 
        for client in tcp_server.clients.itervalues():
2321
 
            client.encrypt_secret(client_settings[client.name]["secret"])
2322
 
 
2323
 
            client_dict = {}
2324
 
 
2325
 
            # A list of attributes that will not be stored when shuting down.
2326
 
            exclude = set(("bus", "changedstate", "secret"))            
2327
 
            for name, typ in inspect.getmembers(dbus.service.Object):
2328
 
                exclude.add(name)
2329
 
                
2330
 
            client_dict["encrypted_secret"] = client.encrypted_secret
2331
 
            for attr in client.client_structure:
2332
 
                if attr not in exclude:
2333
 
                    client_dict[attr] = getattr(client, attr)
2334
 
 
2335
 
            clients.append(client_dict) 
2336
 
            del client_settings[client.name]["secret"]
2337
 
            
 
2398
        with PGPEngine() as pgp:
 
2399
            for client in tcp_server.clients.itervalues():
 
2400
                key = client_settings[client.name]["secret"]
 
2401
                client.encrypted_secret = pgp.encrypt(client.secret,
 
2402
                                                      key)
 
2403
                client_dict = {}
 
2404
                
 
2405
                # A list of attributes that will not be stored when
 
2406
                # shutting down.
 
2407
                exclude = set(("bus", "changedstate", "secret"))
 
2408
                for name, typ in (inspect.getmembers
 
2409
                                  (dbus.service.Object)):
 
2410
                    exclude.add(name)
 
2411
                
 
2412
                client_dict["encrypted_secret"] = (client
 
2413
                                                   .encrypted_secret)
 
2414
                for attr in client.client_structure:
 
2415
                    if attr not in exclude:
 
2416
                        client_dict[attr] = getattr(client, attr)
 
2417
                
 
2418
                clients.append(client_dict)
 
2419
                del client_settings[client.name]["secret"]
 
2420
        
2338
2421
        try:
2339
 
            with os.fdopen(os.open(stored_state_path, os.O_CREAT|os.O_WRONLY|os.O_TRUNC, 0600), "wb") as stored_state:
 
2422
            with os.fdopen(os.open(stored_state_path,
 
2423
                                   os.O_CREAT|os.O_WRONLY|os.O_TRUNC,
 
2424
                                   0600), "wb") as stored_state:
2340
2425
                pickle.dump((clients, client_settings), stored_state)
2341
 
        except IOError as e:
2342
 
            logger.warning("Could not save persistant state: {0}".format(e))
2343
 
            if e.errno != errno.ENOENT:
 
2426
        except (IOError, OSError) as e:
 
2427
            logger.warning("Could not save persistent state: {0}"
 
2428
                           .format(e))
 
2429
            if e.errno not in (errno.ENOENT, errno.EACCES):
2344
2430
                raise
2345
 
 
 
2431
        
2346
2432
        # Delete all clients, and settings from config
2347
2433
        while tcp_server.clients:
2348
2434
            name, client = tcp_server.clients.popitem()
2366
2452
        # Need to initiate checking of clients
2367
2453
        if client.enabled:
2368
2454
            client.init_checker()
2369
 
 
2370
2455
    
2371
2456
    tcp_server.enable()
2372
2457
    tcp_server.server_activate()