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