]> Frank Brehm's Git Trees - pixelpark/pp-admin-tools.git/commitdiff
Rewriting lib/pp_admintools/app/ldap.py to one TCP session per request.
authorFrank Brehm <frank.brehm@pixelpark.com>
Fri, 26 Jan 2024 16:03:11 +0000 (17:03 +0100)
committerFrank Brehm <frank.brehm@pixelpark.com>
Fri, 26 Jan 2024 16:03:11 +0000 (17:03 +0100)
lib/pp_admintools/app/ldap.py
lib/pp_admintools/app/mirror_ldap.py

index 9aecf53c952345ab2a4e6b84882a72f22b2886f4..b650ba2378a2637d48f549ffdd3a474704a7d9e0 100644 (file)
@@ -37,6 +37,7 @@ from ldap3 import Connection, DSA, IP_V4_PREFERRED, SAFE_SYNC, Server
 from ldap3 import MODIFY_ADD, MODIFY_DELETE, MODIFY_REPLACE
 from ldap3.core.exceptions import LDAPBindError
 from ldap3.core.exceptions import LDAPException
+from ldap3.core.exceptions import LDAPSessionTerminatedByServerError
 from ldap3.core.exceptions import LDAPSocketOpenError
 from ldap3.core.exceptions import LDAPSocketReceiveError
 
@@ -49,7 +50,7 @@ from ..config.ldap import DEFAULT_TIMEOUT
 from ..config.ldap import LdapConfiguration, LdapConnectionInfo
 from ..xlate import XLATOR, format_list
 
-__version__ = '0.12.3'
+__version__ = '1.0.0'
 LOG = logging.getLogger(__name__)
 
 _ = XLATOR.gettext
@@ -104,6 +105,13 @@ class LDAPParseError(FatalLDAPError):
     pass
 
 
+# =============================================================================
+class LdapSessionError(FatalLDAPError):
+    """Error in session handling with LDAP."""
+
+    pass
+
+
 # =============================================================================
 class PasswordFileOptionAction(argparse.Action):
     """Argparse action for a password file."""
@@ -181,6 +189,7 @@ class BaseLdapApplication(BaseDPXApplication):
     apply_default_ldap_instance_if_not_given = True
     default_default_ldap_instance = 'default'
     default_wait_on_read_error = 1
+    default_single_session = False
 
     # pattern_re_ldap_dn = (
     # '^([a-z][a-z0-9-]*)=(?![ #])(((?![\\="+,;<>]).)|(\\[ \\#="+,;<>])|(\\[a-f0-9][a-f0-9]))*'
@@ -267,13 +276,15 @@ class BaseLdapApplication(BaseDPXApplication):
         self, appname=None, verbose=0, version=GLOBAL_VERSION, base_dir=None,
             cfg_class=LdapConfiguration, initialized=False, usage=None, description=None,
             argparse_epilog=None, argparse_prefix_chars='-', env_prefix=None,
-            config_dir=DEFAULT_CONFIG_DIR, wait_on_read_error=None):
+            config_dir=DEFAULT_CONFIG_DIR, wait_on_read_error=None,
+            single_session=None):
         """Contrict the application object."""
         self._password_file = None
         self.ldap_instances = []
         self.ldap_server = {}
         self.ldap_connection = {}
         self._wait_on_read_error = self.default_wait_on_read_error
