How the LDAP Connection Handler in Doom Manages Connection Failures

Doom's LDAP connection handler implements a resilient two-stage fallback strategy that first attempts unencrypted LDAP on port 389 with NTLM authentication and automatic encryption, then transparently falls back to LDAPS on port 636 when the server demands stronger authentication, converting all low-level ldap3 exceptions into clear, user-actionable error messages.

The 000pp/doom repository contains a robust LDAP integration module located in src/doom/protocols/ldap.py. This implementation demonstrates enterprise-grade connection resilience by handling authentication failures through a structured fallback mechanism rather than failing immediately on the first refused connection.

Two-Stage Connection Architecture

The handler follows a deliberate two-stage approach designed to maximize compatibility with diverse Active Directory configurations while maintaining security through opportunistic encryption.

Stage 1: Plain LDAP with Opportunistic Encryption

Initially, the code constructs an ldap3.Server instance targeting port 389 without SSL (lines 22-24). It then creates an ldap3.Connection using NTLM authentication with explicit session_security="ENCRYPT" (lines 26-34). This configuration attempts to establish an encrypted session over the standard LDAP port before considering more restrictive transport layers.

Stage 2: Automatic LDAPS Fallback

When the initial bind raises an LDAPBindError containing the string "strongerAuthRequired", the handler automatically prepares a TLS-wrapped server configuration (lines 15-21) and attempts a second connection on port 636 (lines 40-44). This fallback happens transparently to the caller, ensuring connectivity against servers that reject unsigned binds.

Granular Error Classification and Exception Translation

Rather than exposing raw ldap3 exceptions to application layers, the handler implements specific detection logic to categorize failures and raise descriptive Python exceptions.

Strong Authentication Requirements

If the first stage encounters an LDAPBindError that does not indicate "strongerAuthRequired", the code catches this in lines 40-42 and re-raises it as a generic Exception with a clear "LDAP bind error" message. This distinguishes between connectivity issues and security policy violations.

Invalid Credentials Handling

Both connection stages explicitly catch LDAPInvalidCredentialsResult (lines 44-45 for LDAP, lines 61-62 for LDAPS). Upon detection, the handler raises Exception("Invalid credentials provided"), allowing UI layers to present specific authentication failure messages without parsing LDAP result codes.

LDAPS-Specific Diagnostic Failures

When the fallback LDAPS attempt fails for reasons other than invalid credentials, the second except LDAPBindError block (lines 64-65) captures the error and re-raises it with the prefixed message LDAPS bind failed: …. This provides network administrators with precise diagnostic information distinguishing between standard LDAP and secure LDAP transport failures.

Base DN Extraction Strategies

Upon successful connection, the handler extracts the base distinguished name through protocol-specific methods. For standard LDAP connections, it retrieves base_dn from server.info.naming_contexts (lines 37-38). When operating via LDAPS, the code accesses the defaultNamingContext attribute from the server's other dictionary (lines 58-59).

NTLM Hash Normalization

Before any network activity, the handler inspects the provided password for 32-character hexadecimal strings indicating raw NTLM hashes. When detected, it automatically prefixes the hash with the constant value aad3b435b51404eeaad3b435b51404ee: (lines 12-14), forming a properly formatted NTLM hash string compatible with the ldap3 library's NTLM authentication mechanism.

Implementation Examples

The following patterns demonstrate safe integration of the LDAP handler in application code.

Safe Connection Handling

from doom.protocols.ldap import get_ldap_connection, safe_ldap_attr

try:
    conn, base_dn = get_ldap_connection(
        host="ldap.example.com",
        username="jdoe",
        password="Password123",   # or a raw NTLM hash

        domain="EXAMPLE"
    )
    print(f"Connected. Base DN = {base_dn}")

    # Fetch an entry and read attributes robustly

    conn.search(search_base=base_dn,
                search_filter='(sAMAccountName=jdoe)',
                attributes=['displayName', 'mail'])
    entry = conn.entries[0]
    display_name = safe_ldap_attr(entry, "displayName", fallback="N/A")
    email = safe_ldap_attr(entry, "mail", fallback="N/A")
    print(f"{display_name} <{email}>")

except Exception as exc:
    # All connection‑failure cases funnel here with a clear message

    print(f"LDAP error: {exc}")

UI-Level Error Handling

def login(username, password):
    try:
        conn, _ = get_ldap_connection(
            host="ldap.corp.local",
            username=username,
            password=password,
            domain="CORP"
        )
        # Proceed with application logic …

    except Exception as e:
        if "Invalid credentials" in str(e):
            show_error("Wrong username or password.")
        else:
            show_error(f"Could not connect to LDAP: {e}")

Summary

  • Two-stage fallback: The handler attempts LDAP on port 389 before automatically falling back to LDAPS on port 636 when stronger authentication is required.
  • Exception translation: Low-level ldap3 errors are mapped to clear Python exceptions, distinguishing between invalid credentials, policy violations, and transport failures.
  • Automatic hash formatting: Raw 32-character NTLM hashes are automatically prefixed with the standard aad3b435b51404eeaad3b435b51404ee: string before authentication.
  • Flexible base DN retrieval: The implementation adapts its base DN extraction logic based on whether the connection uses standard LDAP or LDAPS.
  • Located in: src/doom/protocols/ldap.py within the 000pp/doom repository.

Frequently Asked Questions

What triggers the LDAPS fallback in Doom's connection handler?

The fallback activates when the initial LDAP connection on port 389 raises an LDAPBindError containing the string "strongerAuthRequired". This indicates the server refuses the bind without channel encryption, prompting the handler to retry using TLS-wrapped LDAPS on port 636 according to the logic in lines 40-44.

How does Doom handle invalid LDAP credentials?

The code explicitly catches LDAPInvalidCredentialsResult exceptions in both the LDAP and LDAPS connection blocks (lines 44-45 and lines 61-62). It translates these into a generic Python Exception with the message "Invalid credentials provided", enabling UI layers to display user-friendly authentication errors without importing LDAP-specific constants.

Can Doom's LDAP handler process raw NTLM hashes?

Yes. Before connection attempts, the handler checks if the password parameter matches a 32-character hexadecimal pattern. When detected, it automatically prefixes the hash with aad3b435b51404eeaad3b435b51404ee: (lines 12-14), converting it into the full NTLM format required by the ldap3 library's NTLM authentication mechanism.

Where does the handler retrieve the base DN after connection?

For standard LDAP connections, the base DN is extracted from server.info.naming_contexts (lines 37-38). When connecting via LDAPS, the handler retrieves it from the defaultNamingContext key within the server's other attribute dictionary (lines 58-59).

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →