Skip to content

Noisy ERROR traceback from LDAPConnector.unbind() when server is unreachable (ReconnectLDAPObject has no attribute '_l') #75

Description

@jensens

Summary

When the LDAP server is unreachable, LDAPConnector.unbind() logs a full ERROR-level traceback ("Unbind not successful.") on teardown, even though the situation is handled. This pollutes logs with tracebacks on every connection failure.

Found while testing pas.plugins.ldap against node.ext.ldap 2.0.0 (Python 3.14, python-ldap 3.4.x).

Cause

In node/ext/ldap/base.py:

def unbind(self):
    """Unbind from Server."""
    if self._con is None:
        return
    try:
        self._con.unbind_s()
    except AttributeError:
        logger.exception("Unbind not successful.")   # <-- full ERROR traceback
    self._con = None

When bind() fails against an unreachable server, _con is a ReconnectLDAPObject whose underlying _l was never established. A later unbind() (e.g. from __del__) then calls unbind_s()self._l.unbind_extReconnectLDAPObject.__getattr__ raises:

AttributeError: ReconnectLDAPObject has no attribute '_l'

The except AttributeError catches it (good), but logger.exception(...) emits a full traceback at ERROR level for what is a perfectly expected "server was never reachable" teardown.

Reproduction

import logging, ldap
logging.basicConfig(level=logging.DEBUG)
from node.ext.ldap import LDAPProps, LDAPConnector

props = LDAPProps(uri="ldap://127.0.0.1:1", user="", password="", cache=False)
con = LDAPConnector(props=props)
try:
    con.bind()           # raises ldap.SERVER_DOWN (expected)
except ldap.SERVER_DOWN:
    pass
con.unbind()             # logs full "Unbind not successful." traceback at ERROR

Resulting log:

ERROR node.ext.ldap: Unbind not successful.
Traceback (most recent call last):
  File ".../node/ext/ldap/base.py", line 161, in unbind
    self._con.unbind_s()
  ...
  File ".../ldap/ldapobject.py", line 162, in __getattr__
    raise AttributeError(...)
AttributeError: ReconnectLDAPObject has no attribute '_l'

Impact

Benign (the error is caught and unbind completes), but downstream packages that are specifically designed to tolerate an unreachable LDAP server (e.g. pas.plugins.ldap, which even throttles its own LDAP-error logging) get spammed with ERROR tracebacks whenever the directory is down.

Suggested fix

Treat "connection was never established" as a non-error during unbind, e.g.:

  • log at debug instead of exception, or
  • only call unbind_s() when the connection was actually bound (guard on the missing _l / connection state), or
  • narrow the message so it does not carry a full traceback.

Happy to send a PR if you agree on the preferred approach.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions