590d18
From 96cadaec3c16b296627991ed517d7015fbe2882f Mon Sep 17 00:00:00 2001
590d18
From: Jan Cholasta <jcholast@redhat.com>
590d18
Date: Wed, 2 Sep 2015 14:04:17 +0200
590d18
Subject: [PATCH] ldap: Make ldap2 connection management thread-safe again
590d18
590d18
This fixes the connection code in LDAPClient to not store the LDAP connection
590d18
in an attribute of the object, which in combination with ldap2's per-thread
590d18
connections lead to race conditions resulting in connection failures. ldap2
590d18
code was updated accordingly.
590d18
590d18
https://fedorahosted.org/freeipa/ticket/5268
590d18
590d18
Reviewed-By: Tomas Babej <tbabej@redhat.com>
590d18
---
590d18
 ipapython/ipaldap.py       | 32 +++++++++-----------------------
590d18
 ipaserver/plugins/ldap2.py | 29 +++++++++++++----------------
590d18
 2 files changed, 22 insertions(+), 39 deletions(-)
590d18
590d18
diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
590d18
index ef7c41a37c1f46290afbb11f912d7a4b8ea224c9..4443db03bcee25033abf63786016a7931f7eed20 100644
590d18
--- a/ipapython/ipaldap.py
590d18
+++ b/ipapython/ipaldap.py
590d18
@@ -710,11 +710,10 @@ class LDAPClient(object):
590d18
         self._decode_attrs = decode_attrs
590d18
 
590d18
         self.log = log_mgr.get_logger(self)
590d18
-        self._conn = None
590d18
         self._has_schema = False
590d18
         self._schema = None
590d18
 
590d18
-        self._connect()
590d18
+        self._conn = self._connect()
590d18
 
590d18
     @property
590d18
     def conn(self):
590d18
@@ -1023,29 +1022,16 @@ class LDAPClient(object):
590d18
         """
590d18
         Close the connection.
590d18
         """
590d18
-        if self._conn is not None:
590d18
-            self._disconnect()
590d18
+        self._conn = None
590d18
 
590d18
     def _connect(self):
590d18
-        if self._conn is not None:
590d18
-            raise errors.DatabaseError(
590d18
-                desc="Can't connect to server", info="Already connected")
590d18
-
590d18
         with self.error_handler():
590d18
-            # bypass ldap2's locking
590d18
-            object.__setattr__(self, '_conn',
590d18
-                               ldap.initialize(self.ldap_uri))
590d18
+            conn = ldap.initialize(self.ldap_uri)
590d18
 
590d18
             if self._start_tls:
590d18
-                self._conn.start_tls_s()
590d18
-
590d18
-    def _disconnect(self):
590d18
-        if self._conn is None:
590d18
-            raise errors.DatabaseError(
590d18
-                desc="Can't disconnect from server", info="Not connected")
590d18
+                conn.start_tls_s()
590d18
 
590d18
-        # bypass ldap2's locking
590d18
-        object.__setattr__(self, '_conn', None)
590d18
+        return conn
590d18
 
590d18
     def simple_bind(self, bind_dn, bind_password, server_controls=None,
590d18
                     client_controls=None):
590d18
@@ -1059,7 +1045,7 @@ class LDAPClient(object):
590d18
             assert isinstance(bind_dn, DN)
590d18
             bind_dn = str(bind_dn)
590d18
             bind_password = self.encode(bind_password)
