# AGENTS.md ## What this is A standalone LDAPv3 **read-only bridge** in front of a NetAuth server (`github.com/netauth/netauth`). It translates LDAP bind/search operations into NetAuth RPC calls via the `netauth/protocol` protobuf types. It is not a full LDAP server and never stores data; it only reshapes and serves live NetAuth data. Single static binary (`ldapd`), intended to run bound to localhost. ## Commands ```sh go build ./... go vet ./... go test ./... # or: go test -race ./internal/ldap/ go run . # needs a config file or NETAUTH_LDAP_DOMAIN go mod vendor # repo vendors deps; vendor/ and dist/ are gitignored # Production-style build (matches Dockerfile) CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o ldapd . ``` CI ([.github/workflows/test.yml](.github/workflows/test.yml)) runs build, vet and `go test -race` on pushes to master and on PRs. Release is via [goreleaser](.goreleaser.yml) triggered by `v*` tags in [.github/workflows/release.yml](.github/workflows/release.yml); its before-hooks run `go mod vendor` and `scripts/prepare.sh` (regenerates `NOTICE` from vendored license files via `scripts/vendor-licenses`, which needs the vendor tree present). Note `dist/config.yaml` is a stale generated artifact, not the config actually used — the real one is `.goreleaser.yml` at the repo root. ## Layout ``` main.go # wiring only: config, logging, client, serve internal/ldap/ # the LDAP server (package ldap) ldap.go # New(), route (re)building, Serve/ServeTLS, SetDomain handler.go # panic guard wrapper + per-op failure responders conn.go # per-connection bind state (bound/markBound/markUnbound) type.go # server struct + naClient interface (the seam for fakes) option.go # functional options: WithLogger, WithNetAuth, WithAnonBind bind.go # bind handler (auth against NetAuth) search.go # RootDSE, entity & group search handlers attributes.go # RFC 4511 attribute selection matching bleve_mapper.go # LDAP filter -> Bleve query string translation util.go # DN parsing (entityIDFromDN) *_test.go # tests; see "Testing" below internal/buildinfo/ # Version var, advertised in RootDSE; set by goreleaser ``` Architecture: `main` builds a `viper` config + `netauth` client, constructs the server with functional options, and calls `Serve`/`ServeTLS`. The underlying LDAP engine is a `RouteMux` (net/http-style routing on LDAP ops) with handlers keyed by base DN and scope. Every handler is wrapped by `server.guard` so a panic cannot take the process down (the vendored server dispatches requests in goroutines with no recovery of its own). ## Non-obvious gotchas - **Forked LDAP libraries**: the server stack is `github.com/ps78674/ldapserver` + `github.com/ps78674/goldap`, not the more common `nwaples/ldapserver` / `go-ldap` packages. Import as `ldap "github.com/ps78674/ldapserver"`. Check the vendored API before assuming upstream signatures. The fork has real limits: TLS floor is SSL 3.0, `Server.Handle` calls `log.Fatalln` on double registration, and there is no panic recovery in the request path. - **The server struct is its own Handler**: `New()` registers `*server` once (the fork forbids re-registration), and `ServeLDAP` delegates to whatever `s.routes` mux is current. `SetDomain` rebuilds the mux in place, which is why it is safe to call repeatedly. Never call `x.Handle()` again. - **SetDomain is required and validated**: returns an error for an empty domain or empty labels; searches do not route until it succeeds. Naming context is always `dc=netauth,dc=