+        self._single_session = self.default_single_session
 
         super(BaseLdapApplication, self).__init__(
             appname=appname, verbose=verbose, version=version, base_dir=base_dir,
@@ -285,6 +296,9 @@ class BaseLdapApplication(BaseDPXApplication):
         if wait_on_read_error:
             self.wait_on_read_error = wait_on_read_error
 
+        if single_session is not None:
+            self.single_session = single_session
+
     # -----------------------------------------------------------
     @property
     def password_file(self):
@@ -328,6 +342,24 @@ class BaseLdapApplication(BaseDPXApplication):
             raise ValueError(msg)
         self._wait_on_read_error = val
 
+    # -----------------------------------------------------------
+    @property
+    def single_session(self):
+        """
+        Execute all LDAP operations in one single TCP session.
+
+        Otherwise each read or write operation will be executed
+        in a separate TCP session.
+        """
+        return self._single_session
+
+    @single_session.setter
+    def single_session(self, value):
+        if value is None:
+            self._single_session = self.default_single_session
+            return
+        self._single_session = to_bool(value)
+
     # -------------------------------------------------------------------------
     def as_dict(self, short=True):
         """
@@ -344,6 +376,7 @@ class BaseLdapApplication(BaseDPXApplication):
         res['default_default_ldap_instance'] = self.default_default_ldap_instance
         res['pattern_re_ldap_dn'] = self.pattern_re_ldap_dn
         res['password_file'] = self.password_file
+        res['single_session'] = self.single_session
         res['show_cmdline_ldap_timeout'] = self.show_cmdline_ldap_timeout
         res['use_default_ldap_connection'] = self.use_default_ldap_connection
         res['use_multiple_ldap_connections'] = self.use_multiple_ldap_connections
@@ -756,17 +789,18 @@ class BaseLdapApplication(BaseDPXApplication):
         LOG.debug(_('Preparations ...'))
         super(BaseLdapApplication, self).pre_run()
 
-        LOG.debug(_('Open all necessary LDAP connections ...'))
+        if self.single_session:
+            LOG.debug(_('Open all necessary LDAP connections ...'))
 
-        for inst in self.ldap_instances:
-            self.connect_instance(inst)
+            for inst in self.ldap_instances:
+                self.connect_instance(inst, single=True)
 
     # -------------------------------------------------------------------------
-    def connect_instance(self, inst):
+    def connect_instance(self, inst, single=False):
         """Connect to the given LDAP instance."""
         connect_info = self.cfg.ldap_connection[inst]
 
-        ldap_server = self.get_ldap_server_obj(inst)
+        ldap_server = self.get_ldap_server_obj(inst, single=single)
         self.ldap_server[inst] = ldap_server
 
         if not connect_info.bind_pw:
@@ -775,20 +809,26 @@ class BaseLdapApplication(BaseDPXApplication):
                 inst=self.colored(connect_info.url, 'CYAN')) + ' '
             connect_info.bind_pw = self.get_password(first_prompt, may_empty=False, repeat=False)
 
-        ldap_connection = self.connect_to_ldap_server(ldap_server, inst)
+        ldap_connection = self.connect_to_ldap_server(ldap_server, inst, single=single)
         self.ldap_connection[inst] = ldap_connection
 
-        if self.verbose > 2:
+        min_verb_level = 3
+        if single:
+            min_verb_level = 2
+        if self.verbose > min_verb_level:
             msg = _('Info about LDAP server {}:').format(connect_info.url)
             msg += ' ' + repr(ldap_connection)
             LOG.debug(msg)
 
     # -------------------------------------------------------------------------
-    def get_ldap_server_obj(self, inst):
+    def get_ldap_server_obj(self, inst, single=False):
         """Return the ldap3-Server object for the given instance."""
         connect_info = self.cfg.ldap_connection[inst]
 
-        if self.verbose > 2:
+        min_verb_level = 3
+        if single:
+            min_verb_level = 2
+        if self.verbose > min_verb_level:
             msg = _('Trying to get LDAP server object for {} ...').format(connect_info.url)
             LOG.debug(msg)
 
@@ -804,20 +844,20 @@ class BaseLdapApplication(BaseDPXApplication):
         server_opts['get_info'] = DSA
         server_opts['mode'] = IP_V4_PREFERRED
         server_opts['connect_timeout'] = self.cfg.ldap_timeout
-        if self.verbose > 2:
+        if self.verbose > min_verb_level:
             msg = _('Connect options to server {!r}:').format(connect_info.url)
             msg += ' ' + pp(server_opts)
             LOG.debug(msg)
 
         ldap_server = Server(connect_info.host, **server_opts)
 
-        if self.verbose > 2:
+        if self.verbose > min_verb_level:
             LOG.debug(_('LDAP server {s}: {re}').format(s=ldap_server, re=repr(ldap_server)))
 
         return ldap_server
 
     # -------------------------------------------------------------------------
-    def connect_to_ldap_server(self, ldap_server, inst, bind_dn=None, bind_pw=None):
+    def connect_to_ldap_server(self, ldap_server, inst, bind_dn=None, bind_pw=None, single=False):
         """Connect to the given LDAP server."""
         connect_info = self.cfg.ldap_connection[inst]
         if not bind_dn:
@@ -825,7 +865,11 @@ class BaseLdapApplication(BaseDPXApplication):
         if not bind_pw:
             bind_pw = connect_info.bind_pw
 
-        if self.verbose > 1:
+        min_verb_level = 2
+        if single:
+            min_verb_level = 1
+
+        if self.verbose > min_verb_level:
             msg = _('Connecting to LDAP server {url} as {dn!r} ...').format(
                 url=connect_info.url, dn=bind_dn)
             LOG.debug(msg)
@@ -863,24 +907,29 @@ class BaseLdapApplication(BaseDPXApplication):
                 LOG.debug(_('Disconnecting from all remaining LDAP instances ...'))
 
                 for inst in self.ldap_instances:
-                    self.disconnect_instance(inst)
+                    self.disconnect_instance(inst, single=True)
 
     # -------------------------------------------------------------------------
-    def disconnect_instance(self, inst):
+    def disconnect_instance(self, inst, single=False):
         """Disconnect from the given instance."""
         connect_info = self.cfg.ldap_connection[inst]
 
+        min_verb_level = 2
+        if single:
+            min_verb_level = 1
+
         if inst in self.ldap_connection:
             ldap_connection = self.ldap_connection[inst]
-            if self.verbose > 1:
+            if self.verbose > min_verb_level:
                 LOG.debug(_('Unbinding from LDAP server {!r} ...').format(connect_info.url))
             ldap_connection.unbind()
             ldap_connection = None
             del self.ldap_connection[inst]
 
-        if inst in self.ldap_server:
-            if self.verbose > 1:
-                LOG.debug(_('Disconnecting from LDAP server {!r} ...').format(connect_info.url))
+        if inst in self.ldap_server and single:
+            if self.verbose > min_verb_level:
+                LOG.debug(_('Removing LDAP server connection data {!r} ...').format(
+                    connect_info.url))
             del self.ldap_server[inst]
 
     # -------------------------------------------------------------------------
@@ -893,7 +942,7 @@ class BaseLdapApplication(BaseDPXApplication):
         with the resulting attributes as values.
         """
         connect_info = self.cfg.ldap_connection[inst]
-        ldap = self.ldap_connection[inst]
+
         if scope is None:
             scope = SUBTREE
 
@@ -913,9 +962,17 @@ class BaseLdapApplication(BaseDPXApplication):
             msg += ' ' + format_list(attributes, do_repr=True)
             LOG.debug(msg)
 
-        req_status, req_result, req_response, req_whatever = ldap.search(
-            search_base=base_dn, search_scope=scope, attributes=attributes,
-            search_filter=ldap_filter, time_limit=self.cfg.ldap_timeout)
+        if inst not in self.ldap_connection:
+            self.connect_instance(inst)
+        ldap = self.ldap_connection[inst]
+
+        try:
+            req_status, req_result, req_response, req_whatever = ldap.search(
+                search_base=base_dn, search_scope=scope, attributes=attributes,
+                search_filter=ldap_filter, time_limit=self.cfg.ldap_timeout)
+        finally:
+            if not self.single_session:
+                self.disconnect_instance(inst)
 
         if req_status:
             if self.verbose > 4:
@@ -950,9 +1007,9 @@ class BaseLdapApplication(BaseDPXApplication):
             self, inst, ldap_filter=None, base_dn=None, scope=None, no_complain=False):
         """Get DNs of all entries in the given LDAP instance and sort them."""
         connect_info = self.cfg.ldap_connection[inst]
