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_connectionhelper insrc/doom/protocols/ldap.py. - For plain LDAP connections, it extracts the first element from
server.info.naming_contexts. - For LDAPS connections, it retrieves the
defaultNamingContextattribute from the server's Root DSE. - The UI layer in
src/doom/screens/loading_screen.pystores 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →