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

> Discover how Doom extracts the LDAP naming context through source code analysis. Learn about its plain connection and LDAPS fallback methods.

- Repository: [000pp/doom](https://github.com/000pp/doom)
- Tags: internals
- Published: 2026-02-22

---

**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`](https://github.com/000pp/doom/blob/main/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`](https://github.com/000pp/doom/blob/main/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.

```python
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.

```python
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`](https://github.com/000pp/doom/blob/main/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.

```python

# 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

```python
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:

```python

# 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`](https://github.com/000pp/doom/blob/main/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`](https://github.com/000pp/doom/blob/main/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`](https://github.com/000pp/doom/blob/main/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`](https://github.com/000pp/doom/blob/main/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.