+
         if not base_dn:
             base_dn = connect_info.base_dn
-        ldap = self.ldap_connection[inst]
         if scope is None:
             scope = SUBTREE
 
@@ -965,10 +1022,18 @@ class BaseLdapApplication(BaseDPXApplication):
         if self.verbose > 1:
             LOG.debug(_('Using LDAP filter: {!r}').format(ldap_filter))
 
-        req_status, req_result, req_response, req_whatever = ldap.search(
-            search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
-            get_operational_attributes=False, attributes=attributes,
-            time_limit=self.cfg.ldap_timeout)
+        if inst not in self.ldap_connection:
+            self.connect_instance(inst)
+        ldap = self.ldap_connection[inst]
+
+        try:
+            req_status, req_result, req_response, req_whatever = ldap.search(
+                search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
+                get_operational_attributes=False, attributes=attributes,
+                time_limit=self.cfg.ldap_timeout)
+        finally:
+            if not self.single_session:
+                self.disconnect_instance(inst)
 
         if req_status:
             if self.verbose > 2:
@@ -1006,7 +1071,6 @@ class BaseLdapApplication(BaseDPXApplication):
         """Get Object classes and DNs of all entries in the given LDAP instance."""
         connect_info = self.cfg.ldap_connection[inst]
         base_dn = connect_info.base_dn
