eldap(3)
π eldap(3) β the Erlang LDAP Client π
Erlang Module Definition β¨ okay bestie, welcome to eldap, the LDAP client for Erlang. Yes we're doing directory services today. Yes it's gonna be cute. Let's GO. π
β¨ NAME β¨
eldap β LDAP Client π
π DESCRIPTION π
So. This module hands you a client API for the Lightweight Directory Access Protocol, aka LDAP. It's giving "let me talk to your directory server" fr fr. π¦
References, because we cite our sources like the icons we are:
Both of the above publications can be found at IETF. π«Ά
Terminology abbreviations β pop quiz time:
- Dn. An LDAPDN is defined to be the representation of a Distinguished Name (DN) after encoding according to the specification in [RFC4514]. So like, the DN but post-encode, iykyk. π
π¦ DATA TYPES π¦
attribute()
attribute() :: {Type :: string(), Value :: [string()]}.
The attributes of an entry. Literally just the name and the values, no drama. β¨
connection_info()
connection_info() ::
#{socket := ssl:sslsocket() | gen_tcp:socket(),
socket_type := tcp | ssl}.
The LDAP socket, plus the transport protocol β TCP or TLS (SSL) β that the ldap connection is riding on. Vibe check: is it secure or nah. π₯
dereference()
-opaque dereference()
How to handle aliases during a search. Go peek at neverDerefAliases/0, derefInSearching/0, derefFindingBaseObj/0, derefAlways/0. πΈ
filter()
filter() :: term().
An opaque type representing a filter operation. π
See the filter creation functions for more info, including present/1, substrings/2, equalityMatch/2, greaterOrEqual/2, lessOrEqual/2, approxMatch/2, extensibleMatch/2, 'and'/1, 'or'/1, 'not'/1. That's the whole squad. π«Ά
handle()
handle() :: term().
An opaque handle, unique for the connection. It's literally the connection's main character ID. π
modify_op()
modify_op() :: term().
Entry modification operations. See mod_add/2, mod_delete/2, mod_replace/2. π
referrals()
referrals() :: [Address :: string()].
The LDAP Server Address. Heads up: the contents of Address is server dependent. So don't come for me if it looks weird. π
scope()
-opaque scope()
Scope of a search. See baseObject/0, singleLevel/0, wholeSubtree/0. π
search_option()
search_option() ::
{base, string()} |
{filter, filter()} |
{scope, scope()} |
{attributes, [string()]} |
{deref, dereference()} |
{types_only, boolean()} |
{timeout, integer()}.
A requested set of serach options for seaching the directory. (Yes, the original says "serach" and "seaching" and I am not fixing it, that's the lore. π)
π₯ FUNCTIONS π₯
add(Handle, Dn, Attributes) -> ok | {ok, Refs} | {error, Reason}
when
Handle :: handle(),
Dn :: string(),
Attributes :: [attribute()],
Refs :: {referral, referrals()},
Reason :: term().
Add an entry. Important: the entry must not exist β if it already does, that's not the move. π
add(Handle,
"cn=Bill Valentine, ou=people, o=Example Org, dc=example, dc=com",
[{"objectclass", ["person"]},
{"cn", ["Bill Valentine"]},
{"sn", ["Valentine"]},
{"telephoneNumber", ["545 555 00"]}]
)
Look at Bill Valentine living his best life in the directory. πβ¨
'and'(ListOfFilters) -> filter()
when ListOfFilters :: [filter()].
Creates a filter where all Filter must be true. Every single one. No exceptions, no skipping. Periodt. π
approxMatch(Type, Value) -> filter()
when Type :: string(), Value :: string().
Create an approximation match filter. Close enough counts. π€β¨
baseObject() -> scope()
Search baseobject only. Just the one. Narrow. Focused. Main character energy. π―
close(Handle) -> ok
when Handle :: handle().
Shutdown the connection after sending an unbindRequest to the server. Polite exit, as always. π«Ά
If the connection is TLS the connection will be closed with ssl:close/1, otherwise with gen_tcp:close/1. Different doors for different vibes. πͺ
delete(Handle, Dn) -> ok | {ok, Refs} | {error, Reason}
when
Handle :: handle(),
Dn :: string(),
Refs :: {referral, referrals()},
Reason :: term().
Delete an entry. Gone. Poof. π¦
delete(Handle, "cn=Bill Valentine, ou=people, o=Example Org, dc=example, dc=com")
RIP Bill Valentine. π
derefAlways() -> dereference()
Always dereference aliases. Every time. No chill. π₯
derefFindingBaseObj() -> dereference()
Dereference aliases only in finding the base. Just that one job. π
derefInSearching() -> dereference()
Dereference aliases only when searching. Only then bestie. π
equalityMatch(Type, Value) -> filter()
when Type :: string(), Value :: string().
Create a equality filter. It's giving "these two are the same" and honestly? Slay. β¨
extensibleMatch(MatchValue, OptionalAttrs) -> filter()
when
MatchValue :: string(),
OptionalAttrs ::
[{matchingRule, string()} |
{type, string()} |
{dnAttributes, boolean()}].
Creates an extensible match filter. The fancy one. The customizable queen. π
For example:
eldap:extensibleMatch("Bar", [{type,"sn"}, {matchingRule,"caseExactMatch"}]))
...creates a filter which performs a caseExactMatch on the attribute sn and matches with the value "Bar". The default value of dnAttributes is false. πΈ
ngl that example has a lil extra ) on the end in the original and I am leaving it exactly as it is because the rule is the rule. π
greaterOrEqual(Type, Value) -> filter()
when Type :: string(), Value :: string().
Create a greater or equal filter. >= but make it LDAP. π₯
info(Handle) -> connection_info()
when Handle :: handle().
Currently available information reveals the socket and the transport protocol, TCP or TLS (SSL), used by the LDAP connection. It's a lil peek behind the curtain. π
lessOrEqual(Type, Value) -> filter()
when Type :: string(), Value :: string().
Create a less or equal filter. <=, but cuter. π
mod_add(Type, Values) -> modify_op()
when Type :: string(), Values :: [string()].
Create an add modification operation. New stuff, coming through. β¨
mod_delete(Type, Values) -> modify_op()
when Type :: string(), Values :: [string()].
Create a delete modification operation. Bye bye, values. π
mod_replace(Type, Values) -> modify_op()
when Type :: string(), Values :: [string()].
Create a replace modification operation. Out with the old, in with the new. π
modify(Handle, Dn, ModifyOps) -> ok | {ok, Refs} | {error, Reason}
when
Handle :: handle(),
Dn :: string(),
ModifyOps :: [term()],
Refs :: {referral, referrals()},
Reason :: term().
Modify an entry. Glow-up time. π₯
modify(Handle, "cn=Bill Valentine, ou=people, o=Example Org, dc=example, dc=com",
[eldap:mod_replace("telephoneNumber", ["555 555 00"]),
eldap:mod_add("description", ["LDAP Hacker"]) ])
Not Bill getting a new phone number AND the description "LDAP Hacker". Iconic behavior honestly. π«Ά
modify_dn(Handle, Dn, NewRDN, DeleteOldRDN, NewSupDN) -> ok | {ok, Refs} | {error, Reason}
when
Handle :: handle(),
Dn :: string(),
NewRDN :: string(),
DeleteOldRDN :: boolean(),
NewSupDN :: string(),
Refs :: {referral, referrals()},
Reason :: term().
Modify the DN of an entry. Name change era. π¦
DeleteOldRDN indicates whether the current RDN should be removed from the attribute list after the operation. NewSupDN is the new parent that the RDN shall be moved to. And if the old parent should remain as parent, NewSupDN shall be "". πΈ
modify_dn(Handle, "cn=Bill Valentine, ou=people, o=Example Org, dc=example, dc=com ",
"cn=Bill Jr Valentine", true, "")
Bill Jr Valerie... wait no, Bill Jr Valentine. The sequel. ππ
modify_password(Handle, Dn, NewPasswd) -> ok | {ok, Refs} | {error, term()} | {ok, GenPasswd}
when
Handle :: handle(),
Dn :: string(),
NewPasswd :: string(),
Refs :: {referral, referrals()},
GenPasswd :: string().
Modify the password of a user. See modify_password/4. π
modify_password(Handle, Dn, NewPasswd, OldPasswd) -> ok | {ok, Refs} | {error, term()} | {ok, GenPasswd}
when
Handle :: handle(),
Dn :: string(),
NewPasswd :: string(),
OldPasswd :: string(),
Refs :: {referral, referrals()},
GenPasswd :: string().
Modify the password of a user. The deluxe version. β¨
- Dn. The user to modify. Should be
""if the modify request is for the user of the LDAP session. π€ - NewPasswd. The new password to set. Should be
""if the server is to generate the password. In this case, the result will be{ok, GenPasswd}. π - OldPasswd. Sometimes required by server policy for a user to change their password. If not required, use
modify_password/3. π
neverDerefAliases() -> dereference()
Never dereference aliases, treat aliases as entries. Strict. Firm. Boundaries. π«
'not'(Filter) -> filter()
when Filter :: filter().
Negate a filter. Flip it. Reverse it. π
open(Hosts) -> {ok, Handle} | {error, Reason}
when
Hosts :: [Host],
Host :: inet:socket_address() | inet:hostname(),
Handle :: handle(),
Reason :: term().
Setup a connection to an LDAP server, the HOST's are tried in order. First one to answer wins the crown. π
open(Hosts, Options) -> {ok, Handle} | {error, Reason}
when
Hosts :: [Host],
Host :: inet:socket_address() | inet:hostname(),
Options ::
[{port, integer()} |
{log, function()} |
{timeout, timeout()} |
{ssl, boolean()} |
{sslopts, [ssl:tls_client_option()]} |
{tcpopts,
[inet:inet_backend() |
gen_tcp:connect_option()]}],
Handle :: handle(),
Reason :: term().
Setup a connection to an LDAP server, the HOST's are tried in order. Same energy, now with options. ποΈ
The log function takes three arguments, fun(Level, FormatString, [FormatArg]) end. π
Timeout set the maximum time in milliseconds that each server request may take. Don't be late bestie, we're timing you. β±οΈ
Heads up: All TCP socket options are accepted exceptactive,binary,deliver,list,modeandpacket. Those six? Not invited. π
'or'(ListOfFilters) -> filter()
when ListOfFilters :: [filter()].
Create a filter where at least one of the Filter must be true. Just one! That's all we need! π
...and that's the end of part 1, bestie. Part 2 is coming, don't you dare leave π«Άβ¨
β¨ paged_result_control(PageSize) β page-size control, no cookie yet πͺ
paged_result_control(PageSize) ->
{control, ControlOID, true, binary()}
when
PageSize :: integer(),
ControlOID :: string().
Paged results is literally an extension to the LDAP protocol, and RFC2696 is the receipt bestie. This function creates a control with the specified page size for use in search/3, for example:
Control = eldap:paged_result_control(50),
{ok, SearchResults} = search(Handle, [{base, "dc=example, dc=com"}], [Control]),
π paged_result_control(PageSize, Cookie) β now with cookie, obsessed πͺβ¨
paged_result_control(PageSize, Cookie) ->
{control, ControlOID, true, binary()}
when
PageSize :: integer(),
Cookie :: string(),
ControlOID :: string().
Again: paged results is an extension to the LDAP protocol specified by RFC2696. This function creates a control with the specified page size and cookie for use in search/3 to retrieve the next results page. It's giving "next page bestie" energy π
.
For example:
PageSize = 50,
Control1 = eldap:paged_result_control(PageSize),
{ok, SearchResults1} = search(Handle, [{base, "dc=example, dc=com"}], [Control1]),
%% retrieve the returned cookie from the search results
{ok, Cookie1} = eldap:paged_result_cookie(SearchResults1),
Control2 = eldap:paged_result_control(PageSize, Cookie1),
{ok, SearchResults2} = eldap:search(Handle, [{base, "dc=example,dc=com"}], [Control2]),
%% etc
π¦ paged_result_cookie β cookie extraction, the main character πͺ
paged_result_cookie(#eldap_search_result{controls ::
maybe_improper_list(),
entries :: term(),
referrals :: term()}) ->
{error, no_cookie} |
{ok,
asn1_NOVALUE |
[[any()] | byte() | {_, _} | {_, _, _}] |
#'AttributeValueAssertion'{attributeDesc
::
[any()],
assertionValue
::
[any()]}}.
Paged results is an extension to the LDAP protocol specified by RFC2696. This function extracts the cookie returned from the server as a result of a paged search result.
If the returned cookie is the empty string "", then these search results represent the last in the series. So like, when the cookie's empty, you're done, periodt. π¬
β¨ present(Type) β presence check, lowkey iconic
present(Type) -> filter() when Type :: string().
Create a filter which filters on attribute type presence. It's exactly what it says on the tin, bestie. π
π₯ search(Handle, SearchOptions) β the big search energy
search(Handle, SearchOptions) ->
{ok,
#eldap_search_result{entries :: term(),
referrals :: term(),
controls :: term()}} |
{ok, Refs} |
{error, Reason}
when
Handle :: handle(),
SearchOptions ::
#eldap_search{base :: term(),
filter :: term(),
size_limit :: term(),
scope :: term(),
deref :: term(),
attributes :: term(),
types_only :: term(),
timeout :: term()} |
[search_option()],
Refs :: {referral, referrals()},
Reason :: term().
Search the directory with the supplied SearchOptions.
The base and filter options must be supplied. Default values: scope is wholeSubtree/0, deref is derefAlways/0, types_only is false and timeout is 0 (meaning infinity). No cap. π§’
Filter = eldap:substrings("cn", [{any,"V"}]),
search(Handle, [{base, "dc=example, dc=com"}, {filter, Filter}, {attributes, ["cn"]}]),
The timeout option in the SearchOptions is for the ldap server, while the timeout in eldap:open/2 is used for each individual request in the search operation. Two different timeouts, two different jobs, we love to see it. β°
π
simple_bind(Handle, Dn, Password) β simple auth, simple slay
simple_bind(Handle, Dn, Password) ->
ok | {ok, Refs} | {error, Reason}
when
Handle :: handle(),
Dn :: string(),
Password :: string(),
Refs :: {referral, referrals()},
Reason :: term().
Authenticate the connection using simple authentication. That's it, that's the vibe. π
πΈ singleLevel() β one level only, no recursion
singleLevel() -> scope().
Search the specified level only, i.e. do not recurse. Stay in your lane, bestie. π£οΈ
π start_tls(Handle, TlsOptions) β TLS glow-up, two-arg edition
start_tls(Handle, TlsOptions) -> ok | {ok, Refs} | {error, Reason}
when
Handle :: handle(),
TlsOptions :: [ssl:tls_client_option()],
Refs :: {referral, referrals()},
Reason :: term().
Same as start_tls(Handle, Options, infinity). Periodt. β¨
π start_tls(Handle, TlsOptions, Timeout) β TLS glow-up, full send
start_tls(Handle, TlsOptions, Timeout) ->
ok | {ok, Refs} | {error, Reason}
when
Handle :: handle(),
TlsOptions :: [ssl:tls_client_option()],
Timeout :: infinity | pos_integer(),
Refs :: {referral, referrals()},
Reason :: term().
Upgrade the connection associated with Handle to a TLS connection if possible. It's giving secure connection main character energy. π
The upgrade is done in two phases: first the server is asked for permission to upgrade. Second, if the request is acknowledged, the upgrade to TLS is performed.
Error responses from phase one will not affect the current encryption state of the connection. Those responses are:
tls_already_startedβ The connection is already encrypted. The connection is not affected.{response,ResponseFromServer}β The upgrade was refused by the LDAP server. TheResponseFromServeris an atom delivered byt the LDAP server explained in section 2.3 of rfc 2830. The connection is not affected, so it is still un-encrypted.
Errors in the second phase will however end the connection:
Errorβ Any error responded fromssl:connect/3
The Timeout parameter is for the actual TLS upgrade (phase 2) while the timeout in eldap:open/2 is used for the initial negotiation about upgrade (phase 1). So like, one timeout for the negotiation, one for the glow-up. π
π substrings(Type, SubStrings) β substring filter, cute and useful
substrings(Type, SubStrings) -> filter()
when
Type :: string(),
SubStrings ::
[{initial, string()} |
{any, string()} |
{final, string()}].
Create a filter which filters on substrings. It's literally in the name, bestie. π
π§ wholeSubtree() β the whole entire subtree
wholeSubtree() -> scope().
Search the entire subtree. No skips, no shortcuts, the whole thing. π³
That's the whole eldap(3) manual, bestie π. Source/version vibes: Ericsson AB, version eldap 1.3.1, page eldap(3). Date on this footer? It's not shown, so no invented date, periodt. Stay encrypted, stay iconic. β¨π
π¦ where this page comes from
- nixos 26.05: erlang 29.1.2
- nixpkgs unstable: erlang 29.1.2
- ubuntu stonking: erlang-doc 1:29.0.6+dfsg-1 Β· Apache-2.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms