How Doom Extracts the Naming Context for LDAP Queries: Source Code Analysis

Doom extracts the LDAP naming context by first attempting a plain connection and reading naming_contexts[0] from the Root DSE, or falling back to LDAPS and retrieving the defaultNamingContext attribute from src/doom/protocols/ldap.py.

Doom (000pp/doom) is an open-source security tool that interacts with directory services to perform reconnaissance and authentication tasks. When connecting to Active Directory or other LDAP servers, Doom must dynamically discover the naming context—the base distinguished name (DN) that serves as the root for all subsequent queries. This article examines how Doom extracts the naming context for LDAP queries directly from the source code, handling both standard LDAP and encrypted LDAPS connections.

The Naming Context Extraction Logic in get_ldap_connection

The core logic resides in src/doom/protocols/ldap.py within the get_ldap_connection function. This helper attempts connections sequentially, extracting the base DN differently depending on whether the server requires TLS encryption.

Standard LDAP Discovery (Port 389)

When connecting via plain LDAP on port 389, Doom binds to the server and accesses the Root DSE (Directory System Agent Specific Entry) information. The naming_contexts list provided by the LDAP server contains available naming contexts, and Doom selects the first entry as the default base DN.

base_dn = ldap_connection.server.info.naming_contexts[0]   # src/doom/protocols/ldap.py L37-L38

LDAPS Fallback Mechanism (Port 636)

If the plain connection fails—typically because the server mandates TLS—Doom falls back to LDAPS on port 636. In this scenario, the code searches the other dictionary within the server info for the defaultNamingContext attribute, which Active Directory populates with the domain's default naming context.

base_dn = ldaps_connection.server.info.other.get(
    "defaultNamingContext", ["<none>"]
)[0]                                                   # src/doom/protocols/ldap.py L58-L59

Integrating Naming Context Discovery in the Application Layer

The get_ldap_connection function returns a tuple (connection, base_dn) that downstream components consume. In src/doom/screens/loading_screen.py (lines 72-89), the UI layer asynchronously invokes this helper during authentication and stores the resulting base DN for use in subsequent search operations.


# Inside LoadingScreen.authenticate_ldap()

connection_result = await asyncio.to_thread(
    get_ldap_connection,
    host=self.login_data.get('ip', ''),
    username=self.login_data.get('username', ''),
    password=self.login_data.get('password', ''),
    domain=self.login_data.get('domain', '')
)

if connection_result:
    self.ldap_connection, self.base_dn = connection_result
    # base_dn is now ready for downstream LDAP queries

Practical Code Examples

Obtaining a Connection and Base DN

from doom.protocols.ldap import get_ldap_connection

# Parameters collected from the login screen

host = "ldap.example.com"
username = "jdoe"
password = "Secret123!"
domain = "EXAMPLE"

# Returns (ldap_connection, base_dn) or raises an exception

conn, base_dn = get_ldap_connection(host, username, password, domain)

print("Connected to:", conn.server)
print("Base DN for searches:", base_dn)

Manual Naming Context Retrieval

If you already possess an ldap3.Connection object, you can replicate Doom's logic to extract the naming context:


# Assume `conn` is a bound ldap3.Connection

if hasattr(conn.server.info, "naming_contexts"):
    # LDAP (non-TLS) case

    naming_context = conn.server.info.naming_contexts[0]
else:
    # LDAPS case – look for the defaultNamingContext entry

    naming_context = conn.server.info.other.get("defaultNamingContext", ["<none>"])[0]

print("Naming context:", naming_context)

Summary

  • Doom discovers the LDAP naming context dynamically through the get_ldap_connection helper in src/doom/protocols/ldap.py.
  • For plain LDAP connections, it extracts the first element from server.info.naming_contexts.
  • For LDAPS connections, it retrieves the defaultNamingContext attribute from the server's Root DSE.
  • The UI layer in src/doom/screens/loading_screen.py stores the returned base DN for subsequent directory searches.
  • If no naming context is found in LDAPS mode, the function returns "<none>" as a placeholder.

Frequently Asked Questions

What is a naming context in LDAP?

A naming context represents the top-level entry in an LDAP directory tree, essentially the base distinguished name (DN) under which all other entries reside. It defines the boundary of the directory namespace that the server manages, such as DC=example,DC=com in Active Directory environments.

Why does Doom use different attributes for LDAP versus LDAPS?

Doom uses naming_contexts for plain LDAP because this attribute is standard in the Root DSE for most LDAP implementations. However, when connecting via LDAPS, the code specifically looks for defaultNamingContext, which is the Active Directory-specific attribute that indicates the default domain naming context, ensuring compatibility with Windows domain controllers that may structure their Root DSE differently.

Where does Doom store the extracted naming context?

After extraction in src/doom/protocols/ldap.py, the naming context is returned as the second element of a tuple from get_ldap_connection. The calling code in src/doom/screens/loading_screen.py assigns this value to self.base_dn, making it available to other UI components and search functions throughout the application lifecycle.

What happens if the LDAPS server does not provide a default naming context?

If the defaultNamingContext attribute is missing from the LDAPS Root DSE, Doom's code returns "<none>" as a fallback string. This prevents the application from crashing and signals to downstream components that no valid base DN was discovered, requiring manual configuration or alternative discovery methods.

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 →