-        ldap = self.ldap_connection[inst]
 
         result = CIDict()
         attributes = ['objectClass']
@@ -1019,10 +1083,18 @@ class BaseLdapApplication(BaseDPXApplication):
         if self.verbose > 1:
             LOG.debug(_('Using LDAP filter: {!r}').format(ldap_filter))
 
-        req_status, req_result, req_response, req_whatever = ldap.search(
-            search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
-            get_operational_attributes=False, attributes=attributes,
-            time_limit=self.cfg.ldap_timeout)
+        if inst not in self.ldap_connection:
+            self.connect_instance(inst)
+        ldap = self.ldap_connection[inst]
+
+        try:
+            req_status, req_result, req_response, req_whatever = ldap.search(
+                search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
+                get_operational_attributes=False, attributes=attributes,
+                time_limit=self.cfg.ldap_timeout)
+        finally:
+            if not self.single_session:
+                self.disconnect_instance(inst)
 
         if req_status:
             if self.verbose > 5:
@@ -1093,7 +1165,6 @@ class BaseLdapApplication(BaseDPXApplication):
         """Get the DN of the user with the given mail address in the given LDAP instance."""
         connect_info = self.cfg.ldap_connection[inst]
         base_dn = connect_info.base_dn
-        ldap = self.ldap_connection[inst]
 
         result = []
 
@@ -1117,10 +1188,18 @@ class BaseLdapApplication(BaseDPXApplication):
                 uri=connect_info.url, bdn=base_dn, fltr=ldap_filter)
             LOG.debug(msg)
 
-        req_status, req_result, req_response, req_whatever = ldap.search(
-            search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
-            get_operational_attributes=False, attributes=attributes,
-            time_limit=self.cfg.ldap_timeout)
+        if inst not in self.ldap_connection:
+            self.connect_instance(inst)
+        ldap = self.ldap_connection[inst]
+
+        try:
+            req_status, req_result, req_response, req_whatever = ldap.search(
+                search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
+                get_operational_attributes=False, attributes=attributes,
+                time_limit=self.cfg.ldap_timeout)
+        finally:
+            if not self.single_session:
+                self.disconnect_instance(inst)
 
         if req_status:
             if self.verbose > 4:
@@ -1146,7 +1225,6 @@ class BaseLdapApplication(BaseDPXApplication):
         """Get the DN of the user with the given uid (POSIX name) in the given LDAP instance."""
         connect_info = self.cfg.ldap_connection[inst]
         base_dn = connect_info.base_dn
-        ldap = self.ldap_connection[inst]
 
         result = []
 
@@ -1170,10 +1248,18 @@ class BaseLdapApplication(BaseDPXApplication):
                 uri=connect_info.url, bdn=base_dn, fltr=ldap_filter)
             LOG.debug(msg)
 
-        req_status, req_result, req_response, req_whatever = ldap.search(
-            search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
-            get_operational_attributes=False, attributes=attributes,
-            time_limit=self.cfg.ldap_timeout)
+        if inst not in self.ldap_connection:
+            self.connect_instance(inst)
+        ldap = self.ldap_connection[inst]
+
+        try:
+            req_status, req_result, req_response, req_whatever = ldap.search(
+                search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
+                get_operational_attributes=False, attributes=attributes,
+                time_limit=self.cfg.ldap_timeout)
+        finally:
+            if not self.single_session:
+                self.disconnect_instance(inst)
 
         if req_status:
             if self.verbose > 4:
@@ -1199,7 +1285,6 @@ class BaseLdapApplication(BaseDPXApplication):
         """Get the DN of the user with the given cn (common name) in the given LDAP instance."""
         connect_info = self.cfg.ldap_connection[inst]
         base_dn = connect_info.base_dn
-        ldap = self.ldap_connection[inst]
 
         result = []
 
@@ -1223,10 +1308,18 @@ class BaseLdapApplication(BaseDPXApplication):
                 uri=connect_info.url, bdn=base_dn, fltr=ldap_filter)
             LOG.debug(msg)
 
