skip to content

When a directory server answers an LDAP write request, what does the LDAPResult in that reply tell the client?

level: juniorimportance: must knowfreq 48%

answer

  1. one reply shape for most operations
  2. match the reply to the request
  3. a number decides, not the text
  4. zero means applied
  5. how far the name resolved

basics

~20 s

LDAPResult is the shared reply body for Add, Modify, ModifyDN, Delete and Compare. It carries a numeric resultCode such as success(0), a matchedDN showing how far the server resolved the name, and a human-readable diagnosticMessage.

solid answer

~40 s

Every message on an LDAP connection is an `LDAPMessage`: a `messageID`, one protocol operation, and an optional `controls [0]` field. The server repeats the client's `messageID` on the reply, and that is the only correlation the protocol offers, because several operations may be outstanding and a server may answer them in any order. Almost every write reply is the same `LDAPResult`: `resultCode`, `matchedDN`, `diagnosticMessage`, and `referral [3]` when the server does not hold that part of the tree. `success (0)` means applied; anything else is a verdict — `entryAlreadyExists (68)`, `noSuchObject (32)`, `insufficientAccessRights (50)`, `unwillingToPerform (53)`. Branch on `resultCode`; `diagnosticMessage` is free text for a human and may be empty.

code

asn1 · 14 lines
asn1
LDAPMessage ::= SEQUENCE {
     messageID      MessageID,
     protocolOp     CHOICE { ... one operation ... },
     controls       [0] Controls OPTIONAL }

LDAPResult ::= SEQUENCE {
     resultCode         ENUMERATED {
          success              (0),
          noSuchObject         (32),
          entryAlreadyExists   (68),
          ... },
     matchedDN          LDAPDN,
     diagnosticMessage  LDAPString,
     referral           [3] Referral OPTIONAL }

go deeper

for a junior

Recall that the reply to a write is a result code plus a little context, not an exception and not the entry. Know that success is zero and that everything else is the server's reason.

for a middle

Explain why messageID exists at all: several operations can be in flight on one connection and the server may answer out of order. Explain what matchedDN adds on a name-resolution failure.

for a senior

Show that you read matchedDN in production triage, that you log diagnosticMessage without parsing it, and that you know a sequence of write requests is not a transaction even when each one succeeds.

for a principal

Frame the tradeoff of a uniform result envelope: one error path for every operation is cheap to implement and hard to make specific, so client error taxonomies must be built on the codes rather than the prose.

## Every message travels in the same envelope An LDAP connection carries `LDAPMessage` structures in both directions. Each one holds a **`messageID`** chosen by the client, exactly one protocol operation — an `AddRequest`, a `ModifyRequest`, a `ModifyDNRequest`, a `DelRequest`, a `CompareRequest` — and an optional `controls [0]` field. The server repeats the client's `messageID` on every message it sends in answer. That repetition is the **only** correlation the protocol gives you. A client may have several operations outstanding on one connection at the same time, and the server is free to answer them in whatever order it finishes them. Pairing a reply with a request by arrival order is a defect that behaves perfectly on a quiet test directory and misattributes results the first time two writes overlap. ## Almost every reply is the same structure The write operations do not each define a bespoke response body. Add, Modify, ModifyDN, Delete and Compare all answer with the same `LDAPResult` fields: - **`resultCode`** — an enumerated verdict. `success (0)` means the server applied the operation; every other value is the server saying why it did not. - **`matchedDN`** — filled in when the failure was one of *name resolution*. It names the deepest existing entry the server managed to resolve from the DN the client supplied. - **`diagnosticMessage`** — free text intended for a human reader. It may be an empty string, its wording differs between implementations, and nothing in the protocol constrains it. - **`referral [3]`** — present when the `resultCode` is `referral (10)`, meaning this server does not hold the named part of the tree and is pointing elsewhere. The consequence for application code is that error handling is uniform. One function reads a reply, switches on `resultCode`, and never needs to know which of the five write operations produced it. ## Reading the resultCode | resultCode | what the server is saying about a write | |---|---| | `success (0)` | the operation was applied | | `entryAlreadyExists (68)` | an Add named a DN that is already in use | | `noSuchObject (32)` | the named entry, or an ancestor of it, does not exist — read `matchedDN` | | `insufficientAccessRights (50)` | the connection's authenticated identity is not permitted to do this | | `unwillingToPerform (53)` | the server refuses on policy grounds; the request itself was well formed | | `notAllowedOnNonLeaf (66)` | a Delete named an entry that still has subordinate entries | | `compareTrue (6)` / `compareFalse (5)` | a Compare's actual answer, not a failure | The last row is the one that catches people. A Compare never answers `success (0)`; its result *is* the verdict, so code that treats every non-zero `resultCode` as an exception will report an error for a perfectly good comparison. ## matchedDN is a diagnosis, not an echo Suppose a consolidation job adds `cn=Dana Okafor,ou=Staff,dc=riverside,dc=example,dc=org` to a directory where `dc=riverside,dc=example,dc=org` was never created. The reply is `noSuchObject (32)` with `matchedDN` set to `dc=example,dc=org` — the deepest ancestor that does exist. That single field tells the client exactly which level of its assumed tree is missing, which turns a retry loop into a one-line fix: create the intermediate entries first. A client that logs only the `resultCode` throws that information away. ## What a success does and does not promise 1. **A single Modify is applied as one unit.** All of its changes take effect or none do, so a `success (0)` on a Modify means the whole change list landed. 2. **A sequence of operations is not a transaction.** Three separate requests that must all land are three separate verdicts, and the base protocol offers nothing that ties them together. A client that fails half way must be able to re-run its own work. 3. **Success means the server accepted the change as it understood it.** Values are stored under the attribute's own rules, so what comes back on a later read need not be byte-identical to what the client sent — success is a statement about acceptance, not about formatting. The practical discipline is short: correlate on `messageID`, branch on `resultCode`, read `matchedDN` when a name failed to resolve, and log `diagnosticMessage` for the human who will read the ticket — never parse it.

  • An Add fails because the parent entry does not exist. What is in matchedDN?
    The deepest existing ancestor of the DN the client named. With `noSuchObject (32)` on an Add under `dc=riverside,dc=example,dc=org`, a `matchedDN` of `dc=example,dc=org` says the intermediate entry is the missing level. The fix is to create it, not to retry the same request.
  • Can a non-zero resultCode ever mean part of a write was applied?
    Not within one Modify: its change list is applied as a single unit, so a failure leaves the entry untouched. Across requests it is different — a client that sends an Add and then two Modifies has three independent verdicts and no transaction joining them, so it must be able to resume its own sequence.
  • Why should code never branch on diagnosticMessage?
    It is free text for a human reader. The protocol allows it to be empty, and its wording is an implementation choice, so string matching on it breaks when the directory is replaced or upgraded. The `resultCode` is the enumerated value the specification defines for exactly this purpose.

saying these in an interview costs you the question

  • Expects a rejected write to close the connection rather than return a code
  • Branches on the diagnostic message text instead of the resultCode
  • Thinks matchedDN simply echoes the DN the client requested
  • Believes every operation has its own reply fields
  • Pairs replies with requests by the order they arrive
  • Treats compareTrue(6) as an error because it is not zero