Documentation
¶
Index ¶
- Constants
- func EncodeFilter(f Filter) (*ber.Packet, error)
- func EqualDN(a, b string) bool
- func EscapeValue(v string) string
- func InScope(dn, base string, scope Scope) bool
- func SupportedControls() []string
- func Unsupported(f Filter) error
- type Abandoner
- type AddRequest
- type Adder
- type And
- type Approx
- type Attribute
- type BindRequest
- type Binder
- type Change
- type CompareRequest
- type Comparer
- type Control
- type DNModifier
- type DeleteRequest
- type Deleter
- type DerefAliases
- type Disconnecter
- type Entry
- type EntryWriter
- type Equality
- type Error
- type ExtendedRequest
- type ExtendedResult
- type Extender
- type ExtensibleMatch
- type Filter
- type GreaterOrEqual
- type LessOrEqual
- type Modifier
- type ModifyDNRequest
- type ModifyOperation
- type ModifyRequest
- type Not
- type Or
- type Present
- type ReadSelection
- type Result
- type ResultCode
- type SASLBinder
- type SASLCredentials
- type SASLResult
- type Scope
- type SearchRequest
- type Searcher
- type Server
- type Session
- type Substrings
- type WriteResult
Constants ¶
const ( // OIDPaging is the simple paged results control (RFC 2696). OIDPaging = "1.2.840.113556.1.4.319" // OIDManageDsaIT says to act on an alias or referral entry itself rather // than following it (RFC 3296). OIDManageDsaIT = "2.16.840.1.113730.3.4.2" // OIDPreRead and OIDPostRead return the entry as it was before, or as it // became after, a write (RFC 4527). They are how a client learns what it // actually wrote. OIDPreRead = "1.3.6.1.1.13.1" OIDPostRead = "1.3.6.1.1.13.2" )
The controls this server knows by name.
const ( // OIDStartTLS is RFC 4511 4.14. OIDStartTLS = "1.3.6.1.4.1.1466.20037" // OIDWhoAmI is RFC 4532: "who does this connection say I am". OIDWhoAmI = "1.3.6.1.4.1.4203.1.11.3" // OIDPasswordModify is RFC 3062, the one write every directory is // expected to offer even when it offers no other. OIDPasswordModify = "1.3.6.1.4.1.4203.1.11.1" // OIDCancel is RFC 3909, an abandon that gets an answer. OIDCancel = "1.3.6.1.1.8" )
The extended operations named in this package.
const ( // FeatureAllOperationalAttributes is RFC 3673: "+" in an attribute list // asks for all operational attributes. "Servers supporting this feature // SHOULD publish the Object Identifier 1.3.6.1.4.1.4203.1.5.1". FeatureAllOperationalAttributes = "1.3.6.1.4.1.4203.1.5.1" // FeatureAbsoluteFilters is RFC 4526: "(&)" matches every entry and "(|)" // matches none. "Servers supporting this feature SHOULD publish the // Object Identifier 1.3.6.1.4.1.4203.1.5.3". FeatureAbsoluteFilters = "1.3.6.1.4.1.4203.1.5.3" )
The features this package implements, for supportedFeatures (RFC 3674).
Unlike controls these are not optional behaviour a client switches on: they are how the filter and the attribute list are READ, always. A client cannot discover them any other way -- "(&)" and "+" either mean what the specification says or they silently mean something else.
const DefaultMaxMessageSize = 4 << 20 // 4 MiB
DefaultMaxMessageSize is how large one LDAPMessage may be.
It is generous enough for a search result carrying a certificate and small enough that a client cannot ask this process to allocate its way out of memory. A deployment that publishes something larger raises it on the Server.
const DefaultMaxPagedSearches = 4
DefaultMaxPagedSearches is how many paged searches one connection may hold open at once.
⛔ Each one is a goroutine parked on its next entry, holding whatever the handler holds -- a database cursor, a row set. A client that starts a paged search and never finishes it has left that behind, and a client that does so in a loop is a resource exhaustion that needs no credentials, because the FIRST page is served before anything is known about it.
Variables ¶
This section is empty.
Functions ¶
func EncodeFilter ¶
EncodeFilter writes a Filter as its BER packet.
func EqualDN ¶
EqualDN reports whether two DNs name the same entry.
⛔ It compares RDN by RDN with case folded, and trims the space either side of each separator, because `uid=a, ou=people` and `uid=a,ou=people` are the same DN (RFC 4514 2). Comparing the whole strings makes them different, and then an entry is in scope or not depending on how the client happened to type its base.
It does NOT normalise attribute values by their matching rules, which needs a schema this package does not have: `cn=Bob` and `cn=bob ` are treated as equal (case, and trailing space) but `cn=B\6fb` is not unescaped to `cn=Bob`. A deployment whose DNs differ only by escaping needs a schema- aware comparison, and should say so rather than discover it.
func EscapeValue ¶
EscapeValue writes an assertion value the way RFC 4515 3 requires.
⛔ The five that MUST be escaped are `*`, `(`, `)`, `\` and NUL, and the reason is not cosmetic: an unescaped `*` in a value turns an equality filter into a substring one, and an unescaped `)` ends the filter early and leaves the rest of the value as syntax. A name containing either -- `O'Brien (contractor)`, or a password reset token -- becomes a different question from the one that was asked.
func InScope ¶
InScope reports whether an entry's DN is within a search's scope (RFC 4511 4.5.1.2).
- ScopeBaseObject is the base entry itself.
- ScopeSingleLevel is its IMMEDIATE subordinates: the base is not in it, and neither is anything two levels down.
- ScopeWholeSubtree is the base and everything beneath it.
An EMPTY base is the root of the DIT: everything is beneath it, its single level is the entries with exactly one RDN, and at base scope it is the root DSE -- which a server answers itself and never asks a handler about.
func SupportedControls ¶ added in v0.3.0
func SupportedControls() []string
SupportedControls is the request controls this server honours, in the order the root DSE reports them.
func Unsupported ¶
Unsupported reports whether this filter asks for something this server cannot do, so a search can answer inappropriateMatching instead of silently returning fewer entries than the client asked about.
Types ¶
type Abandoner ¶
An Abandoner is told a client gave up on an operation (RFC 4511 4.11).
There is no response to an abandon, ever -- which is why this returns nothing. A server that answered one would be sending a message the client has no way to interpret.
type AddRequest ¶
type AddRequest struct {
DN string
Attributes []*Attribute
// PreRead and PostRead are the attribute selections a client asked for
// with RFC 4527's read entry controls, or nil when it asked for neither.
// A handler that can honour them fills WriteResult; one that cannot
// leaves them, and no response control is sent.
PreRead *ReadSelection
PostRead *ReadSelection
}
An AddRequest is RFC 4511 4.7.
type Adder ¶
type Adder interface {
Add(ctx context.Context, s Session, req *AddRequest) (WriteResult, error)
}
An Adder adds an entry (RFC 4511 4.7).
type And ¶
type And struct{ Filters []Filter }
And is `(&(a)(b))`. An EMPTY And is TRUE -- RFC 4526: the "absolute true" filter `(&)` matches every entry.
type Approx ¶
type Approx struct{ Attribute, Value string }
Approx is `(type~=value)`. Nothing here does phonetic matching, so it is answered as an equality -- which RFC 4511 4.5.1.7.6 permits: "the semantics of the approxMatch are implementation defined", and equality is an implementation of "approximately".
type Attribute ¶
type Attribute struct {
Name string
Values [][]byte
// Operational marks an attribute the directory maintains rather than a
// person (RFC 4512 4.1.3): createTimestamp, entryUUID, and everything on
// the root DSE.
//
// ⛔ It changes what a search RETURNS, which is why it is on the model
// and not a detail of one server. RFC 4512 5.1: operational attributes
// "are not returned in search requests unless requested by name" -- so
// `*` (or an empty selection) brings the user attributes only, and `+`
// (RFC 3673) brings these. A server that cannot tell them apart either
// hides an attribute a client asked for or hands out one it did not.
Operational bool
}
An Attribute is a PartialAttribute: a type and its values (RFC 4511 4.1.7).
⛔ Values are [][]byte and not []string. An attribute value is an OCTET STRING, and several that a directory carries are not text at all -- userCertificate;binary, jpegPhoto, objectSid, and the NT hash a Samba deployment reads. Typing them as string works until the first byte that is not valid UTF-8, which Go replaces with U+FFFD on conversion: the value that reaches the client is then not the value the directory holds, and nothing anywhere reports an error.
func OperationalAttribute ¶
OperationalAttribute is StringAttribute for an attribute the directory maintains.
func StringAttribute ¶
StringAttribute is the common case spelled once, since most attributes ARE text and writing the conversion at every call site is how one of them ends up different.
type BindRequest ¶
type BindRequest struct {
Version int
Name string
Simple []byte
SASL *SASLCredentials
}
A BindRequest is RFC 4511 4.2. Exactly one of Simple and SASL is set.
type Binder ¶
A Binder answers a simple bind.
⛔ The password is []byte, not string. It goes to a constant-time comparison or to a KDF, both of which take bytes, and a string cannot be wiped -- Go strings are immutable, so a password that arrives as one stays in memory until the collector happens to reach it. It is also not necessarily UTF-8.
type Change ¶
type Change struct {
Operation ModifyOperation
Attribute *Attribute
}
A Change is one modification: an operation and what it applies to.
type CompareRequest ¶
A CompareRequest is RFC 4511 4.10.
type Comparer ¶
type Comparer interface {
Compare(ctx context.Context, s Session, req *CompareRequest) (Result, error)
}
A Comparer answers a compare (RFC 4511 4.10).
⛔ It answers CompareTrue or CompareFalse, and neither is an error. A handler returning Success is saying something the protocol has no word for, and a client reading `code == 0` as "they matched" would be wrong about every entry.
type Control ¶
type Control struct {
Type string
Criticality bool
// Value is absent as nil, which is different from present and empty.
Value []byte
}
A Control is RFC 4511 4.1.11.
⛔ Criticality is not decoration. A control marked critical that the server does not recognise means the operation MUST be refused with unavailableCriticalExtension -- because the client has said the operation is not the one it wants unless the control is honoured. Ignoring it performs a DIFFERENT operation and reports success: a client that sent the ManageDsaIT control critically and had it dropped is editing the alias rather than what it points at.
func UnhandledCritical ¶
UnhandledCritical returns the first control that is marked critical and is not in known, or nil.
A server calls it for every operation and refuses when it returns something. Getting this wrong is silent in exactly the direction that matters: the operation succeeds, and it is not the operation that was asked for.
type DNModifier ¶
type DNModifier interface {
ModifyDN(ctx context.Context, s Session, req *ModifyDNRequest) (WriteResult, error)
}
A DNModifier renames or moves one (RFC 4511 4.9).
type DeleteRequest ¶
type DeleteRequest struct {
DN string
// PreRead and PostRead are the attribute selections a client asked for
// with RFC 4527's read entry controls, or nil when it asked for neither.
// A handler that can honour them fills WriteResult; one that cannot
// leaves them, and no response control is sent.
PreRead *ReadSelection
PostRead *ReadSelection
}
A DeleteRequest is RFC 4511 4.8: a DN, and what the client asked to see of the entry before it goes.
type Deleter ¶
type Deleter interface {
Delete(ctx context.Context, s Session, req *DeleteRequest) (WriteResult, error)
}
A Deleter deletes one (RFC 4511 4.8).
type DerefAliases ¶
type DerefAliases uint8
A DerefAliases says what to do with alias entries (RFC 4511 4.5.1.3).
Nothing here creates aliases, so nothing here dereferences them. The value is carried and reported rather than ignored, because a client that asked for dereferencing and silently did not get it has been given an answer to a question it did not ask.
const ( NeverDerefAliases DerefAliases = 0 DerefInSearching DerefAliases = 1 DerefFindingBaseObj DerefAliases = 2 DerefAlways DerefAliases = 3 )
type Disconnecter ¶
type Disconnecter interface {
Disconnect(s Session)
}
A Disconnecter is told a connection ended, however it ended.
type Entry ¶
An Entry is a SearchResultEntry: a DN and what is published about it.
type EntryWriter ¶
type EntryWriter interface {
// Entry sends one. The error is the client going away or a limit being
// reached, and a handler that ignores it keeps walking a directory
// nobody is reading any more.
Entry(*Entry) error
// Reference sends a continuation reference (RFC 4511 4.5.3): other
// servers to ask for the part of the tree this one does not hold.
Reference(uris ...string) error
}
An EntryWriter takes the entries a search found.
type ExtendedRequest ¶
An ExtendedRequest is RFC 4511 4.12.
type ExtendedResult ¶
type ExtendedResult struct {
Result
Name string // responseName, usually the request's OID or empty
Value []byte // responseValue, absent as nil
}
An ExtendedResult is an extended response (RFC 4511 4.12).
type Extender ¶
type Extender interface {
// ExtendedNames is the OIDs this handler answers, for the root DSE to
// advertise as supportedExtension.
ExtendedNames() []string
Extended(ctx context.Context, s Session, req *ExtendedRequest) (ExtendedResult, error)
}
An Extender answers an extended operation (RFC 4511 4.12).
StartTLS is NOT routed here: it changes the connection under the protocol and the server does it itself. A handler that received it could not perform the handshake without racing the server's own reader.
type ExtensibleMatch ¶
ExtensibleMatch is `(type:dn:=value)` and its relatives (RFC 4511 4.5.1.7.7). Nothing here implements a matching rule, so one that NAMES a rule is refused rather than guessed at -- see Matches.
func (*ExtensibleMatch) Matches ¶
func (f *ExtensibleMatch) Matches(e *Entry) bool
Matches an extensible match, or refuses to.
⛔ A filter naming a matching rule this server does not implement matches NOTHING, and says so through Unsupported rather than falling back to equality. RFC 4511 4.5.1.7.7 is explicit: a server that does not recognise the rule returns inappropriateMatching. Falling back would answer a different question -- `(memberOf:1.2.840.113556.1.4.1941:=cn=admins,...)` asks for a TRANSITIVE group search, and answering it as an equality would quietly say "not a member" about people who are.
func (*ExtensibleMatch) String ¶
func (f *ExtensibleMatch) String() string
type Filter ¶
type Filter interface {
// Matches reports whether an entry satisfies this filter.
Matches(e *Entry) bool
// String is the RFC 4515 form, parenthesised.
String() string
// contains filtered or unexported methods
}
A Filter is the question a search asked (RFC 4511 4.5.1.7, and its string form in RFC 4515).
⛔ ONE representation, three renderings. The library this replaces had three separate implementations -- one that decoded a filter off the wire into a string, one that encoded a string onto the wire, and one that matched an entry against the wire form -- and they drifted apart. The result was that `(uid=svc-*-prod)` was decoded as `(uid=svc-*)`, encoded as an equality match on a literal '*', and matched on its first component alone. Three answers to one question, all different, and a directory answering a BROADER question than it was asked is disclosure.
So a filter is parsed or decoded ONCE into this tree, and String, Encode and Matches all read the same tree. They cannot disagree about what a filter means, because there is only one thing that means it.
func DecodeFilter ¶
DecodeFilter reads a Filter from its BER packet.
func ParseFilter ¶
ParseFilter reads the RFC 4515 string form into the tree.
It is here for tests, for a configuration that writes a filter down, and for a log line that has to render one back. A SERVER does not parse strings off the wire -- DecodeFilter reads the BER directly -- and that is deliberate: a server that decoded a filter to a string and re-parsed it would be back to two representations that can disagree, which is the defect this whole file exists to prevent.
type GreaterOrEqual ¶
type GreaterOrEqual struct{ Attribute, Value string }
GreaterOrEqual is `(type>=value)`.
func (*GreaterOrEqual) Matches ¶
func (f *GreaterOrEqual) Matches(e *Entry) bool
func (*GreaterOrEqual) String ¶
func (f *GreaterOrEqual) String() string
type LessOrEqual ¶
type LessOrEqual struct{ Attribute, Value string }
LessOrEqual is `(type<=value)`.
func (*LessOrEqual) Matches ¶
func (f *LessOrEqual) Matches(e *Entry) bool
func (*LessOrEqual) String ¶
func (f *LessOrEqual) String() string
type Modifier ¶
type Modifier interface {
Modify(ctx context.Context, s Session, req *ModifyRequest) (WriteResult, error)
}
A Modifier modifies one (RFC 4511 4.6).
⛔ The changes are applied IN ORDER and ATOMICALLY -- see ModifyRequest. A handler that applies half of them and fails has left the directory in a state the client never asked for, and must answer as though it applied none.
type ModifyDNRequest ¶
type ModifyDNRequest struct {
DN string
NewRDN string
DeleteOldRDN bool
// NewSuperior moves the entry to another parent. Empty means "leave it
// where it is", which is a rename rather than a move.
NewSuperior string
// PreRead and PostRead are the attribute selections a client asked for
// with RFC 4527's read entry controls, or nil when it asked for neither.
// A handler that can honour them fills WriteResult; one that cannot
// leaves them, and no response control is sent.
PreRead *ReadSelection
PostRead *ReadSelection
}
A ModifyDNRequest is RFC 4511 4.9.
type ModifyOperation ¶
type ModifyOperation uint8
A ModifyOperation is one of RFC 4511 4.6's three.
const ( // AddValues adds values, leaving any already there. AddValues ModifyOperation = 0 // DeleteValues removes the listed values, or the whole attribute when // none are listed. DeleteValues ModifyOperation = 1 // ReplaceValues replaces every value, and with none listed removes the // attribute -- which is how a client clears one. ReplaceValues ModifyOperation = 2 )
func (ModifyOperation) String ¶
func (o ModifyOperation) String() string
type ModifyRequest ¶
type ModifyRequest struct {
DN string
Changes []Change
// PreRead and PostRead are the attribute selections a client asked for
// with RFC 4527's read entry controls, or nil when it asked for neither.
// A handler that can honour them fills WriteResult; one that cannot
// leaves them, and no response control is sent.
PreRead *ReadSelection
PostRead *ReadSelection
}
A ModifyRequest is RFC 4511 4.6.
⛔ Changes is an ORDERED LIST, and that is the whole point of this type. RFC 4511 4.6: "the modification operations are performed in the order listed", and the whole list is "performed as an atomic operation".
The library this replaces held three buckets -- AddAttributes, DeleteAttributes, ReplaceAttributes -- which throws the order away. Then
delete member=alice; add member=alice (alice stays) add member=alice; delete member=alice (alice goes)
arrive as the same request, and a server cannot tell which was asked. That is not an edge case: it is how a client that reconciles a group membership writes one, and getting it backwards removes somebody's access or grants it. A shape that cannot express the difference produces a write that is wrong INVISIBLY, which is why writes could not simply be bolted onto that library.
Atomicity is the implementation's to provide: a handler that applies half this list and then fails has left the directory in a state the client never asked for, and must answer as though it had applied none.
type Or ¶
type Or struct{ Filters []Filter }
Or is `(|(a)(b))`. An EMPTY Or is FALSE -- RFC 4526's "absolute false" filter `(|)` matches nothing. The asymmetry is the specification's and is the identity element of each operation; getting it backwards turns a filter that should match nothing into one that matches the directory.
type Present ¶
type Present struct{ Attribute string }
Present is `(type=*)`.
type ReadSelection ¶ added in v0.2.0
type ReadSelection struct{ Attributes []string }
A ReadSelection is the attributes a read entry control asked for.
It is a type rather than a []string so that "did not ask" and "asked for everything" are different things: a nil *ReadSelection means no control arrived, and one with no attributes means the empty selection, which RFC 4511 4.5.1.8 makes "all user attributes".
func (*ReadSelection) Keep ¶ added in v0.2.0
func (s *ReadSelection) Keep(e *Entry) *Entry
Keep returns a copy of e carrying only the attributes this selection asked for, by the same rules a search uses.
type Result ¶
type Result struct {
Code ResultCode
// MatchedDN is the part of the requested DN that DOES exist. RFC 4511
// 4.1.9 says it is set for noSuchObject, aliasProblem, invalidDNSyntax
// and aliasDereferencingProblem, and is empty otherwise.
MatchedDN string
// Diagnostic is for a person reading a log, and MAY be empty. It is not a
// machine-readable field and nothing should parse it.
Diagnostic string
// Referral is the URIs to ask instead, and is set only with the referral
// code (RFC 4511 4.1.10).
Referral []string
}
A Result is an LDAPResult: what every operation answers (RFC 4511 4.1.9).
⛔ MatchedDN and Diagnostic are fields here and not an afterthought, because they are fields of the protocol. A server that cannot set MatchedDN cannot tell a client how far along a DN the search base stopped existing -- which is the difference between "you have a typo in ou=" and "that whole tree is gone". The library this replaces hardcoded both to the empty string at every call site, so no server built on it could say either.
type ResultCode ¶
type ResultCode uint16
A ResultCode is the resultCode of an LDAPResult (RFC 4511 4.1.9, and the enumeration in Appendix B).
It is a named type and not an int, because the difference between invalidCredentials and authMethodNotSupported is the difference between "try again" and "not this way", and a client acts on it. A field typed int lets any number through and reads the same at every call site.
const ( Success ResultCode = 0 OperationsError ResultCode = 1 ProtocolError ResultCode = 2 TimeLimitExceeded ResultCode = 3 SizeLimitExceeded ResultCode = 4 CompareFalse ResultCode = 5 CompareTrue ResultCode = 6 AuthMethodNotSupported ResultCode = 7 StrongerAuthRequired ResultCode = 8 Referral ResultCode = 10 AdminLimitExceeded ResultCode = 11 ConfidentialityRequired ResultCode = 13 SaslBindInProgress ResultCode = 14 NoSuchAttribute ResultCode = 16 UndefinedAttributeType ResultCode = 17 InappropriateMatching ResultCode = 18 ConstraintViolation ResultCode = 19 AttributeOrValueExists ResultCode = 20 InvalidAttributeSyntax ResultCode = 21 NoSuchObject ResultCode = 32 AliasProblem ResultCode = 33 InvalidDNSyntax ResultCode = 34 AliasDereferencingProblem ResultCode = 36 InappropriateAuthentication ResultCode = 48 InvalidCredentials ResultCode = 49 InsufficientAccessRights ResultCode = 50 Busy ResultCode = 51 UnwillingToPerform ResultCode = 53 LoopDetect ResultCode = 54 NamingViolation ResultCode = 64 ObjectClassViolation ResultCode = 65 NotAllowedOnNonLeaf ResultCode = 66 NotAllowedOnRDN ResultCode = 67 EntryAlreadyExists ResultCode = 68 ObjectClassModsProhibited ResultCode = 69 AffectsMultipleDSAs ResultCode = 71 Other ResultCode = 80 )
The result codes. The gaps are the specification's: 9, 15, 22-31, 35, 37-47, 55-63, 70 and 72-79 are reserved or unused, and are left out rather than filled in, so that a reader can see the enumeration IS the RFC's.
func (ResultCode) OK ¶
func (c ResultCode) OK() bool
OK reports whether this code means the operation succeeded.
⛔ compareTrue and compareFalse are both answers to a Compare and neither is a failure; saslBindInProgress is a bind that has not finished. A caller that tests `code == Success` treats all three as errors, which is why this exists rather than that comparison.
func (ResultCode) String ¶
func (c ResultCode) String() string
String is the specification's own name for the code, which is what a log line and a client's error message should both say.
type SASLBinder ¶
type SASLBinder interface {
// Mechanisms is what this server offers, for the root DSE to advertise.
// A client discovers what it may use by reading
// supportedSASLMechanisms, so a mechanism missing here is one nothing
// will ever try.
Mechanisms() []string
// BindSASL answers one step. A Result with code SaslBindInProgress and a
// non-nil ServerCreds is a challenge; one with Success must set BoundDN,
// because the name field of a SASL bind is ignored (RFC 4513 5.2.1) and
// the mechanism is the only thing that knows who this is.
BindSASL(ctx context.Context, s Session, req *BindRequest) (SASLResult, error)
}
A SASLBinder answers a SASL bind, which may take several round trips.
It is separate from Binder because a server may implement one and not the other, and because the two carry different things: a SASL exchange has a challenge to send back and an identity of its own, and a simple bind has neither.
type SASLCredentials ¶
type SASLCredentials struct {
Mechanism string
// Credentials is absent as nil and present-and-empty as an empty non-nil
// slice. RFC 4511 gives the two different meanings and several mechanisms
// use a zero-length response as a message in its own right.
Credentials []byte
}
SASLCredentials is RFC 4511 4.2's SaslCredentials.
type SASLResult ¶
type SASLResult struct {
Result
// ServerCreds is serverSaslCreds, sent whenever it is non-nil INCLUDING
// empty: RFC 4511 4.2.2 gives absent and zero-length different meanings.
ServerCreds []byte
// BoundDN is who the exchange proved, read only when the code is
// Success. Empty binds the connection as anonymous, which is a
// legitimate thing for a mechanism to decide and never an accident the
// server can spot.
BoundDN string
}
A SASLResult is what one step of a SASL bind decided.
type SearchRequest ¶
type SearchRequest struct {
BaseObject string
Scope Scope
DerefAliases DerefAliases
// SizeLimit and TimeLimit are 0 for "no limit from the client". The
// server may have its own, and the SMALLER of the two applies -- a client
// asking for more than the server allows does not raise the server's.
SizeLimit int
TimeLimit int
// TypesOnly asks for attribute descriptions WITHOUT their values.
TypesOnly bool
Filter Filter
// Attributes is the selection (RFC 4511 4.5.1.8). Empty means "all user
// attributes", which is NOT the same as "everything": operational
// attributes are returned only when named, or by "+".
Attributes []string
}
A SearchRequest is RFC 4511 4.5.1.
type Searcher ¶
type Searcher interface {
Search(ctx context.Context, s Session, req *SearchRequest, w EntryWriter) (Result, error)
}
A Searcher answers a search.
⛔ Entries are written to w as they are found, not returned as a slice. A directory that materialises every match before sending any has decided that its largest answer fits in memory, and the search that finds out it does not is the one a client ran by accident. Writing as it goes also lets the server stop at the size limit without the handler having to know what the limit is.
type Server ¶
type Server struct {
// Bind answers a simple bind. With none, every simple bind is refused --
// a server with no way to prove anybody should not let anybody in.
Bind Binder
// SASL answers a SASL bind, and names the mechanisms the root DSE
// advertises.
SASL SASLBinder
// Search answers a search. With none, only the root DSE is answerable.
Search Searcher
Compare Comparer
Extended Extender
// The writes. Each is independently optional.
Add Adder
Modify Modifier
Delete Deleter
ModifyDN DNModifier
Abandon Abandoner
Disconnected Disconnecter
// TLSConfig, when set, makes StartTLS available (RFC 4511 4.14). It is
// also what an ldaps:// listener is wrapped with, but that is the
// caller's to do -- a tls.Listener is a listener like any other.
TLSConfig *tls.Config
// RequireTLS refuses every operation on a connection that is not
// protected, with confidentialityRequired.
//
// ⛔ It exists because StartTLS is asked for by the CLIENT: a server that
// merely OFFERS it has promised nothing, and a client that does not ask
// sends its bind password in the clear while everything looks normal at
// both ends. This is what turns "we have a certificate" from an offer
// into a guarantee.
RequireTLS bool
// MaxMessageSize bounds one message. Zero means DefaultMaxMessageSize.
MaxMessageSize int
// MaxPagedSearches is how many paged searches (RFC 2696) one connection
// may hold open at once. Zero means DefaultMaxPagedSearches.
//
// ⛔ Each one is a goroutine parked on its next entry, holding whatever
// the handler holds. A client that starts them and never finishes them
// is a resource exhaustion that needs no credentials, because the first
// page is served before anything is known about it.
MaxPagedSearches int
// MaxEntries bounds what one search may return, whatever the client
// asked for. Zero means no server-side limit.
//
// The SMALLER of this and the client's sizeLimit applies: a client
// asking for more than the server allows does not raise the server's.
MaxEntries int
// Timeout is how long one operation may take. Zero means no limit.
Timeout time.Duration
// IdleTimeout closes a connection nothing has been sent on. Zero means
// never.
IdleTimeout time.Duration
// NamingContexts is what this server holds, published on the root DSE so
// that a client can find out where to search (RFC 4512 5.1.2).
NamingContexts []string
// AltServers are other servers to try when this one is unavailable.
AltServers []string
// Vendor and VendorVersion identify the implementation (RFC 3045).
Vendor string
VendorVersion string
// Log receives what happened. Nil discards it.
//
// ⛔ Nothing here logs an assertion VALUE. A search filter carries what
// somebody typed -- a name, an email address, sometimes a token pasted
// into the wrong box -- and a directory that writes them to disk has
// made a record of every question anybody asked about anybody. The
// filter's SHAPE is logged instead.
Log *slog.Logger
// contains filtered or unexported fields
}
A Server answers LDAP on a listener.
⛔ The handlers are fields rather than one big interface, and a nil one means "this server does not do that". An operation with no handler is answered unwillingToPerform, NOT insufficientAccessRights: "I do not do that" and "you may not do that" send an administrator to two different places, and saying the second when the first is true starts an argument about permissions that do not exist.
func (*Server) Close ¶
Close stops the listeners and closes every connection.
⛔ It closes the CONNECTIONS too, not only the listeners. A server that stopped accepting and left the established ones running looks stopped and is still answering, which is the shape of a restart that does not take.
func (*Server) ListenAndServe ¶
ListenAndServe listens on addr and serves it.
type Session ¶
type Session interface {
// BoundDN is who this connection proved itself to be, and is empty for
// anonymous. RFC 4511 4.2.1: a failed bind leaves it empty, and so does
// a bind still in progress.
BoundDN() string
// TLS is the connection's TLS state, and ok is false on a connection that
// was never protected -- including one where StartTLS was offered and
// never asked for.
TLS() (tls.ConnectionState, bool)
// RemoteAddr is where the client is.
RemoteAddr() net.Addr
// Conn is the underlying connection, for the handler that genuinely needs
// it. Reading or writing it corrupts the LDAP stream; it is here for
// peer certificates and addresses.
Conn() net.Conn
// Controls are the request's controls (RFC 4511 4.1.11).
Controls() []Control
}
A Session is what a handler knows about the client it is answering.
⛔ BoundDN is here rather than passed as a string, and TLS is here at all, because the two questions a write has to ask are "who is this" and "over what". A handler given only a DN cannot refuse a password change sent in the clear, and every server that wants to must reach for the raw net.Conn and type-assert it -- which is a thing each of them then gets slightly differently.
type Substrings ¶
type Substrings struct {
Attribute string
Initial string // empty means absent
Any []string
Final string // empty means absent
}
Substrings is `(type=ini*any*any*fin)`.
⛔ Initial and Final are at most one each and Any is any number of them, in order, which is what RFC 4511 4.5.1.7.2 says and what the three-way drift above got wrong. They are separate fields rather than a list of components so that "at most once" is a thing the type says rather than a thing a check has to remember.
func (*Substrings) Matches ¶
func (f *Substrings) Matches(e *Entry) bool
Matches walks the components in order and WITHOUT OVERLAP, which is the whole of the defect this type exists to prevent.
`(uid=svc-*-prod)` is initial "svc-", final "-prod": the value must start with one, end with the other, and they must not overlap -- "svc-prod" is 8 characters and the two components need 9, so it does not match, and a check that tested them independently would say it does.
func (*Substrings) String ¶
func (f *Substrings) String() string
type WriteResult ¶ added in v0.2.0
type WriteResult struct {
Result
// PreRead is the entry as it was BEFORE the operation.
PreRead *Entry
// PostRead is the entry as it became.
PostRead *Entry
}
A WriteResult is what a write answers.
⛔ It carries the entry copies RFC 4527's read controls asked for, because only the handler can produce them: the read and the update have to be "one atomic action isolated from other update operations", and a server that read the entry, wrote, and read again would be doing three things with gaps between them. The copy it returned could be one somebody ELSE's write made -- a lie in the shape of a confirmation.
Both are ignored unless the client asked (req.PreRead / req.PostRead is non-nil) and the result is a success: RFC 4527 3.1 says no response control accompanies a failure, because there is nothing to confirm.
⛔ A handler MUST NOT fill these for a client that may not read the entry. 4527 4: "Servers MUST ensure that the client is authorized for reading of the information provided in this control." A write somebody may perform is not a read they may perform.