-        req_status, req_result, req_response, req_whatever = ldap.search(
-            search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
-            get_operational_attributes=False, attributes=attributes,
-            time_limit=self.cfg.ldap_timeout)
+        if inst not in self.ldap_connection:
+            self.connect_instance(inst)
+        ldap = self.ldap_connection[inst]
+
+        try:
+            req_status, req_result, req_response, req_whatever = ldap.search(
+                search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
+                get_operational_attributes=False, attributes=attributes,
+                time_limit=self.cfg.ldap_timeout)
+        finally:
+            if not self.single_session:
+                self.disconnect_instance(inst)
 
         if req_status:
             if self.verbose > 4:
@@ -1298,7 +1391,6 @@ class BaseLdapApplication(BaseDPXApplication):
     def get_entry(self, dn, inst, attributes=None, operational_attributes=False, tries=3):
         """Get an complete LDAP entry by the given DN in the given instance."""
         connect_info = self.cfg.ldap_connection[inst]
-        ldap = self.ldap_connection[inst]
 
         if attributes is None:
             attributes = [ALL_ATTRIBUTES]
@@ -1314,6 +1406,10 @@ class BaseLdapApplication(BaseDPXApplication):
         cur_try = 0
         e_msg = None
         while cur_try < tries:
+
+            if inst not in self.ldap_connection:
+                self.connect_instance(inst)
+            ldap = self.ldap_connection[inst]
             e_msg = None
             cur_try += 1
             try:
@@ -1321,6 +1417,7 @@ class BaseLdapApplication(BaseDPXApplication):
                     search_base=dn, search_scope=BASE, attributes=attributes,
                     get_operational_attributes=operational_attributes, search_filter=sfilter,
                     time_limit=self.cfg.ldap_timeout)
+
             except (LDAPSocketReceiveError, LDAPSocketOpenError) as e:
                 e_msg = str(e)
                 if cur_try >= tries:
@@ -1328,6 +1425,15 @@ class BaseLdapApplication(BaseDPXApplication):
                 LOG.debug(_('Waiting because of a failing read operation.'))
                 time.sleep(self.wait_on_read_error)
 
+            except LDAPSessionTerminatedByServerError as e:
+                msg = _('Session terminated on reading entry {dn!r} from instance {i!r}:').format(
+                    dn=dn, i=inst) + ' ' + str(e)
+                raise LdapSessionError(msg)
+
+            finally:
+                if not self.single_session:
+                    self.disconnect_instance(inst)
+
         if e_msg:
             msg = _('Error on reading entry {dn!r} from instance {inst!r}:').format(
                 dn=dn, inst=inst) + ' ' + e_msg
@@ -1384,8 +1490,7 @@ class BaseLdapApplication(BaseDPXApplication):
     def add_entry(self, inst, dn, object_classes, target_entry, ldap=None):
         """Create a LDAP entry."""
         connect_info = self.cfg.ldap_connection[inst]