590d18
-            self._conn.simple_bind_s(
590d18
+            self.conn.simple_bind_s(
590d18
                 bind_dn, bind_password, server_controls, client_controls)
590d18
 
590d18
     def external_bind(self, user_name, server_controls=None,
590d18
@@ -1070,7 +1056,7 @@ class LDAPClient(object):
590d18
         with self.error_handler():
590d18
             auth_tokens = ldap.sasl.external(user_name)
590d18
             self._flush_schema()
590d18
-            self._conn.sasl_interactive_bind_s(
590d18
+            self.conn.sasl_interactive_bind_s(
590d18
                 '', auth_tokens, server_controls, client_controls)
590d18
 
590d18
     def gssapi_bind(self, server_controls=None, client_controls=None):
590d18
@@ -1080,7 +1066,7 @@ class LDAPClient(object):
590d18
         with self.error_handler():
590d18
             auth_tokens = ldap.sasl.sasl({}, 'GSSAPI')
590d18
             self._flush_schema()
590d18
-            self._conn.sasl_interactive_bind_s(
590d18
+            self.conn.sasl_interactive_bind_s(
590d18
                 '', auth_tokens, server_controls, client_controls)
590d18
 
590d18
     def unbind(self):
590d18
@@ -1089,7 +1075,7 @@ class LDAPClient(object):
590d18
         """
590d18
         with self.error_handler():
590d18
             self._flush_schema()
590d18
-            self._conn.unbind_s()
590d18
+            self.conn.unbind_s()
590d18
 
590d18
     def make_dn_from_attr(self, attr, value, parent_dn=None):
590d18
         """
590d18
diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py
590d18
index 68feee4f09eb12e50867dfbe3c482a359838aa82..deb0592ab68ab8eb712a6d29fdffd8776e2e289a 100644
590d18
--- a/ipaserver/plugins/ldap2.py
590d18
+++ b/ipaserver/plugins/ldap2.py
590d18
@@ -76,10 +76,7 @@ class ldap2(CrudBackend, LDAPClient):
590d18
         # do not set it
590d18
         pass
590d18
 
590d18
-    def _disconnect(self):
590d18
-        pass
590d18
-
590d18
-    def __del__(self):
590d18
+    def close(self):
590d18
         if self.isconnected():
590d18
             self.disconnect()
590d18
 
590d18
@@ -118,10 +115,11 @@ class ldap2(CrudBackend, LDAPClient):
590d18
         if debug_level:
590d18
             _ldap.set_option(_ldap.OPT_DEBUG_LEVEL, debug_level)
590d18
 
590d18
-        LDAPClient._connect(self)
590d18
-        conn = self._conn
590d18
+        client = LDAPClient(self.ldap_uri,
590d18
+                            force_schema_updates=self._force_schema_updates)
590d18
+        conn = client._conn
590d18
 
590d18
-        with self.error_handler():
590d18
+        with client.error_handler():
590d18
             if self.ldap_uri.startswith('ldapi://') and ccache:
590d18
                 conn.set_option(_ldap.OPT_HOST_NAME, self.api.env.host)
590d18
             minssf = conn.get_option(_ldap.OPT_X_SASL_SSF_MIN)
590d18
@@ -147,29 +145,28 @@ class ldap2(CrudBackend, LDAPClient):
590d18
                     context=krbV.default_context()).principal().name
590d18
 
590d18
             os.environ['KRB5CCNAME'] = ccache
590d18
-            self.gssapi_bind(server_controls=serverctrls,
590d18
-                             client_controls=clientctrls)
590d18
+            client.gssapi_bind(server_controls=serverctrls,
590d18
+                               client_controls=clientctrls)
590d18
             setattr(context, 'principal', principal)
590d18
         else:
590d18
             # no kerberos ccache, use simple bind or external sasl
590d18
             if autobind:
590d18
                 pent = pwd.getpwuid(os.geteuid())
590d18
-                self.external_bind(pent.pw_name,
590d18
+                client.external_bind(pent.pw_name,
590d18
+                                     server_controls=serverctrls,
590d18
+                                     client_controls=clientctrls)
590d18
+            else:
590d18
+                client.simple_bind(bind_dn, bind_pw,
590d18
                                    server_controls=serverctrls,
590d18
                                    client_controls=clientctrls)
590d18
-            else:
590d18
-                self.simple_bind(bind_dn, bind_pw,
590d18
-                                 server_controls=serverctrls,
590d18
-                                 client_controls=clientctrls)
590d18
 
590d18
         return conn
590d18
 
590d18
     def destroy_connection(self):
590d18
         """Disconnect from LDAP server."""
590d18
         try:
590d18
-            if self._conn is not None:
590d18
+            if self.conn is not None:
590d18
                 self.unbind()
590d18
-                LDAPClient._disconnect(self)
590d18
         except errors.PublicError:
590d18
             # ignore when trying to unbind multiple times
590d18
             pass
590d18
-- 
590d18
2.5.1
590d18