diff options
-rw-r--r-- | lib/eldap/doc/src/eldap.xml | 100 | ||||
-rw-r--r-- | lib/eldap/src/eldap.erl | 253 | ||||
-rw-r--r-- | lib/eldap/test/eldap_basic_SUITE.erl | 94 |
3 files changed, 324 insertions, 123 deletions
diff --git a/lib/eldap/doc/src/eldap.xml b/lib/eldap/doc/src/eldap.xml index 8f4479a730..43873e44e2 100644 --- a/lib/eldap/doc/src/eldap.xml +++ b/lib/eldap/doc/src/eldap.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>2012</year><year>2013</year> + <year>2012</year><year>2016</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -29,7 +29,7 @@ <rev>B</rev> </header> <module>eldap</module> - <modulesummary>Eldap Functions</modulesummary> + <modulesummary>LDAP Client</modulesummary> <description> <p>This module provides a client api to the Lightweight Directory Access Protocol (LDAP). </p> @@ -40,20 +40,67 @@ </list> <p>The above publications can be found at <url href="http://www.ietf.org">IETF</url>. </p> - <p><em>Types</em></p> - <pre> -handle() Connection handle -attribute() {Type = string(), Values=[string()]} -modify_op() See mod_add/2, mod_delete/2, mod_replace/2 -scope() See baseObject/0, singleLevel/0, wholeSubtree/0 -dereference() See neverDerefAliases/0, derefInSearching/0, derefFindingBaseObj/0, derefAlways/0 -filter() See present/1, substrings/2, - equalityMatch/2, greaterOrEqual/2, lessOrEqual/2, - approxMatch/2, extensibleMatch/2, - 'and'/1, 'or'/1, 'not'/1. - </pre> - <p></p> </description> + + <section> + <title>DATA TYPES</title> + <p>Type definitions that are used more than once in this module: + </p> + <taglist> + <tag><c>handle()</c></tag> + <item><p>Connection handle</p></item> + + <tag><c>attribute() =</c></tag> + <item><p><c>{Type = string(), Values=[string()]}</c></p></item> + + <tag><c>modify_op()</c></tag> + <item><p>See + <seealso marker="#mod_add/2">mod_add/2</seealso>, + <seealso marker="#mod_delete/2">mod_delete/2</seealso>, + <seealso marker="#mod_replace/2">mod_replace/2</seealso> + </p></item> + + <tag><c>scope()</c></tag> + <item><p>See + <seealso marker="#baseObject/0">baseObject/0</seealso>, + <seealso marker="#singleLevel/0">singleLevel/0</seealso>, + <seealso marker="#wholeSubtree/0">wholeSubtree/0</seealso> + </p></item> + + <tag><c>dereference()</c></tag> + <item><p>See + <seealso marker="#neverDerefAliases/0">neverDerefAliases/0</seealso>, + <seealso marker="#derefInSearching/0">derefInSearching/0</seealso>, + <seealso marker="#derefFindingBaseObj/0">derefFindingBaseObj/0</seealso>, + <seealso marker="#derefAlways/0">derefAlways/0</seealso> + </p></item> + + <tag><c>filter()</c></tag> + <item><p>See + <seealso marker="#present/1">present/1</seealso>, + <seealso marker="#substrings/2">substrings/2</seealso>, + <seealso marker="#equalityMatch/2">equalityMatch/2</seealso>, + <seealso marker="#greaterOrEqual/2">greaterOrEqual/2</seealso>, + <seealso marker="#lessOrEqual/2">lessOrEqual/2</seealso>, + <seealso marker="#approxMatch/2">approxMatch/2</seealso>, + <seealso marker="#extensibleMatch/2">extensibleMatch/2</seealso>, + <seealso marker="#'and'/1">'and'/1</seealso>, + <seealso marker="#'or'/1">'or'/1</seealso>, + <seealso marker="#'not'/1">'not'/1</seealso> + </p></item> + + <tag><c>return_value() = </c></tag> + <item><p><c>ok | {ok, {referral,referrals()}} | {error,Error}</c> + </p></item> + + <tag><c>referrals() =</c></tag> + <item><p><c>[Address = string()]</c> The contents of <c>Address</c> is server dependent. + </p></item> + + </taglist> + </section> + + <funcs> <func> <name>open([Host]) -> {ok, Handle} | {error, Reason}</name> @@ -88,18 +135,19 @@ filter() See present/1, substrings/2, <v>Handle = handle()</v> </type> <desc> - <p>Shutdown the connection.</p> + <p>Shutdown the connection after sending an unbindRequest to the server. If the connection is tls the connection + will be closed with <c>ssl:close/1</c>, otherwise with <c>gen_tcp:close/1</c>.</p> </desc> </func> <func> - <name>start_tls(Handle, Options) -> ok | {error,Error}</name> + <name>start_tls(Handle, Options) -> return_value()</name> <fsummary>Upgrade a connection to TLS.</fsummary> <desc> <p>Same as start_tls(Handle, Options, infinity)</p> </desc> </func> <func> - <name>start_tls(Handle, Options, Timeout) -> ok | {error,Error}</name> + <name>start_tls(Handle, Options, Timeout) -> return_value()</name> <fsummary>Upgrade a connection to TLS.</fsummary> <type> <v>Handle = handle()</v> @@ -128,7 +176,7 @@ filter() See present/1, substrings/2, </desc> </func> <func> - <name>simple_bind(Handle, Dn, Password) -> ok | {error, Reason}</name> + <name>simple_bind(Handle, Dn, Password) -> return_value()</name> <fsummary>Authenticate the connection.</fsummary> <type> <v>Handle = handle()</v> @@ -140,7 +188,7 @@ filter() See present/1, substrings/2, </desc> </func> <func> - <name>add(Handle, Dn, [Attribute]) -> ok | {error, Reason}</name> + <name>add(Handle, Dn, [Attribute]) -> return_value()</name> <fsummary>Add an entry.</fsummary> <type> <v>Handle = handle()</v> @@ -161,7 +209,7 @@ filter() See present/1, substrings/2, </desc> </func> <func> - <name>delete(Handle, Dn) -> ok | {error, Reason}</name> + <name>delete(Handle, Dn) -> return_value()</name> <fsummary>Delete an entry.</fsummary> <type> <v>Dn = string()</v> @@ -203,7 +251,7 @@ filter() See present/1, substrings/2, </func> <func> - <name>modify(Handle, Dn, [ModifyOp]) -> ok | {error, Reason}</name> + <name>modify(Handle, Dn, [ModifyOp]) -> return_value()</name> <fsummary>Modify an entry.</fsummary> <type> <v>Dn = string()</v> @@ -219,7 +267,7 @@ filter() See present/1, substrings/2, </desc> </func> <func> - <name>modify_password(Handle, Dn, NewPasswd) -> ok | {ok, GenPasswd} | {error, Reason}</name> + <name>modify_password(Handle, Dn, NewPasswd) -> return_value() | {ok, GenPasswd}</name> <fsummary>Modify the password of a user.</fsummary> <type> <v>Dn = string()</v> @@ -230,7 +278,7 @@ filter() See present/1, substrings/2, </desc> </func> <func> - <name>modify_password(Handle, Dn, NewPasswd, OldPasswd) -> ok | {ok, GenPasswd} | {error, Reason}</name> + <name>modify_password(Handle, Dn, NewPasswd, OldPasswd) -> return_value() | {ok, GenPasswd}</name> <fsummary>Modify the password of a user.</fsummary> <type> <v>Dn = string()</v> @@ -259,7 +307,7 @@ filter() See present/1, substrings/2, </desc> </func> <func> - <name>modify_dn(Handle, Dn, NewRDN, DeleteOldRDN, NewSupDN) -> ok | {error, Reason}</name> + <name>modify_dn(Handle, Dn, NewRDN, DeleteOldRDN, NewSupDN) -> return_value()</name> <fsummary>Modify the DN of an entry.</fsummary> <type> <v>Dn = string()</v> @@ -279,7 +327,7 @@ filter() See present/1, substrings/2, </desc> </func> <func> - <name>search(Handle, SearchOptions) -> {ok, #eldap_search_result{}} | {error, Reason}</name> + <name>search(Handle, SearchOptions) -> {ok, #eldap_search_result{}} | {ok, {referral,referrals()}} | {error, Reason}</name> <fsummary>Search the Directory</fsummary> <type> <v>SearchOptions = #eldap_search{} | [SearchOption]</v> diff --git a/lib/eldap/src/eldap.erl b/lib/eldap/src/eldap.erl index df87ddde08..5d9718f00c 100644 --- a/lib/eldap/src/eldap.erl +++ b/lib/eldap/src/eldap.erl @@ -10,16 +10,23 @@ %%% See MIT-LICENSE at the top dir for licensing information. %%% -------------------------------------------------------------------- -vc('$Id$ '). --export([open/1,open/2,simple_bind/3,controlling_process/2, - start_tls/2, start_tls/3, - modify_password/3, modify_password/4, +-export([open/1, open/2, + simple_bind/3, simple_bind/4, + controlling_process/2, + start_tls/2, start_tls/3, start_tls/4, + modify_password/3, modify_password/4, modify_password/5, getopts/2, baseObject/0,singleLevel/0,wholeSubtree/0,close/1, equalityMatch/2,greaterOrEqual/2,lessOrEqual/2, extensibleMatch/2, - approxMatch/2,search/2,substrings/2,present/1, - 'and'/1,'or'/1,'not'/1,modify/3, mod_add/2, mod_delete/2, - mod_replace/2, add/3, delete/2, modify_dn/5,parse_dn/1, + search/2, search/3, + approxMatch/2,substrings/2,present/1, + 'and'/1,'or'/1,'not'/1,mod_add/2, mod_delete/2, + mod_replace/2, + modify/3, modify/4, + add/3, add/4, + delete/2, delete/3, + modify_dn/5,parse_dn/1, parse_ldap_url/1]). -export([neverDerefAliases/0, derefInSearching/0, @@ -91,7 +98,10 @@ start_tls(Handle, TlsOptions) -> start_tls(Handle, TlsOptions, infinity). start_tls(Handle, TlsOptions, Timeout) -> - send(Handle, {start_tls,TlsOptions,Timeout}), + start_tls(Handle, TlsOptions, Timeout, asn1_NOVALUE). + +start_tls(Handle, TlsOptions, Timeout, Controls) -> + send(Handle, {start_tls,TlsOptions,Timeout,Controls}), recv(Handle). %%% -------------------------------------------------------------------- @@ -108,7 +118,11 @@ modify_password(Handle, Dn, NewPasswd) -> modify_password(Handle, Dn, NewPasswd, OldPasswd) when is_pid(Handle), is_list(Dn), is_list(NewPasswd), is_list(OldPasswd) -> - send(Handle, {passwd_modify,optional(Dn),optional(NewPasswd),optional(OldPasswd)}), + modify_password(Handle, Dn, NewPasswd, OldPasswd, asn1_NOVALUE). + +modify_password(Handle, Dn, NewPasswd, OldPasswd, Controls) + when is_pid(Handle), is_list(Dn), is_list(NewPasswd), is_list(OldPasswd) -> + send(Handle, {passwd_modify,optional(Dn),optional(NewPasswd),optional(OldPasswd),Controls}), recv(Handle). %%% -------------------------------------------------------------------- @@ -147,7 +161,10 @@ controlling_process(Handle, Pid) when is_pid(Handle), is_pid(Pid) -> %%% Returns: ok | {error, Error} %%% -------------------------------------------------------------------- simple_bind(Handle, Dn, Passwd) when is_pid(Handle) -> - send(Handle, {simple_bind, Dn, Passwd}), + simple_bind(Handle, Dn, Passwd, asn1_NOVALUE). + +simple_bind(Handle, Dn, Passwd, Controls) when is_pid(Handle) -> + send(Handle, {simple_bind, Dn, Passwd, Controls}), recv(Handle). %%% -------------------------------------------------------------------- @@ -164,7 +181,10 @@ simple_bind(Handle, Dn, Passwd) when is_pid(Handle) -> %%% ) %%% -------------------------------------------------------------------- add(Handle, Entry, Attributes) when is_pid(Handle),is_list(Entry),is_list(Attributes) -> - send(Handle, {add, Entry, add_attrs(Attributes)}), + add(Handle, Entry, Attributes, asn1_NOVALUE). + +add(Handle, Entry, Attributes, Controls) when is_pid(Handle),is_list(Entry),is_list(Attributes) -> + send(Handle, {add, Entry, add_attrs(Attributes), Controls}), recv(Handle). %%% Do sanity check ! @@ -188,7 +208,10 @@ add_attrs(Attrs) -> %%% ) %%% -------------------------------------------------------------------- delete(Handle, Entry) when is_pid(Handle), is_list(Entry) -> - send(Handle, {delete, Entry}), + delete(Handle, Entry, asn1_NOVALUE). + +delete(Handle, Entry, Controls) when is_pid(Handle), is_list(Entry) -> + send(Handle, {delete, Entry, Controls}), recv(Handle). %%% -------------------------------------------------------------------- @@ -203,7 +226,10 @@ delete(Handle, Entry) when is_pid(Handle), is_list(Entry) -> %%% ) %%% -------------------------------------------------------------------- modify(Handle, Object, Mods) when is_pid(Handle), is_list(Object), is_list(Mods) -> - send(Handle, {modify, Object, Mods}), + modify(Handle, Object, Mods, asn1_NOVALUE). + +modify(Handle, Object, Mods, Controls) when is_pid(Handle), is_list(Object), is_list(Mods) -> + send(Handle, {modify, Object, Mods, Controls}), recv(Handle). %%% @@ -236,8 +262,12 @@ m(Operation, Type, Values) -> %%% -------------------------------------------------------------------- modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) when is_pid(Handle),is_list(Entry),is_list(NewRDN),is_atom(DelOldRDN),is_list(NewSup) -> + modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup, asn1_NOVALUE). + +modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup, Controls) + when is_pid(Handle),is_list(Entry),is_list(NewRDN),is_atom(DelOldRDN),is_list(NewSup) -> send(Handle, {modify_dn, Entry, NewRDN, - bool_p(DelOldRDN), optional(NewSup)}), + bool_p(DelOldRDN), optional(NewSup), Controls}), recv(Handle). %%% Sanity checks ! @@ -272,16 +302,19 @@ optional(Value) -> Value. %%% []}} %%% %%% -------------------------------------------------------------------- -search(Handle, A) when is_pid(Handle), is_record(A, eldap_search) -> - call_search(Handle, A); -search(Handle, L) when is_pid(Handle), is_list(L) -> +search(Handle, X) when is_pid(Handle), is_record(X,eldap_search) ; is_list(X) -> + search(Handle, X, asn1_NOVALUE). + +search(Handle, A, Controls) when is_pid(Handle), is_record(A, eldap_search) -> + call_search(Handle, A, Controls); +search(Handle, L, Controls) when is_pid(Handle), is_list(L) -> case catch parse_search_args(L) of {error, Emsg} -> {error, Emsg}; - A when is_record(A, eldap_search) -> call_search(Handle, A) + A when is_record(A, eldap_search) -> call_search(Handle, A, Controls) end. -call_search(Handle, A) -> - send(Handle, {search, A}), +call_search(Handle, A, Controls) -> + send(Handle, {search, A, Controls}), recv(Handle). parse_search_args(Args) -> @@ -484,33 +517,33 @@ do_connect(Host, Data, Opts) when Data#eldap.ldaps == true -> loop(Cpid, Data) -> receive - {From, {search, A}} -> - {Res,NewData} = do_search(Data, A), + {From, {search, A, Controls}} -> + {Res,NewData} = do_search(Data, A, Controls), send(From,Res), ?MODULE:loop(Cpid, NewData); - {From, {modify, Obj, Mod}} -> - {Res,NewData} = do_modify(Data, Obj, Mod), + {From, {modify, Obj, Mod, Controls}} -> + {Res,NewData} = do_modify(Data, Obj, Mod, Controls), send(From,Res), ?MODULE:loop(Cpid, NewData); - {From, {modify_dn, Obj, NewRDN, DelOldRDN, NewSup}} -> - {Res,NewData} = do_modify_dn(Data, Obj, NewRDN, DelOldRDN, NewSup), + {From, {modify_dn, Obj, NewRDN, DelOldRDN, NewSup, Controls}} -> + {Res,NewData} = do_modify_dn(Data, Obj, NewRDN, DelOldRDN, NewSup, Controls), send(From,Res), ?MODULE:loop(Cpid, NewData); - {From, {add, Entry, Attrs}} -> - {Res,NewData} = do_add(Data, Entry, Attrs), + {From, {add, Entry, Attrs, Controls}} -> + {Res,NewData} = do_add(Data, Entry, Attrs, Controls), send(From,Res), ?MODULE:loop(Cpid, NewData); - {From, {delete, Entry}} -> - {Res,NewData} = do_delete(Data, Entry), + {From, {delete, Entry, Controls}} -> + {Res,NewData} = do_delete(Data, Entry, Controls), send(From,Res), ?MODULE:loop(Cpid, NewData); - {From, {simple_bind, Dn, Passwd}} -> - {Res,NewData} = do_simple_bind(Data, Dn, Passwd), + {From, {simple_bind, Dn, Passwd, Controls}} -> + {Res,NewData} = do_simple_bind(Data, Dn, Passwd, Controls), send(From,Res), ?MODULE:loop(Cpid, NewData); @@ -520,17 +553,18 @@ loop(Cpid, Data) -> ?PRINT("New Cpid is: ~p~n",[NewCpid]), ?MODULE:loop(NewCpid, Data); - {From, {start_tls,TlsOptions,Timeout}} -> - {Res,NewData} = do_start_tls(Data, TlsOptions, Timeout), + {From, {start_tls,TlsOptions,Timeout,Controls}} -> + {Res,NewData} = do_start_tls(Data, TlsOptions, Timeout, Controls), send(From,Res), ?MODULE:loop(Cpid, NewData); - {From, {passwd_modify,Dn,NewPasswd,OldPasswd}} -> - {Res,NewData} = do_passwd_modify(Data, Dn, NewPasswd, OldPasswd), + {From, {passwd_modify,Dn,NewPasswd,OldPasswd,Controls}} -> + {Res,NewData} = do_passwd_modify(Data, Dn, NewPasswd, OldPasswd, Controls), send(From, Res), ?MODULE:loop(Cpid, NewData); {_From, close} -> + {no_reply,_NewData} = do_unbind(Data), unlink(Cpid), exit(closed); @@ -578,11 +612,10 @@ loop(Cpid, Data) -> %%% -------------------------------------------------------------------- %%% startTLS Request %%% -------------------------------------------------------------------- - -do_start_tls(Data=#eldap{using_tls=true}, _, _) -> +do_start_tls(Data=#eldap{using_tls=true}, _, _, _) -> {{error,tls_already_started}, Data}; -do_start_tls(Data=#eldap{fd=FD} , TlsOptions, Timeout) -> - case catch exec_start_tls(Data) of +do_start_tls(Data=#eldap{fd=FD} , TlsOptions, Timeout, Controls) -> + case catch exec_start_tls(Data, Controls) of {ok,NewData} -> case ssl:connect(FD,TlsOptions,Timeout) of {ok, SslSocket} -> @@ -593,15 +626,16 @@ do_start_tls(Data=#eldap{fd=FD} , TlsOptions, Timeout) -> {error,Error} -> {{error,Error}, Data} end; - {error,Error} -> {{error,Error},Data}; - Else -> {{error,Else},Data} + {{ok,Val},NewData} -> {{ok,Val},NewData}; + {error,Error} -> {{error,Error},Data}; + Else -> {{error,Else},Data} end. -define(START_TLS_OID, "1.3.6.1.4.1.1466.20037"). -exec_start_tls(Data) -> +exec_start_tls(Data, Controls) -> Req = #'ExtendedRequest'{requestName = ?START_TLS_OID}, - Reply = request(Data#eldap.fd, Data, Data#eldap.id, {extendedReq, Req}), + Reply = request(Data#eldap.fd, Data, Data#eldap.id, {extendedReq, Req, Controls}), exec_extended_req_reply(Data, Reply). exec_extended_req_reply(Data, {ok,Msg}) when @@ -611,6 +645,8 @@ exec_extended_req_reply(Data, {ok,Msg}) when case Result#'ExtendedResponse'.resultCode of success -> {ok,Data}; + referral -> + {{ok, {referral,Result#'ExtendedResponse'.referral}}, Data}; Error -> {error, {response,Error}} end; @@ -626,30 +662,32 @@ exec_extended_req_reply(_, Error) -> %%% Authenticate ourselves to the directory using %%% simple authentication. -do_simple_bind(Data, anon, anon) -> %% For testing - do_the_simple_bind(Data, "", ""); -do_simple_bind(Data, Dn, _Passwd) when Dn=="",Data#eldap.anon_auth==false -> +do_simple_bind(Data, anon, anon, Controls) -> %% For testing + do_the_simple_bind(Data, "", "", Controls); +do_simple_bind(Data, Dn, _Passwd,_) when Dn=="",Data#eldap.anon_auth==false -> {{error,anonymous_auth},Data}; -do_simple_bind(Data, _Dn, Passwd) when Passwd=="",Data#eldap.anon_auth==false -> +do_simple_bind(Data, _Dn, Passwd,_) when Passwd=="",Data#eldap.anon_auth==false -> {{error,anonymous_auth},Data}; -do_simple_bind(Data, Dn, Passwd) -> - do_the_simple_bind(Data, Dn, Passwd). +do_simple_bind(Data, Dn, Passwd, Controls) -> + do_the_simple_bind(Data, Dn, Passwd, Controls). -do_the_simple_bind(Data, Dn, Passwd) -> +do_the_simple_bind(Data, Dn, Passwd, Controls) -> case catch exec_simple_bind(Data#eldap{binddn = Dn, passwd = Passwd, - id = bump_id(Data)}) of - {ok,NewData} -> {ok,NewData}; - {error,Emsg} -> {{error,Emsg},Data}; - Else -> {{error,Else},Data} + id = bump_id(Data)}, + Controls) of + {ok,NewData} -> {ok,NewData}; + {{ok,Val},NewData} -> {{ok,Val},NewData}; + {error,Emsg} -> {{error,Emsg},Data}; + Else -> {{error,Else},Data} end. -exec_simple_bind(Data) -> +exec_simple_bind(Data, Controls) -> Req = #'BindRequest'{version = Data#eldap.version, name = Data#eldap.binddn, authentication = {simple, Data#eldap.passwd}}, log2(Data, "bind request = ~p~n", [Req]), - Reply = request(Data#eldap.fd, Data, Data#eldap.id, {bindRequest, Req}), + Reply = request(Data#eldap.fd, Data, Data#eldap.id, {bindRequest, Req, Controls}), log2(Data, "bind reply = ~p~n", [Reply]), exec_simple_bind_reply(Data, Reply). @@ -659,6 +697,7 @@ exec_simple_bind_reply(Data, {ok,Msg}) when {bindResponse, Result} -> case Result#'BindResponse'.resultCode of success -> {ok,Data}; + referral -> {{ok, {referral,Msg#'BindResponse'.referral}}, Data}; Error -> {error, Error} end; Other -> {error, Other} @@ -671,10 +710,11 @@ exec_simple_bind_reply(_, Error) -> %%% searchRequest %%% -------------------------------------------------------------------- -do_search(Data, A) -> - case catch do_search_0(Data, A) of +do_search(Data, A, Controls) -> + case catch do_search_0(Data, A, Controls) of {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data}; {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data}; + {{ok,Val},NewData} -> {{ok,Val},NewData}; {ok,Res,Ref,NewData} -> {{ok,polish(Res, Ref)},NewData}; {{error,Reason},NewData} -> {{error,Reason},NewData}; Else -> {ldap_closed_p(Data, Else),Data} @@ -700,7 +740,7 @@ polish_result([H|T]) when is_record(H, 'SearchResultEntry') -> polish_result([]) -> []. -do_search_0(Data, A) -> +do_search_0(Data, A, Controls) -> Req = #'SearchRequest'{baseObject = A#eldap_search.base, scope = v_scope(A#eldap_search.scope), derefAliases = v_deref(A#eldap_search.deref), @@ -711,15 +751,15 @@ do_search_0(Data, A) -> attributes = v_attributes(A#eldap_search.attributes) }, Id = bump_id(Data), - collect_search_responses(Data#eldap{id=Id}, Req, Id). + collect_search_responses(Data#eldap{id=Id}, Req, Id, Controls). %%% The returned answers cames in one packet per entry %%% mixed with possible referals -collect_search_responses(Data, Req, ID) -> +collect_search_responses(Data, Req, ID, Controls) -> S = Data#eldap.fd, log2(Data, "search request = ~p~n", [Req]), - send_request(S, Data, ID, {searchRequest, Req}), + send_request(S, Data, ID, {searchRequest, Req, Controls}), Resp = recv_response(S, Data), log2(Data, "search reply = ~p~n", [Resp]), collect_search_responses(Data, S, ID, Resp, [], []). @@ -732,6 +772,8 @@ collect_search_responses(Data, S, ID, {ok,Msg}, Acc, Ref) success -> log2(Data, "search reply = searchResDone ~n", []), {ok,Acc,Ref,Data}; + referral -> + {{ok, {referral,R#'LDAPResult'.referral}}, Data}; Reason -> {{error,Reason},Data} end; @@ -756,21 +798,22 @@ collect_search_responses(_, _, _, Else, _, _) -> %%% addRequest %%% -------------------------------------------------------------------- -do_add(Data, Entry, Attrs) -> - case catch do_add_0(Data, Entry, Attrs) of +do_add(Data, Entry, Attrs, Controls) -> + case catch do_add_0(Data, Entry, Attrs, Controls) of {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data}; {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data}; {ok,NewData} -> {ok,NewData}; + {{ok,Val},NewData} -> {{ok,Val},NewData}; Else -> {ldap_closed_p(Data, Else),Data} end. -do_add_0(Data, Entry, Attrs) -> +do_add_0(Data, Entry, Attrs, Controls) -> Req = #'AddRequest'{entry = Entry, attributes = Attrs}, S = Data#eldap.fd, Id = bump_id(Data), log2(Data, "add request = ~p~n", [Req]), - Resp = request(S, Data, Id, {addRequest, Req}), + Resp = request(S, Data, Id, {addRequest, Req, Controls}), log2(Data, "add reply = ~p~n", [Resp]), check_reply(Data#eldap{id = Id}, Resp, addResponse). @@ -779,19 +822,20 @@ do_add_0(Data, Entry, Attrs) -> %%% deleteRequest %%% -------------------------------------------------------------------- -do_delete(Data, Entry) -> - case catch do_delete_0(Data, Entry) of +do_delete(Data, Entry, Controls) -> + case catch do_delete_0(Data, Entry, Controls) of {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data}; {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data}; {ok,NewData} -> {ok,NewData}; + {{ok,Val},NewData} -> {{ok,Val},NewData}; Else -> {ldap_closed_p(Data, Else),Data} end. -do_delete_0(Data, Entry) -> +do_delete_0(Data, Entry, Controls) -> S = Data#eldap.fd, Id = bump_id(Data), log2(Data, "del request = ~p~n", [Entry]), - Resp = request(S, Data, Id, {delRequest, Entry}), + Resp = request(S, Data, Id, {delRequest, Entry, Controls}), log2(Data, "del reply = ~p~n", [Resp]), check_reply(Data#eldap{id = Id}, Resp, delResponse). @@ -800,22 +844,23 @@ do_delete_0(Data, Entry) -> %%% modifyRequest %%% -------------------------------------------------------------------- -do_modify(Data, Obj, Mod) -> - case catch do_modify_0(Data, Obj, Mod) of +do_modify(Data, Obj, Mod, Controls) -> + case catch do_modify_0(Data, Obj, Mod, Controls) of {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data}; {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data}; {ok,NewData} -> {ok,NewData}; + {{ok,Val},NewData} -> {{ok,Val},NewData}; Else -> {ldap_closed_p(Data, Else),Data} end. -do_modify_0(Data, Obj, Mod) -> +do_modify_0(Data, Obj, Mod, Controls) -> v_modifications(Mod), Req = #'ModifyRequest'{object = Obj, changes = Mod}, S = Data#eldap.fd, Id = bump_id(Data), log2(Data, "modify request = ~p~n", [Req]), - Resp = request(S, Data, Id, {modifyRequest, Req}), + Resp = request(S, Data, Id, {modifyRequest, Req, Controls}), log2(Data, "modify reply = ~p~n", [Resp]), check_reply(Data#eldap{id = Id}, Resp, modifyResponse). @@ -825,16 +870,17 @@ do_modify_0(Data, Obj, Mod) -> -define(PASSWD_MODIFY_OID, "1.3.6.1.4.1.4203.1.11.1"). -do_passwd_modify(Data, Dn, NewPasswd, OldPasswd) -> - case catch do_passwd_modify_0(Data, Dn, NewPasswd, OldPasswd) of +do_passwd_modify(Data, Dn, NewPasswd, OldPasswd, Controls) -> + case catch do_passwd_modify_0(Data, Dn, NewPasswd, OldPasswd, Controls) of {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data}; {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data}; {ok,NewData} -> {ok,NewData}; + {{ok,Val},NewData} -> {{ok,Val},NewData}; {ok,Passwd,NewData} -> {{ok, Passwd},NewData}; Else -> {ldap_closed_p(Data, Else),Data} end. -do_passwd_modify_0(Data, Dn, NewPasswd, OldPasswd) -> +do_passwd_modify_0(Data, Dn, NewPasswd, OldPasswd, Controls) -> Req = #'PasswdModifyRequestValue'{userIdentity = Dn, oldPasswd = OldPasswd, newPasswd = NewPasswd}, @@ -844,7 +890,7 @@ do_passwd_modify_0(Data, Dn, NewPasswd, OldPasswd) -> requestValue = Bytes}, Id = bump_id(Data), log2(Data, "extended request = ~p~n", [ExtReq]), - Reply = request(Data#eldap.fd, Data, Id, {extendedReq, ExtReq}), + Reply = request(Data#eldap.fd, Data, Id, {extendedReq, ExtReq, Controls}), log2(Data, "modify password reply = ~p~n", [Reply]), exec_passwd_modify_reply(Data#eldap{id = Id}, Reply). @@ -865,6 +911,8 @@ exec_passwd_modify_reply(Data, {ok,Msg}) when throw(Error) end end; + referral -> + {{ok, {referral,Result#'ExtendedResponse'.referral}}, Data}; Error -> {error, {response,Error}} end; @@ -877,15 +925,16 @@ exec_passwd_modify_reply(_, Error) -> %%% modifyDNRequest %%% -------------------------------------------------------------------- -do_modify_dn(Data, Entry, NewRDN, DelOldRDN, NewSup) -> - case catch do_modify_dn_0(Data, Entry, NewRDN, DelOldRDN, NewSup) of +do_modify_dn(Data, Entry, NewRDN, DelOldRDN, NewSup, Controls) -> + case catch do_modify_dn_0(Data, Entry, NewRDN, DelOldRDN, NewSup, Controls) of {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data}; {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data}; {ok,NewData} -> {ok,NewData}; + {{ok,Val},NewData} -> {{ok,Val},NewData}; Else -> {ldap_closed_p(Data, Else),Data} end. -do_modify_dn_0(Data, Entry, NewRDN, DelOldRDN, NewSup) -> +do_modify_dn_0(Data, Entry, NewRDN, DelOldRDN, NewSup, Controls) -> Req = #'ModifyDNRequest'{entry = Entry, newrdn = NewRDN, deleteoldrdn = DelOldRDN, @@ -893,22 +942,51 @@ do_modify_dn_0(Data, Entry, NewRDN, DelOldRDN, NewSup) -> S = Data#eldap.fd, Id = bump_id(Data), log2(Data, "modify DN request = ~p~n", [Req]), - Resp = request(S, Data, Id, {modDNRequest, Req}), + Resp = request(S, Data, Id, {modDNRequest, Req, Controls}), log2(Data, "modify DN reply = ~p~n", [Resp]), check_reply(Data#eldap{id = Id}, Resp, modDNResponse). +%%%-------------------------------------------------------------------- +%%% unbindRequest +%%%-------------------------------------------------------------------- +do_unbind(Data) -> + Req = "", + log2(Data, "unbind request = ~p (has no reply)~n", [Req]), + send_request(Data#eldap.fd, Data, Data#eldap.id, {unbindRequest, Req}), + case Data#eldap.using_tls of + true -> ssl:close(Data#eldap.fd); + false -> gen_tcp:close(Data#eldap.fd) + end, + {no_reply, Data#eldap{binddn = (#eldap{})#eldap.binddn, + passwd = (#eldap{})#eldap.passwd, + fd = (#eldap{})#eldap.fd, + using_tls = false + }}. + + %%% -------------------------------------------------------------------- %%% Send an LDAP request and receive the answer %%% -------------------------------------------------------------------- - request(S, Data, ID, Request) -> send_request(S, Data, ID, Request), recv_response(S, Data). -send_request(S, Data, ID, Request) -> - Message = #'LDAPMessage'{messageID = ID, - protocolOp = Request}, - {ok,Bytes} = 'ELDAPv3':encode('LDAPMessage', Message), +send_request(S, Data, Id, {T,P}) -> + send_the_LDAPMessage(S, Data, #'LDAPMessage'{messageID = Id, + protocolOp = {T,P}}); +send_request(S, Data, Id, {T,P,asn1_NOVALUE}) -> + send_the_LDAPMessage(S, Data, #'LDAPMessage'{messageID = Id, + protocolOp = {T,P}}); +send_request(S, Data, Id, {T,P,Controls0}) -> + Controls = [#'Control'{controlType=F1, + criticality=F2, + controlValue=F3} || {control,F1,F2,F3} <- Controls0], + send_the_LDAPMessage(S, Data, #'LDAPMessage'{messageID = Id, + protocolOp = {T,P}, + controls = Controls}). + +send_the_LDAPMessage(S, Data, LDAPMessage) -> + {ok,Bytes} = 'ELDAPv3':encode('LDAPMessage', LDAPMessage), case do_send(S, Data, Bytes) of {error,Reason} -> throw({gen_tcp_error,Reason}); Else -> Else @@ -942,6 +1020,7 @@ check_reply(Data, {ok,Msg}, Op) when {Op, Result} -> case Result#'LDAPResult'.resultCode of success -> {ok,Data}; + referral -> {{ok, {referral,Result#'LDAPResult'.referral}}, Data}; Error -> {error, Error} end; Other -> {error, Other} diff --git a/lib/eldap/test/eldap_basic_SUITE.erl b/lib/eldap/test/eldap_basic_SUITE.erl index 8efed3cc18..4c1c2f8144 100644 --- a/lib/eldap/test/eldap_basic_SUITE.erl +++ b/lib/eldap/test/eldap_basic_SUITE.erl @@ -30,6 +30,11 @@ -define(TIMEOUT, 120000). % 2 min + +%% Control to delete a referral object: +-define(manageDsaIT, {control,"2.16.840.1.113730.3.4.2",false,asn1_NOVALUE}). + + all() -> [app, appup, @@ -59,6 +64,7 @@ groups() -> {api_bound, [], [add_when_bound, add_already_exists, more_add, + add_referral, search_filter_equalityMatch, search_filter_substring_any, search_filter_initial, @@ -67,8 +73,11 @@ groups() -> search_filter_or, search_filter_and_not, search_two_hits, + search_referral, modify, + modify_referral, delete, + delete_referral, modify_dn_delete_old, modify_dn_keep_old]}, {v4_connections, [], connection_tests()}, @@ -92,11 +101,16 @@ connection_tests() -> init_per_suite(Config) -> SSL_available = init_ssl_certs_et_al(Config), - LDAP_server = find_first_server(false, [{config,eldap_server}, {config,ldap_server}, {"localhost",9876}]), + LDAP_server = find_first_server(false, [{config,eldap_server}, + {config,ldap_server}, + {"localhost",9876}, + {"aramis.otp.ericsson.se",9876}]), LDAPS_server = case SSL_available of true -> - find_first_server(true, [{config,ldaps_server}, {"localhost",9877}]); + find_first_server(true, [{config,ldaps_server}, + {"localhost",9877}, + {"aramis.otp.ericsson.se",9877}]); false -> undefined end, @@ -454,6 +468,16 @@ more_add(Config) -> [{"objectclass", ["organizationalUnit"]}, {"ou", ["Team"]}]). +%%%---------------------------------------------------------------- +add_referral(Config) -> + H = ?config(handle, Config), + BasePath = ?config(eldap_path, Config), + {ok,{referral,["ldap://nowhere.example.com"++_]}} = + eldap:add(H, "cn=Foo Bar,dc=notHere," ++ BasePath, + [{"objectclass", ["person"]}, + {"cn", ["Foo Bar"]}, + {"sn", ["Bar"]}, + {"telephoneNumber", ["555-1232", "555-5432"]}]). %%%---------------------------------------------------------------- search_filter_equalityMatch(Config) -> @@ -569,6 +593,16 @@ search_two_hits(Config) -> [ok=eldap:delete(H,DN) || DN <- ExpectedDNs]. %%%---------------------------------------------------------------- +search_referral(Config) -> + H = ?config(handle, Config), + BasePath = ?config(eldap_path, Config), + DN = "cn=Santa Claus,dc=notHere," ++ BasePath, + {ok,{referral,["ldap://nowhere.example.com"++_]}} = + eldap:search(H, #eldap_search{base = DN, + filter = eldap:present("description"), + scope=eldap:singleLevel()}). + +%%%---------------------------------------------------------------- modify(Config) -> H = ?config(handle, Config), BasePath = ?config(eldap_path, Config), @@ -602,6 +636,19 @@ modify(Config) -> restore_original_object(H, DN, OriginalAttrs). %%%---------------------------------------------------------------- +modify_referral(Config) -> + H = ?config(handle, Config), + BasePath = ?config(eldap_path, Config), + %% The object to modify + DN = "cn=Foo Bar,dc=notHere," ++ BasePath, + + %% Do a change + Mod = [eldap:mod_replace("telephoneNumber", ["555-12345"]), + eldap:mod_add("description", ["Nice guy"])], + {ok,{referral,["ldap://nowhere.example.com"++_]}} = + eldap:modify(H, DN, Mod). + +%%%---------------------------------------------------------------- delete(Config) -> H = ?config(handle, Config), BasePath = ?config(eldap_path, Config), @@ -620,6 +667,14 @@ delete(Config) -> restore_original_object(H, DN, OriginalAttrs). %%%---------------------------------------------------------------- +delete_referral(Config) -> + H = ?config(handle, Config), + BasePath = ?config(eldap_path, Config), + %% The element to play with: + DN = "cn=Jonas Jonsson,dc=notHere," ++ BasePath, + {ok,{referral,["ldap://nowhere.example.com"++_]}} = eldap:delete(H, DN). + +%%%---------------------------------------------------------------- modify_dn_delete_old(Config) -> H = ?config(handle, Config), BasePath = ?config(eldap_path, Config), @@ -817,25 +872,44 @@ delete_old_contents(H, Path) -> {filter, eldap:present("objectclass")}, {scope, eldap:wholeSubtree()}]) of - {ok, #eldap_search_result{entries=Entries}} -> + {ok, _R=#eldap_search_result{entries=Entries}} -> + case eldap:delete(H, "dc=notHere,"++Path, [?manageDsaIT]) of + ok -> ok; + {error,noSuchObject} -> ok; + Other -> ct:fail("eldap:delete notHere ret ~p",[Other]) + end, [ok = eldap:delete(H,DN) || #eldap_entry{object_name=DN} <- Entries]; _Res -> ignore end. + +-define(ok(X), ok(?MODULE,?LINE,X)). + add_new_contents(H, Path, MyHost) -> - ok(eldap:add(H,"dc=ericsson,dc=se", + ?ok(eldap:add(H,"dc=ericsson,dc=se", [{"objectclass", ["dcObject", "organization"]}, {"dc", ["ericsson"]}, {"o", ["Testing"]}])), - ok(eldap:add(H,Path, + ?ok(eldap:add(H,Path, [{"objectclass", ["dcObject", "organization"]}, {"dc", [MyHost]}, - {"o", ["Test machine"]}])). - - -ok({error,entryAlreadyExists}) -> ok; -ok(X) -> ok=X. + {"o", ["Test machine"]}])), + ?ok(eldap:add(H, "dc=notHere,"++Path, + [{"objectclass", ["referral", + "dcObject" + ]}, + {"ref", ["ldap://nowhere.example.com/notHere,"++Path]}, + {"dc", ["notHere"]} + ])). + + + +ok(_, _, {error,entryAlreadyExists}) -> ok; +ok(_, _, ok) -> ok; +ok(MODULE, LINE, X) -> + ct:pal("~p:~p add_new_contents: ret from eldap:add = ~p",[MODULE,LINE,X]), + X. |