1-module(eldap).
2%%% --------------------------------------------------------------------
3%%% Created:  12 Oct 2000 by Tobbe <tnt@home.se>
4%%% Function: Erlang client LDAP implementation according RFC 2251,2253
5%%%           and 2255. The interface is based on RFC 1823, and
6%%%           draft-ietf-asid-ldap-c-api-00.txt
7%%%
8%%% Copyright (c) 2010 Torbjorn Tornkvist
9%%% Copyright Ericsson AB 2011-2013. All Rights Reserved.
10%%% See MIT-LICENSE at the top dir for licensing information.
11%%% --------------------------------------------------------------------
12-vc('$Id$ ').
13-export([open/1, open/2,
14	 simple_bind/3, simple_bind/4,
15	 controlling_process/2,
16	 start_tls/2, start_tls/3, start_tls/4,
17         modify_password/3, modify_password/4, modify_password/5,
18	 getopts/2,
19	 baseObject/0,singleLevel/0,wholeSubtree/0,close/1,
20	 equalityMatch/2,greaterOrEqual/2,lessOrEqual/2,
21	 extensibleMatch/2,
22	 search/2, search/3,
23	 approxMatch/2,substrings/2,present/1,
24	 'and'/1,'or'/1,'not'/1,mod_add/2, mod_delete/2,
25	 mod_replace/2,
26	 modify/3, modify/4,
27	 add/3, add/4,
28	 delete/2, delete/3,
29	 modify_dn/5,parse_dn/1,
30	 parse_ldap_url/1]).
31
32-export([neverDerefAliases/0, derefInSearching/0,
33         derefFindingBaseObj/0, derefAlways/0]).
34
35%% for upgrades
36-export([loop/2]).
37
38-import(lists,[concat/1]).
39
40-include("ELDAPv3.hrl").
41-include("eldap.hrl").
42
43-define(LDAP_VERSION, 3).
44-define(LDAP_PORT, 389).
45-define(LDAPS_PORT, 636).
46
47-record(eldap, {version = ?LDAP_VERSION,
48		host,                % Host running LDAP server
49		port = ?LDAP_PORT,   % The LDAP server port
50		fd,                  % Socket filedescriptor.
51		prev_fd,	     % Socket that was upgraded by start_tls
52		binddn = "",         % Name of the entry to bind as
53		passwd,              % Password for (above) entry
54		id = 0,              % LDAP Request ID
55		log,                 % User provided log function
56		timeout = infinity,  % Request timeout
57		anon_auth = false,   % Allow anonymous authentication
58		ldaps = false,       % LDAP/LDAPS
59		using_tls = false,   % true if LDAPS or START_TLS executed
60		tls_opts = [],       % ssl:ssloption()
61		tcp_opts = []        % inet6 support
62	       }).
63
64%%% For debug purposes
65%%-define(PRINT(S, A), io:fwrite("~w(~w): " ++ S, [?MODULE,?LINE|A])).
66-define(PRINT(S, A), true).
67
68-define(elog(S, A), error_logger:info_msg("~w(~w): "++S,[?MODULE,?LINE|A])).
69
70%%% ====================================================================
71%%% Exported interface
72%%% ====================================================================
73
74%%% --------------------------------------------------------------------
75%%% open(Hosts [,Opts] )
76%%% --------------------
77%%% Setup a connection to on of the Hosts in the argument
78%%% list. Stop at the first successful connection attempt.
79%%% Valid Opts are:      Where:
80%%%
81%%%    {port, Port}        - Port is the port number
82%%%    {log, F}            - F(LogLevel, FormatString, ListOfArgs)
83%%%    {timeout, milliSec} - Server request timeout
84%%%
85%%% --------------------------------------------------------------------
86open(Hosts) ->
87    open(Hosts, []).
88
89open(Hosts, Opts) when is_list(Hosts), is_list(Opts) ->
90    Self = self(),
91    Pid = spawn_link(fun() -> init(Hosts, Opts, Self) end),
92    recv(Pid).
93
94%%% --------------------------------------------------------------------
95%%% Upgrade an existing connection to tls
96%%% --------------------------------------------------------------------
97start_tls(Handle, TlsOptions) ->
98    start_tls(Handle, TlsOptions, infinity).
99
100start_tls(Handle, TlsOptions, Timeout) ->
101    start_tls(Handle, TlsOptions, Timeout, asn1_NOVALUE).
102
103start_tls(Handle, TlsOptions, Timeout, Controls) ->
104    send(Handle, {start_tls,TlsOptions,Timeout,Controls}),
105    recv(Handle).
106
107%%% --------------------------------------------------------------------
108%%% Modify the password of a user.
109%%%
110%%% Dn        - Name of the entry to modify. If empty, the session user.
111%%% NewPasswd - New password. If empty, the server returns a new password.
112%%% OldPasswd - Original password for server verification, may be empty.
113%%%
114%%% Returns: ok | {ok, GenPasswd} | {error, term()}
115%%% --------------------------------------------------------------------
116modify_password(Handle, Dn, NewPasswd) ->
117    modify_password(Handle, Dn, NewPasswd, []).
118
119modify_password(Handle, Dn, NewPasswd, OldPasswd)
120  when is_pid(Handle), is_list(Dn), is_list(NewPasswd), is_list(OldPasswd) ->
121    modify_password(Handle, Dn, NewPasswd, OldPasswd, asn1_NOVALUE).
122
123modify_password(Handle, Dn, NewPasswd, OldPasswd, Controls)
124  when is_pid(Handle), is_list(Dn), is_list(NewPasswd), is_list(OldPasswd) ->
125    send(Handle, {passwd_modify,optional(Dn),optional(NewPasswd),optional(OldPasswd),Controls}),
126    recv(Handle).
127
128%%% --------------------------------------------------------------------
129%%% Ask for option values on the socket.
130%%% Warning: This is an undocumented function for testing purposes only.
131%%%          Use at own risk...
132%%% --------------------------------------------------------------------
133getopts(Handle, OptNames) when is_pid(Handle), is_list(OptNames) ->
134    send(Handle, {getopts, OptNames}),
135    recv(Handle).
136
137%%% --------------------------------------------------------------------
138%%% Shutdown connection (and process) asynchronous.
139%%% --------------------------------------------------------------------
140
141close(Handle) when is_pid(Handle) ->
142    send(Handle, close),
143    ok.
144
145%%% --------------------------------------------------------------------
146%%% Set who we should link ourselves to
147%%% --------------------------------------------------------------------
148
149controlling_process(Handle, Pid) when is_pid(Handle), is_pid(Pid)  ->
150    link(Pid),
151    send(Handle, {cnt_proc, Pid}),
152    recv(Handle).
153
154%%% --------------------------------------------------------------------
155%%% Authenticate ourselves to the Directory
156%%% using simple authentication.
157%%%
158%%%  Dn      -  The name of the entry to bind as
159%%%  Passwd  -  The password to be used
160%%%
161%%%  Returns: ok | {error, Error}
162%%% --------------------------------------------------------------------
163simple_bind(Handle, Dn, Passwd) when is_pid(Handle)  ->
164    simple_bind(Handle, Dn, Passwd, asn1_NOVALUE).
165
166simple_bind(Handle, Dn, Passwd, Controls) when is_pid(Handle)  ->
167    send(Handle, {simple_bind, Dn, Passwd, Controls}),
168    recv(Handle).
169
170%%% --------------------------------------------------------------------
171%%% Add an entry. The entry field MUST NOT exist for the AddRequest
172%%% to succeed. The parent of the entry MUST exist.
173%%% Example:
174%%%
175%%%  add(Handle,
176%%%         "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com",
177%%%         [{"objectclass", ["person"]},
178%%%          {"cn", ["Bill Valentine"]},
179%%%          {"sn", ["Valentine"]},
180%%%          {"telephoneNumber", ["545 555 00"]}]
181%%%     )
182%%% --------------------------------------------------------------------
183add(Handle, Entry, Attributes) when is_pid(Handle),is_list(Entry),is_list(Attributes) ->
184    add(Handle, Entry, Attributes, asn1_NOVALUE).
185
186add(Handle, Entry, Attributes, Controls) when is_pid(Handle),is_list(Entry),is_list(Attributes) ->
187    send(Handle, {add, Entry, add_attrs(Attributes), Controls}),
188    recv(Handle).
189
190%%% Do sanity check !
191add_attrs(Attrs) ->
192    F = fun({Type,Vals}) when is_list(Type),is_list(Vals) ->
193		%% Confused ? Me too... :-/
194		{'AddRequest_attributes',Type, Vals}
195	end,
196    case catch lists:map(F, Attrs) of
197	{'EXIT', _} -> throw({error, attribute_values});
198	Else        -> Else
199    end.
200
201%%% --------------------------------------------------------------------
202%%% Delete an entry. The entry consists of the DN of
203%%% the entry to be deleted.
204%%% Example:
205%%%
206%%%  delete(Handle,
207%%%         "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com"
208%%%        )
209%%% --------------------------------------------------------------------
210delete(Handle, Entry) when is_pid(Handle), is_list(Entry) ->
211    delete(Handle, Entry, asn1_NOVALUE).
212
213delete(Handle, Entry, Controls)  when is_pid(Handle), is_list(Entry) ->
214    send(Handle, {delete, Entry, Controls}),
215    recv(Handle).
216
217%%% --------------------------------------------------------------------
218%%% Modify an entry. Given an entry a number of modification
219%%% operations can be performed as one atomic operation.
220%%% Example:
221%%%
222%%%  modify(Handle,
223%%%         "cn=Torbjorn Tornkvist, ou=people, o=Bluetail AB, dc=bluetail, dc=com",
224%%%         [mod_replace("telephoneNumber", ["555 555 00"]),
225%%%          mod_add("description", ["LDAP hacker"])]
226%%%        )
227%%% --------------------------------------------------------------------
228modify(Handle, Object, Mods) when is_pid(Handle), is_list(Object), is_list(Mods) ->
229    modify(Handle, Object, Mods, asn1_NOVALUE).
230
231modify(Handle, Object, Mods, Controls) when is_pid(Handle), is_list(Object), is_list(Mods) ->
232    send(Handle, {modify, Object, Mods, Controls}),
233    recv(Handle).
234
235%%%
236%%% Modification operations.
237%%% Example:
238%%%            mod_replace("telephoneNumber", ["555 555 00"])
239%%%
240mod_add(Type, Values) when is_list(Type), is_list(Values)     -> m(add, Type, Values).
241mod_delete(Type, Values) when is_list(Type), is_list(Values)  -> m(delete, Type, Values).
242mod_replace(Type, Values) when is_list(Type), is_list(Values) -> m(replace, Type, Values).
243
244m(Operation, Type, Values) ->
245    #'ModifyRequest_changes_SEQOF'{
246       operation = Operation,
247       modification = #'PartialAttribute'{
248	 type = Type,
249	 vals = Values}}.
250
251%%% --------------------------------------------------------------------
252%%% Modify an entry. Given an entry a number of modification
253%%% operations can be performed as one atomic operation.
254%%% Example:
255%%%
256%%%  modify_dn(Handle,
257%%%    "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com",
258%%%    "cn=Ben Emerson",
259%%%    true,
260%%%    ""
261%%%        )
262%%% --------------------------------------------------------------------
263modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup)
264  when is_pid(Handle),is_list(Entry),is_list(NewRDN),is_atom(DelOldRDN),is_list(NewSup) ->
265    modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup, asn1_NOVALUE).
266
267modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup, Controls)
268  when is_pid(Handle),is_list(Entry),is_list(NewRDN),is_atom(DelOldRDN),is_list(NewSup) ->
269    send(Handle, {modify_dn, Entry, NewRDN,
270		  bool_p(DelOldRDN), optional(NewSup), Controls}),
271    recv(Handle).
272
273%%% Sanity checks !
274
275bool_p(Bool) when is_boolean(Bool) -> Bool.
276
277optional([])    -> asn1_NOVALUE;
278optional(Value) -> Value.
279
280%%% --------------------------------------------------------------------
281%%% Synchronous search of the Directory returning a
282%%% requested set of attributes.
283%%%
284%%%  Example:
285%%%
286%%%	Filter = eldap:substrings("cn", [{any,"o"}]),
287%%%	eldap:search(S, [{base, "dc=bluetail, dc=com"},
288%%%	                 {filter, Filter},
289%%%			 {attributes,["cn"]}])),
290%%%
291%%% Returned result:  {ok, #eldap_search_result{}}
292%%%
293%%% Example:
294%%%
295%%%  {ok,{eldap_search_result,
296%%%        [{eldap_entry,
297%%%           "cn=Magnus Froberg, dc=bluetail, dc=com",
298%%%           [{"cn",["Magnus Froberg"]}]},
299%%%         {eldap_entry,
300%%%           "cn=Torbjorn Tornkvist, dc=bluetail, dc=com",
301%%%           [{"cn",["Torbjorn Tornkvist"]}]}],
302%%%        []}}
303%%%
304%%% --------------------------------------------------------------------
305search(Handle, X) when is_pid(Handle), is_record(X,eldap_search) ; is_list(X) ->
306    search(Handle, X, asn1_NOVALUE).
307
308search(Handle, A, Controls) when is_pid(Handle), is_record(A, eldap_search) ->
309    call_search(Handle, A, Controls);
310search(Handle, L, Controls) when is_pid(Handle), is_list(L) ->
311    case catch parse_search_args(L) of
312	{error, Emsg}                  -> {error, Emsg};
313	A when is_record(A, eldap_search) -> call_search(Handle, A, Controls)
314    end.
315
316call_search(Handle, A, Controls) ->
317    send(Handle, {search, A, Controls}),
318    recv(Handle).
319
320parse_search_args(Args) ->
321    parse_search_args(Args,
322		      #eldap_search{scope = wholeSubtree,
323				    deref = derefAlways}).
324
325parse_search_args([{base, Base}|T],A) ->
326    parse_search_args(T,A#eldap_search{base = Base});
327parse_search_args([{filter, Filter}|T],A) ->
328    parse_search_args(T,A#eldap_search{filter = Filter});
329parse_search_args([{scope, Scope}|T],A) ->
330    parse_search_args(T,A#eldap_search{scope = Scope});
331parse_search_args([{deref, Deref}|T],A) ->
332    parse_search_args(T,A#eldap_search{deref = Deref});
333parse_search_args([{attributes, Attrs}|T],A) ->
334    parse_search_args(T,A#eldap_search{attributes = Attrs});
335parse_search_args([{types_only, TypesOnly}|T],A) ->
336    parse_search_args(T,A#eldap_search{types_only = TypesOnly});
337parse_search_args([{timeout, Timeout}|T],A) when is_integer(Timeout) ->
338    parse_search_args(T,A#eldap_search{timeout = Timeout});
339parse_search_args([H|_],_) ->
340    throw({error,{unknown_arg, H}});
341parse_search_args([],A) ->
342    A.
343
344%%%
345%%% The Scope parameter
346%%%
347baseObject()   -> baseObject.
348singleLevel()  -> singleLevel.
349wholeSubtree() -> wholeSubtree.
350
351%%
352%% The derefAliases parameter
353%%
354neverDerefAliases()   -> neverDerefAliases.
355derefInSearching()    -> derefInSearching.
356derefFindingBaseObj() -> derefFindingBaseObj.
357derefAlways()         -> derefAlways.
358
359%%%
360%%% Boolean filter operations
361%%%
362'and'(ListOfFilters) when is_list(ListOfFilters) -> {'and',ListOfFilters}.
363'or'(ListOfFilters)  when is_list(ListOfFilters) -> {'or', ListOfFilters}.
364'not'(Filter)        when is_tuple(Filter)       -> {'not',Filter}.
365
366%%%
367%%% The following Filter parameters consist of an attribute
368%%% and an attribute value. Example: F("uid","tobbe")
369%%%
370equalityMatch(Desc, Value)   -> {equalityMatch, av_assert(Desc, Value)}.
371greaterOrEqual(Desc, Value)  -> {greaterOrEqual, av_assert(Desc, Value)}.
372lessOrEqual(Desc, Value)     -> {lessOrEqual, av_assert(Desc, Value)}.
373approxMatch(Desc, Value)     -> {approxMatch, av_assert(Desc, Value)}.
374
375av_assert(Desc, Value) ->
376    #'AttributeValueAssertion'{attributeDesc  = Desc,
377			       assertionValue = Value}.
378
379%%%
380%%% Filter to check for the presence of an attribute
381%%%
382present(Attribute) when is_list(Attribute) ->
383    {present, Attribute}.
384
385
386%%%
387%%% A substring filter seem to be based on a pattern:
388%%%
389%%%   InitValue*AnyValue*FinalValue
390%%%
391%%% where all three parts seem to be optional (at least when
392%%% talking with an OpenLDAP server). Thus, the arguments
393%%% to substrings/2 looks like this:
394%%%
395%%% Type   ::= string( <attribute> )
396%%% SubStr ::= listof( {initial,Value} | {any,Value}, {final,Value})
397%%%
398%%% Example: substrings("sn",[{initial,"To"},{any,"kv"},{final,"st"}])
399%%% will match entries containing:  'sn: Tornkvist'
400%%%
401substrings(Type, SubStr) when is_list(Type), is_list(SubStr) ->
402    Ss = v_substr(SubStr),
403    {substrings,#'SubstringFilter'{type = Type,
404				   substrings = Ss}}.
405
406%%%
407%%% Filter for extensibleMatch
408%%%
409extensibleMatch(MatchValue, OptArgs) ->
410    MatchingRuleAssertion =
411	mra(OptArgs, #'MatchingRuleAssertion'{matchValue = MatchValue}),
412    {extensibleMatch, MatchingRuleAssertion}.
413
414mra([{matchingRule,Val}|T], Ack) when is_list(Val) ->
415    mra(T, Ack#'MatchingRuleAssertion'{matchingRule=Val});
416mra([{type,Val}|T], Ack) when is_list(Val) ->
417    mra(T, Ack#'MatchingRuleAssertion'{type=Val});
418mra([{dnAttributes,true}|T], Ack) ->
419    mra(T, Ack#'MatchingRuleAssertion'{dnAttributes="TRUE"});
420mra([{dnAttributes,false}|T], Ack) ->
421    mra(T, Ack#'MatchingRuleAssertion'{dnAttributes="FALSE"});
422mra([H|_], _) ->
423    throw({error,{extensibleMatch_arg,H}});
424mra([], Ack) ->
425    Ack.
426
427%%% --------------------------------------------------------------------
428%%% Worker process. We keep track of a controlling process to
429%%% be able to terminate together with it.
430%%% --------------------------------------------------------------------
431
432init(Hosts, Opts, Cpid) ->
433    Data = parse_args(Opts, Cpid, #eldap{}),
434    case try_connect(Hosts, Data) of
435	{ok,Data2} ->
436	    send(Cpid, {ok,self()}),
437	    ?MODULE:loop(Cpid, Data2);
438	Else ->
439 	    send(Cpid, Else),
440	    unlink(Cpid),
441	    exit(Else)
442    end.
443
444parse_args([{port, Port}|T], Cpid, Data) when is_integer(Port) ->
445    parse_args(T, Cpid, Data#eldap{port = Port});
446parse_args([{timeout, Timeout}|T], Cpid, Data) when is_integer(Timeout),Timeout>0 ->
447    parse_args(T, Cpid, Data#eldap{timeout = Timeout});
448parse_args([{anon_auth, true}|T], Cpid, Data) ->
449    parse_args(T, Cpid, Data#eldap{anon_auth = true});
450parse_args([{anon_auth, _}|T], Cpid, Data) ->
451    parse_args(T, Cpid, Data);
452parse_args([{ssl, true}|T], Cpid, Data) ->
453    parse_args(T, Cpid, Data#eldap{ldaps = true, using_tls=true});
454parse_args([{ssl, _}|T], Cpid, Data) ->
455    parse_args(T, Cpid, Data);
456parse_args([{sslopts, Opts}|T], Cpid, Data) when is_list(Opts) ->
457    parse_args(T, Cpid, Data#eldap{ldaps = true, using_tls=true, tls_opts = Opts ++ Data#eldap.tls_opts});
458parse_args([{sslopts, _}|T], Cpid, Data) ->
459    parse_args(T, Cpid, Data);
460parse_args([{tcpopts, Opts}|T], Cpid, Data) when is_list(Opts) ->
461    parse_args(T, Cpid, Data#eldap{tcp_opts = tcp_opts(Opts,Cpid,Data#eldap.tcp_opts)});
462parse_args([{log, F}|T], Cpid, Data) when is_function(F) ->
463    parse_args(T, Cpid, Data#eldap{log = F});
464parse_args([{log, _}|T], Cpid, Data) ->
465    parse_args(T, Cpid, Data);
466parse_args([H|_], Cpid, _) ->
467    send(Cpid, {error,{wrong_option,H}}),
468    unlink(Cpid),
469    exit(wrong_option);
470parse_args([], _, Data) ->
471    Data.
472
473tcp_opts([Opt|Opts], Cpid, Acc) ->
474    Key = if is_atom(Opt) -> Opt;
475	     is_tuple(Opt) -> element(1,Opt)
476	  end,
477    case lists:member(Key,[active,binary,deliver,list,mode,packet]) of
478	false ->
479	    tcp_opts(Opts, Cpid, [Opt|Acc]);
480	true ->
481	    tcp_opts_error(Opt, Cpid)
482    end;
483tcp_opts([], _Cpid, Acc) -> Acc.
484
485tcp_opts_error(Opt, Cpid) ->
486    send(Cpid, {error, {{forbidden_tcp_option,Opt},
487			"This option affects the eldap functionality and can't be set by user"}}),
488    unlink(Cpid),
489    exit(forbidden_tcp_option).
490
491%%% Try to connect to the hosts in the listed order,
492%%% and stop with the first one to which a successful
493%%% connection is made.
494
495try_connect([Host|Hosts], Data) ->
496    TcpOpts = [{packet, asn1}, {active,false}],
497    try do_connect(Host, Data, TcpOpts) of
498	{ok,Fd} -> {ok,Data#eldap{host = Host, fd   = Fd}};
499	Err    ->
500	    log2(Data, "Connect: ~p failed ~p~n",[Host, Err]),
501	    try_connect(Hosts, Data)
502    catch _:Err ->
503	    log2(Data, "Connect: ~p failed ~p~n",[Host, Err]),
504	    try_connect(Hosts, Data)
505    end;
506try_connect([],_) ->
507    {error,"connect failed"}.
508
509do_connect(Host, Data, Opts) when Data#eldap.ldaps == false ->
510    gen_tcp:connect(Host, Data#eldap.port, Opts ++ Data#eldap.tcp_opts,
511		    Data#eldap.timeout);
512do_connect(Host, Data, Opts) when Data#eldap.ldaps == true ->
513    ssl:connect(Host, Data#eldap.port,
514		Opts ++ Data#eldap.tls_opts ++ Data#eldap.tcp_opts,
515		Data#eldap.timeout).
516
517loop(Cpid, Data) ->
518    receive
519
520	{From, {search, A, Controls}} ->
521	    {Res,NewData} = do_search(Data, A, Controls),
522	    send(From,Res),
523	    ?MODULE:loop(Cpid, NewData);
524
525	{From, {modify, Obj, Mod, Controls}} ->
526	    {Res,NewData} = do_modify(Data, Obj, Mod, Controls),
527	    send(From,Res),
528	    ?MODULE:loop(Cpid, NewData);
529
530	{From, {modify_dn, Obj, NewRDN, DelOldRDN, NewSup, Controls}} ->
531	    {Res,NewData} = do_modify_dn(Data, Obj, NewRDN, DelOldRDN, NewSup, Controls),
532	    send(From,Res),
533	    ?MODULE:loop(Cpid, NewData);
534
535	{From, {add, Entry, Attrs, Controls}} ->
536	    {Res,NewData} = do_add(Data, Entry, Attrs, Controls),
537	    send(From,Res),
538	    ?MODULE:loop(Cpid, NewData);
539
540	{From, {delete, Entry, Controls}} ->
541	    {Res,NewData} = do_delete(Data, Entry, Controls),
542	    send(From,Res),
543	    ?MODULE:loop(Cpid, NewData);
544
545	{From, {simple_bind, Dn, Passwd, Controls}} ->
546	    {Res,NewData} = do_simple_bind(Data, Dn, Passwd, Controls),
547	    send(From,Res),
548	    ?MODULE:loop(Cpid, NewData);
549
550	{From, {cnt_proc, NewCpid}} ->
551	    unlink(Cpid),
552	    send(From,ok),
553	    ?PRINT("New Cpid is: ~p~n",[NewCpid]),
554	    ?MODULE:loop(NewCpid, Data);
555
556	{From, {start_tls,TlsOptions,Timeout,Controls}} ->
557	    {Res,NewData} = do_start_tls(Data, TlsOptions, Timeout, Controls),
558	    send(From,Res),
559	    ?MODULE:loop(Cpid, NewData);
560
561        {From, {passwd_modify,Dn,NewPasswd,OldPasswd,Controls}} ->
562            {Res,NewData} = do_passwd_modify(Data, Dn, NewPasswd, OldPasswd, Controls),
563            send(From, Res),
564            ?MODULE:loop(Cpid, NewData);
565
566	{_From, close} ->
567	    % Ignore tcp error if connection is already closed.
568	    try do_unbind(Data) of
569	        {no_reply,_NewData} -> ok
570	    catch
571	        throw:{gen_tcp_error, _TcpErr} -> ok
572	    end,
573	    unlink(Cpid),
574	    exit(closed);
575
576	{From, {getopts, OptNames}} ->
577	    Result =
578		try
579		    [case OptName of
580			 port ->    {port,    Data#eldap.port};
581			 log ->     {log,     Data#eldap.log};
582			 timeout -> {timeout, Data#eldap.timeout};
583			 ssl ->     {ssl,     Data#eldap.ldaps};
584			 {sslopts, SslOptNames} when Data#eldap.using_tls==true ->
585			     case ssl:getopts(Data#eldap.fd, SslOptNames) of
586				 {ok,SslOptVals} -> {sslopts, SslOptVals};
587				 {error,Reason} -> throw({error,Reason})
588			     end;
589			 {sslopts, _} ->
590			     throw({error,no_tls});
591			 {tcpopts, TcpOptNames} ->
592			     case inet:getopts(Data#eldap.fd, TcpOptNames) of
593				 {ok,TcpOptVals} -> {tcpopts, TcpOptVals};
594				 {error,Posix} -> throw({error,Posix})
595			     end
596		     end || OptName <- OptNames]
597		of
598		    OptsList -> {ok,OptsList}
599		catch
600		    throw:Error -> Error;
601		    Class:Error -> {error,{Class,Error}}
602		end,
603	    send(From, Result),
604	    ?MODULE:loop(Cpid, Data);
605
606	{Cpid, 'EXIT', Reason} ->
607	    ?PRINT("Got EXIT from Cpid, reason=~p~n",[Reason]),
608	    exit(Reason);
609
610	_XX ->
611	    ?PRINT("loop got: ~p~n",[_XX]),
612	    ?MODULE:loop(Cpid, Data)
613
614    end.
615
616
617%%% --------------------------------------------------------------------
618%%% startTLS Request
619%%% --------------------------------------------------------------------
620do_start_tls(Data=#eldap{using_tls=true}, _, _, _) ->
621    {{error,tls_already_started}, Data};
622do_start_tls(Data=#eldap{fd=FD} , TlsOptions, Timeout, Controls) ->
623    case catch exec_start_tls(Data, Controls) of
624	{ok,NewData} ->
625	    case ssl:connect(FD,TlsOptions,Timeout) of
626		{ok, SslSocket} ->
627		    {ok, NewData#eldap{prev_fd = FD,
628				       fd = SslSocket,
629				       using_tls = true
630				      }};
631		{error,Error} ->
632		    {{error,Error}, Data}
633	    end;
634	{{ok,Val},NewData} -> {{ok,Val},NewData};
635	{error,Error}      -> {{error,Error},Data};
636	Else               -> {{error,Else},Data}
637    end.
638
639-define(START_TLS_OID, "1.3.6.1.4.1.1466.20037").
640
641exec_start_tls(Data, Controls) ->
642    Req = #'ExtendedRequest'{requestName = ?START_TLS_OID},
643    Reply = request(Data#eldap.fd, Data, Data#eldap.id, {extendedReq, Req, Controls}),
644    exec_extended_req_reply(Data, Reply).
645
646exec_extended_req_reply(Data, {ok,Msg}) when
647  Msg#'LDAPMessage'.messageID == Data#eldap.id ->
648    case Msg#'LDAPMessage'.protocolOp of
649	{extendedResp, Result} ->
650	    case Result#'ExtendedResponse'.resultCode of
651		success ->
652		    {ok,Data};
653		referral ->
654		    {{ok, {referral,Result#'ExtendedResponse'.referral}}, Data};
655		Error ->
656		    {error, {response,Error}}
657	    end;
658	Other -> {error, Other}
659    end;
660exec_extended_req_reply(_, Error) ->
661    {error, Error}.
662
663%%% --------------------------------------------------------------------
664%%% bindRequest
665%%% --------------------------------------------------------------------
666
667%%% Authenticate ourselves to the directory using
668%%% simple authentication.
669
670do_simple_bind(Data, anon, anon, Controls) ->   %% For testing
671    do_the_simple_bind(Data, "", "", Controls);
672do_simple_bind(Data, Dn, _Passwd,_) when Dn=="",Data#eldap.anon_auth==false ->
673    {{error,anonymous_auth},Data};
674do_simple_bind(Data, _Dn, Passwd,_) when Passwd=="",Data#eldap.anon_auth==false ->
675    {{error,anonymous_auth},Data};
676do_simple_bind(Data, Dn, Passwd, Controls) ->
677    do_the_simple_bind(Data, Dn, Passwd, Controls).
678
679do_the_simple_bind(Data, Dn, Passwd, Controls) ->
680    case catch exec_simple_bind(Data#eldap{binddn = Dn,
681					   passwd = Passwd,
682					   id     = bump_id(Data)},
683			       Controls) of
684	{ok,NewData}       -> {ok,NewData};
685	{{ok,Val},NewData} -> {{ok,Val},NewData};
686	{error,Emsg}       -> {{error,Emsg},Data};
687	Else               -> {{error,Else},Data}
688    end.
689
690exec_simple_bind(Data, Controls) ->
691    Req = #'BindRequest'{version        = Data#eldap.version,
692			 name           = Data#eldap.binddn,
693			 authentication = {simple, Data#eldap.passwd}},
694    log2(Data, "bind request = ~p~n", [Req]),
695    Reply = request(Data#eldap.fd, Data, Data#eldap.id, {bindRequest, Req, Controls}),
696    log2(Data, "bind reply = ~p~n", [Reply]),
697    exec_simple_bind_reply(Data, Reply).
698
699exec_simple_bind_reply(Data, {ok,Msg}) when
700  Msg#'LDAPMessage'.messageID == Data#eldap.id ->
701    case Msg#'LDAPMessage'.protocolOp of
702	{bindResponse, Result} ->
703	    case Result#'BindResponse'.resultCode of
704		success -> {ok,Data};
705		referral -> {{ok, {referral,Result#'BindResponse'.referral}}, Data};
706		Error   -> {error, Error}
707	    end;
708	Other -> {error, Other}
709    end;
710exec_simple_bind_reply(_, Error) ->
711    {error, Error}.
712
713
714%%% --------------------------------------------------------------------
715%%% searchRequest
716%%% --------------------------------------------------------------------
717
718do_search(Data, A, Controls) ->
719    case catch do_search_0(Data, A, Controls) of
720	{error,Emsg}         -> {ldap_closed_p(Data, Emsg),Data};
721	{'EXIT',Error}       -> {ldap_closed_p(Data, Error),Data};
722	{{ok,Val},NewData}   -> {{ok,Val},NewData};
723	{ok,Res,Ref,NewData} -> {{ok,polish(Res, Ref)},NewData};
724	{{error,Reason},NewData} -> {{error,Reason},NewData};
725	Else                 -> {ldap_closed_p(Data, Else),Data}
726    end.
727
728%%%
729%%% Polish the returned search result
730%%%
731
732polish(Res, Ref) ->
733    R = polish_result(Res),
734    %%% No special treatment of referrals at the moment.
735    #eldap_search_result{entries = R,
736			 referrals = Ref}.
737
738polish_result([H|T]) when is_record(H, 'SearchResultEntry') ->
739    ObjectName = H#'SearchResultEntry'.objectName,
740    F = fun({_,A,V}) -> {A,V} end,
741    Attrs = lists:map(F, H#'SearchResultEntry'.attributes),
742    [#eldap_entry{object_name = ObjectName,
743		  attributes  = Attrs}|
744     polish_result(T)];
745polish_result([]) ->
746    [].
747
748do_search_0(Data, A, Controls) ->
749    Req = #'SearchRequest'{baseObject = A#eldap_search.base,
750			   scope = v_scope(A#eldap_search.scope),
751			   derefAliases = v_deref(A#eldap_search.deref),
752			   sizeLimit = 0, % no size limit
753			   timeLimit = v_timeout(A#eldap_search.timeout),
754			   typesOnly = v_bool(A#eldap_search.types_only),
755			   filter = v_filter(A#eldap_search.filter),
756			   attributes = v_attributes(A#eldap_search.attributes)
757			  },
758    Id = bump_id(Data),
759    collect_search_responses(Data#eldap{id=Id}, Req, Id, Controls).
760
761%%% The returned answers cames in one packet per entry
762%%% mixed with possible referals
763
764collect_search_responses(Data, Req, ID, Controls) ->
765    S = Data#eldap.fd,
766    log2(Data, "search request = ~p~n", [Req]),
767    send_request(S, Data, ID, {searchRequest, Req, Controls}),
768    Resp = recv_response(S, Data),
769    log2(Data, "search reply = ~p~n", [Resp]),
770    collect_search_responses(Data, S, ID, Resp, [], []).
771
772collect_search_responses(Data, S, ID, {ok,Msg}, Acc, Ref)
773  when is_record(Msg,'LDAPMessage') ->
774    case Msg#'LDAPMessage'.protocolOp of
775	{'searchResDone',R} ->
776            case R#'LDAPResult'.resultCode of
777                success ->
778                    log2(Data, "search reply = searchResDone ~n", []),
779                    {ok,Acc,Ref,Data};
780		referral ->
781		    {{ok, {referral,R#'LDAPResult'.referral}}, Data};
782                Reason ->
783                    {{error,Reason},Data}
784            end;
785	{'searchResEntry',R} when is_record(R,'SearchResultEntry') ->
786	    Resp = recv_response(S, Data),
787	    log2(Data, "search reply = ~p~n", [Resp]),
788	    collect_search_responses(Data, S, ID, Resp, [R|Acc], Ref);
789	{'searchResRef',R} ->
790	    %% At the moment we don't do anyting sensible here since
791	    %% I haven't been able to trigger the server to generate
792	    %% a response like this.
793	    Resp = recv_response(S, Data),
794	    log2(Data, "search reply = ~p~n", [Resp]),
795	    collect_search_responses(Data, S, ID, Resp, Acc, [R|Ref]);
796	Else ->
797	    throw({error,Else})
798    end;
799collect_search_responses(_, _, _, Else, _, _) ->
800    throw({error,Else}).
801
802%%% --------------------------------------------------------------------
803%%% addRequest
804%%% --------------------------------------------------------------------
805
806do_add(Data, Entry, Attrs, Controls) ->
807    case catch do_add_0(Data, Entry, Attrs, Controls) of
808	{error,Emsg}   -> {ldap_closed_p(Data, Emsg),Data};
809	{'EXIT',Error} -> {ldap_closed_p(Data, Error),Data};
810	{ok,NewData}   -> {ok,NewData};
811	{{ok,Val},NewData} -> {{ok,Val},NewData};
812	Else           -> {ldap_closed_p(Data, Else),Data}
813    end.
814
815do_add_0(Data, Entry, Attrs, Controls) ->
816    Req = #'AddRequest'{entry = Entry,
817			attributes = Attrs},
818    S = Data#eldap.fd,
819    Id = bump_id(Data),
820    log2(Data, "add request = ~p~n", [Req]),
821    Resp = request(S, Data, Id, {addRequest, Req, Controls}),
822    log2(Data, "add reply = ~p~n", [Resp]),
823    check_reply(Data#eldap{id = Id}, Resp, addResponse).
824
825
826%%% --------------------------------------------------------------------
827%%% deleteRequest
828%%% --------------------------------------------------------------------
829
830do_delete(Data, Entry, Controls) ->
831    case catch do_delete_0(Data, Entry, Controls) of
832	{error,Emsg}   -> {ldap_closed_p(Data, Emsg),Data};
833	{'EXIT',Error} -> {ldap_closed_p(Data, Error),Data};
834	{ok,NewData}   -> {ok,NewData};
835	{{ok,Val},NewData} -> {{ok,Val},NewData};
836	Else           -> {ldap_closed_p(Data, Else),Data}
837    end.
838
839do_delete_0(Data, Entry, Controls) ->
840    S = Data#eldap.fd,
841    Id = bump_id(Data),
842    log2(Data, "del request = ~p~n", [Entry]),
843    Resp = request(S, Data, Id, {delRequest, Entry, Controls}),
844    log2(Data, "del reply = ~p~n", [Resp]),
845    check_reply(Data#eldap{id = Id}, Resp, delResponse).
846
847
848%%% --------------------------------------------------------------------
849%%% modifyRequest
850%%% --------------------------------------------------------------------
851
852do_modify(Data, Obj, Mod, Controls) ->
853    case catch do_modify_0(Data, Obj, Mod, Controls) of
854	{error,Emsg}   -> {ldap_closed_p(Data, Emsg),Data};
855	{'EXIT',Error} -> {ldap_closed_p(Data, Error),Data};
856	{ok,NewData}   -> {ok,NewData};
857	{{ok,Val},NewData} -> {{ok,Val},NewData};
858	Else           -> {ldap_closed_p(Data, Else),Data}
859    end.
860
861do_modify_0(Data, Obj, Mod, Controls) ->
862    v_modifications(Mod),
863    Req = #'ModifyRequest'{object = Obj,
864			   changes = Mod},
865    S = Data#eldap.fd,
866    Id = bump_id(Data),
867    log2(Data, "modify request = ~p~n", [Req]),
868    Resp = request(S, Data, Id, {modifyRequest, Req, Controls}),
869    log2(Data, "modify reply = ~p~n", [Resp]),
870    check_reply(Data#eldap{id = Id}, Resp, modifyResponse).
871
872%%% --------------------------------------------------------------------
873%%% PasswdModifyRequest
874%%% --------------------------------------------------------------------
875
876-define(PASSWD_MODIFY_OID, "1.3.6.1.4.1.4203.1.11.1").
877
878do_passwd_modify(Data, Dn, NewPasswd, OldPasswd, Controls) ->
879    case catch do_passwd_modify_0(Data, Dn, NewPasswd, OldPasswd, Controls) of
880	{error,Emsg}        -> {ldap_closed_p(Data, Emsg),Data};
881	{'EXIT',Error}      -> {ldap_closed_p(Data, Error),Data};
882	{ok,NewData}        -> {ok,NewData};
883	{{ok,Val},NewData}  -> {{ok,Val},NewData};
884        {ok,Passwd,NewData} -> {{ok, Passwd},NewData};
885	Else                -> {ldap_closed_p(Data, Else),Data}
886    end.
887
888do_passwd_modify_0(Data, Dn, NewPasswd, OldPasswd, Controls) ->
889    Req = #'PasswdModifyRequestValue'{userIdentity = Dn,
890                                      oldPasswd = OldPasswd,
891                                      newPasswd = NewPasswd},
892    log2(Data, "modify password request = ~p~n", [Req]),
893    {ok, Bytes} = 'ELDAPv3':encode('PasswdModifyRequestValue', Req),
894    ExtReq = #'ExtendedRequest'{requestName = ?PASSWD_MODIFY_OID,
895                             requestValue = Bytes},
896    Id = bump_id(Data),
897    log2(Data, "extended request = ~p~n", [ExtReq]),
898    Reply = request(Data#eldap.fd, Data, Id, {extendedReq, ExtReq, Controls}),
899    log2(Data, "modify password reply = ~p~n", [Reply]),
900    exec_passwd_modify_reply(Data#eldap{id = Id}, Reply).
901
902exec_passwd_modify_reply(Data, {ok,Msg}) when
903  Msg#'LDAPMessage'.messageID == Data#eldap.id ->
904    case Msg#'LDAPMessage'.protocolOp of
905	{extendedResp, Result} ->
906	    case Result#'ExtendedResponse'.resultCode of
907		success ->
908                    case Result#'ExtendedResponse'.responseValue of
909                        asn1_NOVALUE ->
910                            {ok, Data};
911                        Value ->
912                            case 'ELDAPv3':decode('PasswdModifyResponseValue', Value) of
913                                {ok,#'PasswdModifyResponseValue'{genPasswd = Passwd}} ->
914                                    {ok, Passwd, Data};
915                                Error ->
916                                    throw(Error)
917                            end
918                    end;
919		referral ->
920		    {{ok, {referral,Result#'ExtendedResponse'.referral}}, Data};
921		Error ->
922		    {error, {response,Error}}
923	    end;
924	Other -> {error, Other}
925    end;
926exec_passwd_modify_reply(_, Error) ->
927    {error, Error}.
928
929%%% --------------------------------------------------------------------
930%%% modifyDNRequest
931%%% --------------------------------------------------------------------
932
933do_modify_dn(Data, Entry, NewRDN, DelOldRDN, NewSup, Controls) ->
934    case catch do_modify_dn_0(Data, Entry, NewRDN, DelOldRDN, NewSup, Controls) of
935	{error,Emsg}   -> {ldap_closed_p(Data, Emsg),Data};
936	{'EXIT',Error} -> {ldap_closed_p(Data, Error),Data};
937	{ok,NewData}   -> {ok,NewData};
938	{{ok,Val},NewData} -> {{ok,Val},NewData};
939	Else           -> {ldap_closed_p(Data, Else),Data}
940    end.
941
942do_modify_dn_0(Data, Entry, NewRDN, DelOldRDN, NewSup, Controls) ->
943    Req = #'ModifyDNRequest'{entry = Entry,
944			     newrdn = NewRDN,
945			     deleteoldrdn = DelOldRDN,
946			     newSuperior = NewSup},
947    S = Data#eldap.fd,
948    Id = bump_id(Data),
949    log2(Data, "modify DN request = ~p~n", [Req]),
950    Resp = request(S, Data, Id, {modDNRequest, Req, Controls}),
951    log2(Data, "modify DN reply = ~p~n", [Resp]),
952    check_reply(Data#eldap{id = Id}, Resp, modDNResponse).
953
954%%%--------------------------------------------------------------------
955%%% unbindRequest
956%%%--------------------------------------------------------------------
957do_unbind(Data) ->
958    Req = "",
959    log2(Data, "unbind request = ~p (has no reply)~n", [Req]),
960    _ = case Data#eldap.using_tls of
961            true ->
962                send_request(Data#eldap.fd, Data, Data#eldap.id, {unbindRequest, Req}),
963                ssl:close(Data#eldap.fd);
964            false ->
965                OldTrapExit = process_flag(trap_exit, true),
966                catch send_request(Data#eldap.fd, Data, Data#eldap.id, {unbindRequest, Req}),
967                catch gen_tcp:close(Data#eldap.fd),
968                receive
969                    {'EXIT', _From, _Reason} -> ok
970                after 0 -> ok
971                end,
972                process_flag(trap_exit, OldTrapExit)
973        end,
974    {no_reply, Data#eldap{binddn = (#eldap{})#eldap.binddn,
975			  passwd = (#eldap{})#eldap.passwd,
976			  fd     = (#eldap{})#eldap.fd,
977			  using_tls = false
978			 }}.
979
980
981%%% --------------------------------------------------------------------
982%%% Send an LDAP request and receive the answer
983%%% --------------------------------------------------------------------
984request(S, Data, ID, Request) ->
985    send_request(S, Data, ID, Request),
986    recv_response(S, Data).
987
988send_request(S, Data, Id, {T,P}) ->
989    send_the_LDAPMessage(S, Data, #'LDAPMessage'{messageID = Id,
990						 protocolOp = {T,P}});
991send_request(S, Data, Id, {T,P,asn1_NOVALUE}) ->
992    send_the_LDAPMessage(S, Data, #'LDAPMessage'{messageID = Id,
993						 protocolOp = {T,P}});
994send_request(S, Data, Id, {T,P,Controls0}) ->
995    Controls = [#'Control'{controlType=F1,
996			   criticality=F2,
997			   controlValue=F3} || {control,F1,F2,F3} <- Controls0],
998    send_the_LDAPMessage(S, Data, #'LDAPMessage'{messageID = Id,
999						 protocolOp = {T,P},
1000						 controls = Controls}).
1001
1002send_the_LDAPMessage(S, Data, LDAPMessage) ->
1003    {ok,Bytes} = 'ELDAPv3':encode('LDAPMessage', LDAPMessage),
1004    case do_send(S, Data, Bytes) of
1005	{error,Reason} -> throw({gen_tcp_error,Reason});
1006	Else           -> Else
1007    end.
1008
1009do_send(S, Data, Bytes) when Data#eldap.using_tls == false ->
1010    gen_tcp:send(S, Bytes);
1011do_send(S, Data, Bytes) when Data#eldap.using_tls == true ->
1012    ssl:send(S, Bytes).
1013
1014do_recv(S, #eldap{using_tls=false, timeout=Timeout}, Len) ->
1015    gen_tcp:recv(S, Len, Timeout);
1016do_recv(S, #eldap{using_tls=true, timeout=Timeout}, Len) ->
1017    ssl:recv(S, Len, Timeout).
1018
1019recv_response(S, Data) ->
1020    case do_recv(S, Data, 0) of
1021	{ok, Packet} ->
1022	    case 'ELDAPv3':decode('LDAPMessage', Packet) of
1023		{ok,Resp} -> {ok,Resp};
1024		Error     -> throw(Error)
1025	    end;
1026	{error,Reason} ->
1027	    throw({gen_tcp_error, Reason})
1028    end.
1029
1030%%% Check for expected kind of reply
1031check_reply(Data, {ok,Msg}, Op) when
1032  Msg#'LDAPMessage'.messageID == Data#eldap.id ->
1033    case Msg#'LDAPMessage'.protocolOp of
1034	{Op, Result} ->
1035	    case Result#'LDAPResult'.resultCode of
1036		success -> {ok,Data};
1037		referral -> {{ok, {referral,Result#'LDAPResult'.referral}}, Data};
1038		Error   -> {error, Error}
1039	    end;
1040	Other -> {error, Other}
1041    end;
1042check_reply(_, Error, _) ->
1043    {error, Error}.
1044
1045
1046%%% --------------------------------------------------------------------
1047%%% Verify the input data
1048%%% --------------------------------------------------------------------
1049
1050v_filter({'and',L})           -> {'and',L};
1051v_filter({'or', L})           -> {'or',L};
1052v_filter({'not',L})           -> {'not',L};
1053v_filter({equalityMatch,AV})  -> {equalityMatch,AV};
1054v_filter({greaterOrEqual,AV}) -> {greaterOrEqual,AV};
1055v_filter({lessOrEqual,AV})    -> {lessOrEqual,AV};
1056v_filter({approxMatch,AV})    -> {approxMatch,AV};
1057v_filter({present,A})         -> {present,A};
1058v_filter({substrings,S}) when is_record(S,'SubstringFilter') -> {substrings,S};
1059v_filter({extensibleMatch,S}) when is_record(S,'MatchingRuleAssertion') -> {extensibleMatch,S};
1060v_filter(_Filter) -> throw({error,concat(["unknown filter: ",_Filter])}).
1061
1062v_modifications(Mods) ->
1063    F = fun({_,Op,_}) ->
1064		case lists:member(Op,[add,delete,replace]) of
1065		    true -> true;
1066		    _    -> throw({error,{mod_operation,Op}})
1067		end
1068	end,
1069    lists:foreach(F, Mods).
1070
1071v_substr([{Key,Str}|T]) when is_list(Str),Key==initial;Key==any;Key==final ->
1072    [{Key,Str}|v_substr(T)];
1073v_substr([H|_]) ->
1074    throw({error,{substring_arg,H}});
1075v_substr([]) ->
1076    [].
1077v_scope(baseObject)   -> baseObject;
1078v_scope(singleLevel)  -> singleLevel;
1079v_scope(wholeSubtree) -> wholeSubtree;
1080v_scope(_Scope)       -> throw({error,concat(["unknown scope: ",_Scope])}).
1081
1082v_deref(DR = neverDerefAliases)   -> DR;
1083v_deref(DR = derefInSearching)    -> DR;
1084v_deref(DR = derefFindingBaseObj) -> DR;
1085v_deref(DR = derefAlways )        -> DR.
1086
1087v_bool(true)  -> true;
1088v_bool(false) -> false;
1089v_bool(_Bool) -> throw({error,concat(["not Boolean: ",_Bool])}).
1090
1091v_timeout(I) when is_integer(I), I>=0 -> I;
1092v_timeout(_I) -> throw({error,concat(["timeout not positive integer: ",_I])}).
1093
1094v_attributes(Attrs) ->
1095    F = fun(A) when is_list(A) -> A;
1096	   (A) -> throw({error,concat(["attribute not String: ",A])})
1097	end,
1098    lists:map(F,Attrs).
1099
1100
1101%%% --------------------------------------------------------------------
1102%%% Log routines. Call a user provided log routine F.
1103%%% --------------------------------------------------------------------
1104
1105%log1(Data, Str, Args) -> log(Data, Str, Args, 1).
1106log2(Data, Str, Args) -> log(Data, Str, Args, 2).
1107
1108log(Data, Str, Args, Level) when is_function(Data#eldap.log) ->
1109    catch (Data#eldap.log)(Level, Str, Args);
1110log(_, _, _, _) ->
1111    ok.
1112
1113
1114%%% --------------------------------------------------------------------
1115%%% Misc. routines
1116%%% --------------------------------------------------------------------
1117
1118send(To,Msg) ->
1119    To ! {self(), Msg},
1120    ok.
1121
1122recv(From)   ->
1123    receive
1124	{From, Msg} -> Msg;
1125	{'EXIT', From, Reason} ->
1126	    {error, {internal_error, Reason}}
1127    end.
1128
1129ldap_closed_p(Data, Emsg) when Data#eldap.using_tls == true ->
1130    %% Check if the SSL socket seems to be alive or not
1131    case catch ssl:sockname(Data#eldap.fd) of
1132	{error, _} ->
1133	    _ = ssl:close(Data#eldap.fd),
1134	    {error, ldap_closed};
1135	{ok, _} ->
1136	    {error, Emsg};
1137	_ ->
1138	    %% sockname crashes if the socket pid is not alive
1139	    {error, ldap_closed}
1140    end;
1141ldap_closed_p(Data, Emsg) ->
1142    %% non-SSL socket
1143    case inet:port(Data#eldap.fd) of
1144	{error,_} -> {error, ldap_closed};
1145	_         -> {error,Emsg}
1146    end.
1147
1148bump_id(Data) -> Data#eldap.id + 1.
1149
1150
1151%%% --------------------------------------------------------------------
1152%%% parse_dn/1  -  Implementation of RFC 2253:
1153%%%
1154%%%   "UTF-8 String Representation of Distinguished Names"
1155%%%
1156%%% Test cases:
1157%%%
1158%%%  The simplest case:
1159%%%
1160%%%  1> eldap:parse_dn("CN=Steve Kille,O=Isode Limited,C=GB").
1161%%%  {ok,[[{attribute_type_and_value,"CN","Steve Kille"}],
1162%%%       [{attribute_type_and_value,"O","Isode Limited"}],
1163%%%       [{attribute_type_and_value,"C","GB"}]]}
1164%%%
1165%%%  The first RDN is multi-valued:
1166%%%
1167%%%  2> eldap:parse_dn("OU=Sales+CN=J. Smith,O=Widget Inc.,C=US").
1168%%%  {ok,[[{attribute_type_and_value,"OU","Sales"},
1169%%%        {attribute_type_and_value,"CN","J. Smith"}],
1170%%%       [{attribute_type_and_value,"O","Widget Inc."}],
1171%%%       [{attribute_type_and_value,"C","US"}]]}
1172%%%
1173%%%  Quoting a comma:
1174%%%
1175%%%  3> eldap:parse_dn("CN=L. Eagle,O=Sue\\, Grabbit and Runn,C=GB").
1176%%%  {ok,[[{attribute_type_and_value,"CN","L. Eagle"}],
1177%%%       [{attribute_type_and_value,"O","Sue\\, Grabbit and Runn"}],
1178%%%       [{attribute_type_and_value,"C","GB"}]]}
1179%%%
1180%%%  A value contains a carriage return:
1181%%%
1182%%%  4> eldap:parse_dn("CN=Before
1183%%%  4> After,O=Test,C=GB").
1184%%%  {ok,[[{attribute_type_and_value,"CN","Before\nAfter"}],
1185%%%       [{attribute_type_and_value,"O","Test"}],
1186%%%       [{attribute_type_and_value,"C","GB"}]]}
1187%%%
1188%%%  5> eldap:parse_dn("CN=Before\\0DAfter,O=Test,C=GB").
1189%%%  {ok,[[{attribute_type_and_value,"CN","Before\\0DAfter"}],
1190%%%       [{attribute_type_and_value,"O","Test"}],
1191%%%       [{attribute_type_and_value,"C","GB"}]]}
1192%%%
1193%%%  An RDN in OID form:
1194%%%
1195%%%  6> eldap:parse_dn("1.3.6.1.4.1.1466.0=#04024869,O=Test,C=GB").
1196%%%  {ok,[[{attribute_type_and_value,"1.3.6.1.4.1.1466.0","#04024869"}],
1197%%%       [{attribute_type_and_value,"O","Test"}],
1198%%%       [{attribute_type_and_value,"C","GB"}]]}
1199%%%
1200%%%
1201%%% --------------------------------------------------------------------
1202
1203parse_dn("") -> % empty DN string
1204    {ok,[]};
1205parse_dn([H|_] = Str) when H=/=$, -> % 1:st name-component !
1206    case catch parse_name(Str,[]) of
1207	{'EXIT',Reason} -> {parse_error,internal_error,Reason};
1208	Else            -> Else
1209    end.
1210
1211parse_name("",Acc)  ->
1212    {ok,lists:reverse(Acc)};
1213parse_name([$,|T],Acc) -> % N:th name-component !
1214    parse_name(T,Acc);
1215parse_name(Str,Acc) ->
1216    {Rest,NameComponent} = parse_name_component(Str),
1217    parse_name(Rest,[NameComponent|Acc]).
1218
1219parse_name_component(Str) ->
1220    parse_name_component(Str,[]).
1221
1222parse_name_component(Str,Acc) ->
1223    case parse_attribute_type_and_value(Str) of
1224	{[$+|Rest], ATV} ->
1225	    parse_name_component(Rest,[ATV|Acc]);
1226	{Rest,ATV} ->
1227	    {Rest,lists:reverse([ATV|Acc])}
1228    end.
1229
1230parse_attribute_type_and_value(Str) ->
1231    case parse_attribute_type(Str) of
1232	{_Rest,[]} ->
1233	    parse_error(expecting_attribute_type,Str);
1234	{Rest,Type} ->
1235	    Rest2 = parse_equal_sign(Rest),
1236	    {Rest3,Value} = parse_attribute_value(Rest2),
1237	    {Rest3,{attribute_type_and_value,Type,Value}}
1238    end.
1239
1240-define(IS_ALPHA(X) , X>=$a,X=<$z;X>=$A,X=<$Z ).
1241-define(IS_DIGIT(X) , X>=$0,X=<$9 ).
1242-define(IS_SPECIAL(X) , X==$,;X==$=;X==$+;X==$<;X==$>;X==$#;X==$; ).
1243-define(IS_QUOTECHAR(X) , X=/=$\\,X=/=$" ).
1244-define(IS_STRINGCHAR(X) ,
1245	X=/=$,,X=/=$=,X=/=$+,X=/=$<,X=/=$>,X=/=$#,X=/=$;,?IS_QUOTECHAR(X) ).
1246-define(IS_HEXCHAR(X) , ?IS_DIGIT(X);X>=$a,X=<$f;X>=$A,X=<$F ).
1247
1248parse_attribute_type([H|T]) when ?IS_ALPHA(H) ->
1249    %% NB: It must be an error in the RFC in the definition
1250    %% of 'attributeType', should be: (ALPHA *keychar)
1251    {Rest,KeyChars} = parse_keychars(T),
1252    {Rest,[H|KeyChars]};
1253parse_attribute_type([H|_] = Str) when ?IS_DIGIT(H) ->
1254    parse_oid(Str);
1255parse_attribute_type(Str) ->
1256    parse_error(invalid_attribute_type,Str).
1257
1258
1259
1260%%% Is a hexstring !
1261parse_attribute_value([$#,X,Y|T]) when ?IS_HEXCHAR(X),?IS_HEXCHAR(Y) ->
1262    {Rest,HexString} = parse_hexstring(T),
1263    {Rest,[$#,X,Y|HexString]};
1264%%% Is a "quotation-sequence" !
1265parse_attribute_value([$"|T]) ->
1266    {Rest,Quotation} = parse_quotation(T),
1267    {Rest,[$"|Quotation]};
1268%%% Is a stringchar , pair or Empty !
1269parse_attribute_value(Str) ->
1270    parse_string(Str).
1271
1272parse_hexstring(Str) ->
1273    parse_hexstring(Str,[]).
1274
1275parse_hexstring([X,Y|T],Acc) when ?IS_HEXCHAR(X),?IS_HEXCHAR(Y) ->
1276    parse_hexstring(T,[Y,X|Acc]);
1277parse_hexstring(T,Acc) ->
1278    {T,lists:reverse(Acc)}.
1279
1280parse_quotation([$"|T]) -> % an empty: ""  is ok !
1281    {T,[$"]};
1282parse_quotation(Str) ->
1283    parse_quotation(Str,[]).
1284
1285%%% Parse to end of quotation
1286parse_quotation([$"|T],Acc) ->
1287    {T,lists:reverse([$"|Acc])};
1288parse_quotation([X|T],Acc) when ?IS_QUOTECHAR(X) ->
1289    parse_quotation(T,[X|Acc]);
1290parse_quotation([$\\,X|T],Acc) when ?IS_SPECIAL(X) ->
1291    parse_quotation(T,[X,$\\|Acc]);
1292parse_quotation([$\\,$\\|T],Acc) ->
1293    parse_quotation(T,[$\\,$\\|Acc]);
1294parse_quotation([$\\,$"|T],Acc) ->
1295    parse_quotation(T,[$",$\\|Acc]);
1296parse_quotation([$\\,X,Y|T],Acc) when ?IS_HEXCHAR(X),?IS_HEXCHAR(Y) ->
1297    parse_quotation(T,[Y,X,$\\|Acc]);
1298parse_quotation(T,_) ->
1299    parse_error(expecting_double_quote_mark,T).
1300
1301parse_string(Str) ->
1302    parse_string(Str,[]).
1303
1304parse_string("",Acc) ->
1305    {"",lists:reverse(Acc)};
1306parse_string([H|T],Acc) when ?IS_STRINGCHAR(H) ->
1307    parse_string(T,[H|Acc]);
1308parse_string([$\\,X|T],Acc) when ?IS_SPECIAL(X) -> % is a pair !
1309    parse_string(T,[X,$\\|Acc]);
1310parse_string([$\\,$\\|T],Acc)                   -> % is a pair !
1311    parse_string(T,[$\\,$\\|Acc]);
1312parse_string([$\\,$" |T],Acc)                   -> % is a pair !
1313    parse_string(T,[$" ,$\\|Acc]);
1314parse_string([$\\,X,Y|T],Acc) when ?IS_HEXCHAR(X),?IS_HEXCHAR(Y) -> % is a pair!
1315    parse_string(T,[Y,X,$\\|Acc]);
1316parse_string(T,Acc) ->
1317    {T,lists:reverse(Acc)}.
1318
1319parse_equal_sign([$=|T]) -> T;
1320parse_equal_sign(T)      -> parse_error(expecting_equal_sign,T).
1321
1322parse_keychars(Str) -> parse_keychars(Str,[]).
1323
1324parse_keychars([H|T],Acc) when ?IS_ALPHA(H) -> parse_keychars(T,[H|Acc]);
1325parse_keychars([H|T],Acc) when ?IS_DIGIT(H) -> parse_keychars(T,[H|Acc]);
1326parse_keychars([$-|T],Acc)                  -> parse_keychars(T,[$-|Acc]);
1327parse_keychars(T,Acc)                       -> {T,lists:reverse(Acc)}.
1328
1329parse_oid(Str) -> parse_oid(Str,[]).
1330
1331parse_oid([H,$.|T], Acc) when ?IS_DIGIT(H) ->
1332    parse_oid(T,[$.,H|Acc]);
1333parse_oid([H|T], Acc) when ?IS_DIGIT(H) ->
1334    parse_oid(T,[H|Acc]);
1335parse_oid(T, Acc) ->
1336    {T,lists:reverse(Acc)}.
1337
1338parse_error(Emsg,Rest) ->
1339    throw({parse_error,Emsg,Rest}).
1340
1341
1342%%% --------------------------------------------------------------------
1343%%% Parse LDAP url according to RFC 2255
1344%%%
1345%%% Test case:
1346%%%
1347%%%  2> eldap:parse_ldap_url("ldap://10.42.126.33:389/cn=Administrative%20CA,o=Post%20Danmark,c=DK?certificateRevokationList;binary").
1348%%%  {ok,{{10,42,126,33},389},
1349%%%      [[{attribute_type_and_value,"cn","Administrative%20CA"}],
1350%%%       [{attribute_type_and_value,"o","Post%20Danmark"}],
1351%%%       [{attribute_type_and_value,"c","DK"}]],
1352%%%      {attributes,["certificateRevokationList;binary"]}}
1353%%%
1354%%% --------------------------------------------------------------------
1355
1356parse_ldap_url("ldap://" ++ Rest1 = Str) ->
1357    {Rest2,HostPort} = parse_hostport(Rest1),
1358    %% Split the string into DN and Attributes+etc
1359    {Sdn,Rest3} = split_string(rm_leading_slash(Rest2),$?),
1360    case parse_dn(Sdn) of
1361	{parse_error,internal_error,_Reason} ->
1362	    {parse_error,internal_error,{Str,[]}};
1363	{parse_error,Emsg,Tail} ->
1364	    Head = get_head(Str,Tail),
1365	    {parse_error,Emsg,{Head,Tail}};
1366	{ok,DN} ->
1367            %% We stop parsing here for now and leave
1368            %% 'scope', 'filter' and 'extensions' to
1369            %% be implemented later if needed.
1370	    {_Rest4,Attributes} = parse_attributes(Rest3),
1371	    {ok,HostPort,DN,Attributes}
1372    end.
1373
1374rm_leading_slash([$/|Tail]) -> Tail;
1375rm_leading_slash(Tail)      -> Tail.
1376
1377parse_attributes([$?|Tail]) ->
1378    case split_string(Tail,$?) of
1379        {[],Attributes} ->
1380	    {[],{attributes,string:lexemes(Attributes,",")}};
1381        {Attributes,Rest} ->
1382            {Rest,{attributes,string:lexemes(Attributes,",")}}
1383    end.
1384
1385parse_hostport(Str) ->
1386    {HostPort,Rest} = split_string(Str,$/),
1387    case split_string(HostPort,$:) of
1388	{Shost,[]} ->
1389	    {Rest,{parse_host(Rest,Shost),?LDAP_PORT}};
1390	{Shost,[$:|Sport]} ->
1391	    {Rest,{parse_host(Rest,Shost),
1392		   parse_port(Rest,Sport)}}
1393    end.
1394
1395parse_port(Rest,Sport) ->
1396    try	list_to_integer(Sport)
1397    catch _:_ -> parse_error(parsing_port,Rest)
1398    end.
1399
1400parse_host(Rest,Shost) ->
1401    case catch validate_host(Shost) of
1402	{parse_error,Emsg,_} -> parse_error(Emsg,Rest);
1403	Host -> Host
1404    end.
1405
1406validate_host(Shost) ->
1407    case inet_parse:address(Shost) of
1408	{ok,Host} -> Host;
1409	_ ->
1410	    case inet_parse:domain(Shost) of
1411		true -> Shost;
1412		_    -> parse_error(parsing_host,Shost)
1413	    end
1414    end.
1415
1416
1417split_string(Str,Key) ->
1418    Pred = fun(X) when X==Key -> false; (_) -> true end,
1419    lists:splitwith(Pred, Str).
1420
1421get_head(Str,Tail) ->
1422    get_head(Str,Tail,[]).
1423
1424%%% Should always succeed !
1425get_head([H|Tail],Tail,Rhead) -> lists:reverse([H|Rhead]);
1426get_head([H|Rest],Tail,Rhead) -> get_head(Rest,Tail,[H|Rhead]).
1427