-        if not ldap:
-            ldap = self.ldap_connection[inst]
+        keep_ldap = False
 
         if self.verbose > 2:
             msg = _('Creating entry {dn!r} on {uri}:').format(
@@ -1397,6 +1502,13 @@ class BaseLdapApplication(BaseDPXApplication):
             LOG.info(_('Simulation mode - entry will not be created.'))
             return True
 
+        if ldap:
+            keep_ldap = True
+        else:
+            if inst not in self.ldap_connection:
+                self.connect_instance(inst)
+            ldap = self.ldap_connection[inst]
+
         try:
             req_status, req_result, req_response, req_whatever = ldap.add(
                 dn, object_class=object_classes, attributes=target_entry)
@@ -1406,6 +1518,9 @@ class BaseLdapApplication(BaseDPXApplication):
             msg += '\nobjectClasses:\n' + pp(object_classes)
             msg += '\nAttributes:\n' + pp(target_entry)
             raise WriteLDAPItemError(msg)
+        finally:
+            if not self.single_session or not keep_ldap:
+                self.disconnect_instance(inst)
 
         # Example result on a not successful modification:
         # {     'description': 'objectClassViolation',
@@ -1434,8 +1549,7 @@ class BaseLdapApplication(BaseDPXApplication):
     def modify_entry(self, inst, dn, changes, ldap=None):
         """Mofify an existing LDAP entry."""
         connect_info = self.cfg.ldap_connection[inst]
-        if not ldap:
-            ldap = self.ldap_connection[inst]
+        keep_ldap = False
 
         if self.verbose > 1:
             msg = _('Applying changes on {uri} to DN {dn!r}:').format(
@@ -1446,6 +1560,13 @@ class BaseLdapApplication(BaseDPXApplication):
             LOG.info(_('Simulation mode - changes are not applied.'))
             return True
 
+        if ldap:
+            keep_ldap = True
+        else:
+            if inst not in self.ldap_connection:
+                self.connect_instance(inst)
+            ldap = self.ldap_connection[inst]
+
         try:
             req_status, req_result, req_response, req_whatever = ldap.modify(dn, changes)
         except LDAPException as e:
@@ -1453,6 +1574,9 @@ class BaseLdapApplication(BaseDPXApplication):
                 dn=dn, c=e.__class__.__name__, e=e)
             msg += '\n' + _('Changes:') + '\n' + pp(changes)
             raise WriteLDAPItemError(msg)
+        finally:
+            if not self.single_session or not keep_ldap:
+                self.disconnect_instance(inst)
 
         # Example result on a not successful modification:
         # {     'description': 'objectClassViolation',
@@ -1480,8 +1604,7 @@ class BaseLdapApplication(BaseDPXApplication):
     def delete_entry(self, inst, dn, ldap=None):
         """Delete a LDAP entry."""
         connect_info = self.cfg.ldap_connection[inst]
-        if not ldap:
-            ldap = self.ldap_connection[inst]
+        keep_ldap = False
 
         msg = _('Deleting LDAP entry {dn!r} on {uri} ...').format(
             uri=connect_info.url, dn=dn)
@@ -1491,12 +1614,22 @@ class BaseLdapApplication(BaseDPXApplication):
             LOG.info(_('Simulation mode - deletion will not be executed.'))
             return True
 
+        if ldap:
+            keep_ldap = True
+        else:
+            if inst not in self.ldap_connection:
+                self.connect_instance(inst)
+            ldap = self.ldap_connection[inst]
+
         try:
             req_status, req_result, req_response, req_whatever = ldap.delete(dn)
         except LDAPException as e:
             msg = _('Deletion of {dn!r} was NOT successfull - {c}: {e}').format(
                 c=e.__class__.__name__, e=e)
             raise DeleteLDAPItemError(msg)
+        finally:
+            if not self.single_session or not keep_ldap:
+                self.disconnect_instance(inst)
 
         if self.verbose > 1:
             LOG.debug(_('Deletion status: {!r}.').format(req_status))
@@ -1516,7 +1649,6 @@ class BaseLdapApplication(BaseDPXApplication):
     def get_group_memberships(self, inst, dn, base_dn=None):
         """Return a list with DNs of all groups containing the given DN as a member."""
         connect_info = self.cfg.ldap_connection[inst]
-        ldap = self.ldap_connection[inst]
 
         if not base_dn:
             base_dn = connect_info.base_dn
@@ -1531,10 +1663,18 @@ class BaseLdapApplication(BaseDPXApplication):
                 uri=connect_info.url, bdn=base_dn, fltr=ldap_filter)
             LOG.debug(msg)
 
-        req_status, req_result, req_response, req_whatever = ldap.search(
-            search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
-            get_operational_attributes=False, attributes=attributes,
-            time_limit=self.cfg.ldap_timeout)
+        if inst not in self.ldap_connection:
+            self.connect_instance(inst)
+        ldap = self.ldap_connection[inst]
+
+        try:
+            req_status, req_result, req_response, req_whatever = ldap.search(
+                search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
+                get_operational_attributes=False, attributes=attributes,
+                time_limit=self.cfg.ldap_timeout)
+        finally:
+            if not self.single_session:
+                self.disconnect_instance(inst)
 
         if req_status:
             for entry in req_response:
@@ -1550,7 +1690,6 @@ class BaseLdapApplication(BaseDPXApplication):
     def get_unique_group_memberships(self, inst, dn, base_dn=None):
         """Return a list with DNs of all groups containing the given DN as a unique member."""
         connect_info = self.cfg.ldap_connection[inst]
-        ldap = self.ldap_connection[inst]
 
         if not base_dn:
             base_dn = connect_info.base_dn
@@ -1565,10 +1704,18 @@ class BaseLdapApplication(BaseDPXApplication):
                 uri=connect_info.url, bdn=base_dn, fltr=ldap_filter)
             LOG.debug(msg)
 
-        req_status, req_result, req_response, req_whatever = ldap.search(
-            search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
-            get_operational_attributes=False, attributes=attributes,
-            time_limit=self.cfg.ldap_timeout)
+        if inst not in self.ldap_connection:
+            self.connect_instance(inst)
+        ldap = self.ldap_connection[inst]
+
+        try:
+            req_status, req_result, req_response, req_whatever = ldap.search(
+                search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
+                get_operational_attributes=False, attributes=attributes,
+                time_limit=self.cfg.ldap_timeout)
+        finally:
+            if not self.single_session:
+                self.disconnect_instance(inst)
 
         if req_status:
             for entry in req_response:
@@ -1584,7 +1731,6 @@ class BaseLdapApplication(BaseDPXApplication):
     def get_posix_group_memberships(self, inst, uid, base_dn=None):
         """Return a list with DNs of all POSIX groups containing the given UID as a member."""
         connect_info = self.cfg.ldap_connection[inst]
-        ldap = self.ldap_connection[inst]
 
         if not base_dn:
             base_dn = connect_info.base_dn
@@ -1599,10 +1745,18 @@ class BaseLdapApplication(BaseDPXApplication):
                 uri=connect_info.url, bdn=base_dn, fltr=ldap_filter)
             LOG.debug(msg)
 
-        req_status, req_result, req_response, req_whatever = ldap.search(
-            search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
-            get_operational_attributes=False, attributes=attributes,
-            time_limit=self.cfg.ldap_timeout)
+        if inst not in self.ldap_connection:
+            self.connect_instance(inst)
+        ldap = self.ldap_connection[inst]
+
+        try:
+            req_status, req_result, req_response, req_whatever = ldap.search(
+                search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
+                get_operational_attributes=False, attributes=attributes,
+                time_limit=self.cfg.ldap_timeout)
+        finally:
+            if not self.single_session:
+                self.disconnect_instance(inst)
 
         if req_status:
             for entry in req_response:
@@ -1618,7 +1772,6 @@ class BaseLdapApplication(BaseDPXApplication):
     def get_sudo_group_memberships(self, inst, uid, base_dn=None):
         """Return a list with DNs of all sudo roles containing the given UID as a member."""
         connect_info = self.cfg.ldap_connection[inst]
-        ldap = self.ldap_connection[inst]
 
         if not base_dn:
             base_dn = connect_info.base_dn
@@ -1633,10 +1786,18 @@ class BaseLdapApplication(BaseDPXApplication):
                 uri=connect_info.url, bdn=base_dn, fltr=ldap_filter)
             LOG.debug(msg)
 
-        req_status, req_result, req_response, req_whatever = ldap.search(
-            search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
-            get_operational_attributes=False, attributes=attributes,
-            time_limit=self.cfg.ldap_timeout)
+        if inst not in self.ldap_connection:
+            self.connect_instance(inst)
+        ldap = self.ldap_connection[inst]
+
+        try:
+            req_status, req_result, req_response, req_whatever = ldap.search(
+                search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
+                get_operational_attributes=False, attributes=attributes,
+                time_limit=self.cfg.ldap_timeout)
+        finally:
+            if not self.single_session:
+                self.disconnect_instance(inst)
 
         if req_status:
             for entry in req_response:
index 960df25b834c1fd6b46bf74d8395fac54c5ff290..a1522d5046750e67fa8484d4a54f3d7ca01664eb 100644 (file)
@@ -31,13 +31,14 @@ from ldap3.core.exceptions import LDAPSocketReceiveError
 from .ldap import BaseLdapApplication
 from .ldap import LdapAppError
 from .ldap import LdapReadError
+from .ldap import LdapSessionError
 from .. import pp
 from ..argparse_actions import LimitedFloatOptionAction
 from ..argparse_actions import NonNegativeIntegerOptionAction
 from ..config.mirror_ldap import MirrorLdapConfiguration
 from ..xlate import XLATOR
 
-__version__ = '1.1.1'
+__version__ = '1.1.2'
 LOG = logging.getLogger(__name__)
 
 _ = XLATOR.gettext
@@ -338,8 +339,9 @@ class MirrorLdapApplication(BaseLdapApplication):
 
                 self.empty_line()
 
-        except LdapReadError as e:
-            msg = _('Got a {}:').format('LdapReadError') + ' ' + str(e)
+        except (LdapReadError, LdapSessionError) as e:
+            cls = e.__class__.__name__
+            msg = _('Got a {}:').format(cls) + ' ' + str(e)
             LOG.error(msg)
             self.exit(11)