PageRenderTime 64ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/eldap/src/eldap.erl

https://github.com/babo/jungerl
Erlang | 1082 lines | 661 code | 148 blank | 273 comment | 11 complexity | 2b13ba79cca61207396bd5fc427b33b1 MD5 | raw file
Possible License(s): LGPL-2.1, BSD-3-Clause, AGPL-1.0
  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. -vc('$Id$ ').
  9. -export([open/1,open/2,simple_bind/3,controlling_process/2,
  10. baseObject/0,singleLevel/0,wholeSubtree/0,close/1,
  11. equalityMatch/2,greaterOrEqual/2,lessOrEqual/2,
  12. approxMatch/2,search/2,substrings/2,present/1,
  13. 'and'/1,'or'/1,'not'/1,modify/3, mod_add/2, mod_delete/2,
  14. mod_replace/2, add/3, delete/2, modify_dn/5,parse_dn/1,
  15. parse_ldap_url/1]).
  16. -import(lists,[concat/1]).
  17. -include("ELDAPv3.hrl").
  18. -include("eldap.hrl").
  19. -define(LDAP_VERSION, 3).
  20. -define(LDAP_PORT, 389).
  21. -define(LDAPS_PORT, 636).
  22. -record(eldap, {version = ?LDAP_VERSION,
  23. host, % Host running LDAP server
  24. port = ?LDAP_PORT, % The LDAP server port
  25. fd, % Socket filedescriptor.
  26. binddn = "", % Name of the entry to bind as
  27. passwd, % Password for (above) entry
  28. id = 0, % LDAP Request ID
  29. log, % User provided log function
  30. timeout = infinity, % Request timeout
  31. anon_auth = false, % Allow anonymous authentication
  32. use_tls = false % LDAP/LDAPS
  33. }).
  34. %%% For debug purposes
  35. %%-define(PRINT(S, A), io:fwrite("~w(~w): " ++ S, [?MODULE,?LINE|A])).
  36. -define(PRINT(S, A), true).
  37. -define(elog(S, A), error_logger:info_msg("~w(~w): "++S,[?MODULE,?LINE|A])).
  38. %%% ====================================================================
  39. %%% Exported interface
  40. %%% ====================================================================
  41. %%% --------------------------------------------------------------------
  42. %%% open(Hosts [,Opts] )
  43. %%% --------------------
  44. %%% Setup a connection to on of the Hosts in the argument
  45. %%% list. Stop at the first successful connection attempt.
  46. %%% Valid Opts are: Where:
  47. %%%
  48. %%% {port, Port} - Port is the port number
  49. %%% {log, F} - F(LogLevel, FormatString, ListOfArgs)
  50. %%% {timeout, milliSec} - request timeout
  51. %%%
  52. %%% --------------------------------------------------------------------
  53. open(Hosts) ->
  54. open(Hosts, []).
  55. open(Hosts, Opts) when list(Hosts), list(Opts) ->
  56. Self = self(),
  57. Pid = spawn_link(fun() -> init(Hosts, Opts, Self) end),
  58. recv(Pid).
  59. %%% --------------------------------------------------------------------
  60. %%% Shutdown connection (and process) asynchronous.
  61. %%% --------------------------------------------------------------------
  62. close(Handle) when pid(Handle) ->
  63. send(Handle, close).
  64. %%% --------------------------------------------------------------------
  65. %%% Set who we should link ourselves to
  66. %%% --------------------------------------------------------------------
  67. controlling_process(Handle, Pid) when pid(Handle),pid(Pid) ->
  68. link(Pid),
  69. send(Handle, {cnt_proc, Pid}),
  70. recv(Handle).
  71. %%% --------------------------------------------------------------------
  72. %%% Authenticate ourselves to the Directory
  73. %%% using simple authentication.
  74. %%%
  75. %%% Dn - The name of the entry to bind as
  76. %%% Passwd - The password to be used
  77. %%%
  78. %%% Returns: ok | {error, Error}
  79. %%% --------------------------------------------------------------------
  80. simple_bind(Handle, Dn, Passwd) when pid(Handle) ->
  81. send(Handle, {simple_bind, Dn, Passwd}),
  82. recv(Handle).
  83. %%% --------------------------------------------------------------------
  84. %%% Add an entry. The entry field MUST NOT exist for the AddRequest
  85. %%% to succeed. The parent of the entry MUST exist.
  86. %%% Example:
  87. %%%
  88. %%% add(Handle,
  89. %%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com",
  90. %%% [{"objectclass", ["person"]},
  91. %%% {"cn", ["Bill Valentine"]},
  92. %%% {"sn", ["Valentine"]},
  93. %%% {"telephoneNumber", ["545 555 00"]}]
  94. %%% )
  95. %%% --------------------------------------------------------------------
  96. add(Handle, Entry, Attributes) when pid(Handle),list(Entry),list(Attributes) ->
  97. send(Handle, {add, Entry, add_attrs(Attributes)}),
  98. recv(Handle).
  99. %%% Do sanity check !
  100. add_attrs(Attrs) ->
  101. F = fun({Type,Vals}) when list(Type),list(Vals) ->
  102. %% Confused ? Me too... :-/
  103. {'AddRequest_attributes',Type, Vals}
  104. end,
  105. case catch lists:map(F, Attrs) of
  106. {'EXIT', _} -> throw({error, attribute_values});
  107. Else -> Else
  108. end.
  109. %%% --------------------------------------------------------------------
  110. %%% Delete an entry. The entry consists of the DN of
  111. %%% the entry to be deleted.
  112. %%% Example:
  113. %%%
  114. %%% delete(Handle,
  115. %%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com"
  116. %%% )
  117. %%% --------------------------------------------------------------------
  118. delete(Handle, Entry) when pid(Handle), list(Entry) ->
  119. send(Handle, {delete, Entry}),
  120. recv(Handle).
  121. %%% --------------------------------------------------------------------
  122. %%% Modify an entry. Given an entry a number of modification
  123. %%% operations can be performed as one atomic operation.
  124. %%% Example:
  125. %%%
  126. %%% modify(Handle,
  127. %%% "cn=Torbjorn Tornkvist, ou=people, o=Bluetail AB, dc=bluetail, dc=com",
  128. %%% [replace("telephoneNumber", ["555 555 00"]),
  129. %%% add("description", ["LDAP hacker"])]
  130. %%% )
  131. %%% --------------------------------------------------------------------
  132. modify(Handle, Object, Mods) when pid(Handle), list(Object), list(Mods) ->
  133. send(Handle, {modify, Object, Mods}),
  134. recv(Handle).
  135. %%%
  136. %%% Modification operations.
  137. %%% Example:
  138. %%% replace("telephoneNumber", ["555 555 00"])
  139. %%%
  140. mod_add(Type, Values) when list(Type), list(Values) -> m(add, Type, Values).
  141. mod_delete(Type, Values) when list(Type), list(Values) -> m(delete, Type, Values).
  142. mod_replace(Type, Values) when list(Type), list(Values) -> m(replace, Type, Values).
  143. m(Operation, Type, Values) ->
  144. #'ModifyRequest_modification_SEQOF'{
  145. operation = Operation,
  146. modification = #'AttributeTypeAndValues'{
  147. type = Type,
  148. vals = Values}}.
  149. %%% --------------------------------------------------------------------
  150. %%% Modify an entry. Given an entry a number of modification
  151. %%% operations can be performed as one atomic operation.
  152. %%% Example:
  153. %%%
  154. %%% modify_dn(Handle,
  155. %%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com",
  156. %%% "cn=Ben Emerson",
  157. %%% true,
  158. %%% ""
  159. %%% )
  160. %%% --------------------------------------------------------------------
  161. modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup)
  162. when pid(Handle),list(Entry),list(NewRDN),atom(DelOldRDN),list(NewSup) ->
  163. send(Handle, {modify_dn, Entry, NewRDN,
  164. bool_p(DelOldRDN), optional(NewSup)}),
  165. recv(Handle).
  166. %%% Sanity checks !
  167. bool_p(Bool) when Bool==true;Bool==false -> Bool.
  168. optional([]) -> asn1_NOVALUE;
  169. optional(Value) -> Value.
  170. %%% --------------------------------------------------------------------
  171. %%% Synchronous search of the Directory returning a
  172. %%% requested set of attributes.
  173. %%%
  174. %%% Example:
  175. %%%
  176. %%% Filter = eldap:substrings("sn", [{any,"o"}]),
  177. %%% eldap:search(S, [{base, "dc=bluetail, dc=com"},
  178. %%% {filter, Filter},
  179. %%% {attributes,["cn"]}])),
  180. %%%
  181. %%% Returned result: {ok, #eldap_search_result{}}
  182. %%%
  183. %%% Example:
  184. %%%
  185. %%% {ok,{eldap_search_result,
  186. %%% [{eldap_entry,
  187. %%% "cn=Magnus Froberg, dc=bluetail, dc=com",
  188. %%% [{"cn",["Magnus Froberg"]}]},
  189. %%% {eldap_entry,
  190. %%% "cn=Torbjorn Tornkvist, dc=bluetail, dc=com",
  191. %%% [{"cn",["Torbjorn Tornkvist"]}]}],
  192. %%% []}}
  193. %%%
  194. %%% --------------------------------------------------------------------
  195. search(Handle, A) when pid(Handle), record(A, eldap_search) ->
  196. call_search(Handle, A);
  197. search(Handle, L) when pid(Handle), list(L) ->
  198. case catch parse_search_args(L) of
  199. {error, Emsg} -> {error, Emsg};
  200. A when record(A, eldap_search) -> call_search(Handle, A)
  201. end.
  202. call_search(Handle, A) ->
  203. send(Handle, {search, A}),
  204. recv(Handle).
  205. parse_search_args(Args) ->
  206. parse_search_args(Args, #eldap_search{scope = wholeSubtree}).
  207. parse_search_args([{base, Base}|T],A) ->
  208. parse_search_args(T,A#eldap_search{base = Base});
  209. parse_search_args([{filter, Filter}|T],A) ->
  210. parse_search_args(T,A#eldap_search{filter = Filter});
  211. parse_search_args([{scope, Scope}|T],A) ->
  212. parse_search_args(T,A#eldap_search{scope = Scope});
  213. parse_search_args([{attributes, Attrs}|T],A) ->
  214. parse_search_args(T,A#eldap_search{attributes = Attrs});
  215. parse_search_args([{types_only, TypesOnly}|T],A) ->
  216. parse_search_args(T,A#eldap_search{types_only = TypesOnly});
  217. parse_search_args([{timeout, Timeout}|T],A) when integer(Timeout) ->
  218. parse_search_args(T,A#eldap_search{timeout = Timeout});
  219. parse_search_args([H|_],_) ->
  220. throw({error,{unknown_arg, H}});
  221. parse_search_args([],A) ->
  222. A.
  223. %%%
  224. %%% The Scope parameter
  225. %%%
  226. baseObject() -> baseObject.
  227. singleLevel() -> singleLevel.
  228. wholeSubtree() -> wholeSubtree.
  229. %%%
  230. %%% Boolean filter operations
  231. %%%
  232. 'and'(ListOfFilters) when list(ListOfFilters) -> {'and',ListOfFilters}.
  233. 'or'(ListOfFilters) when list(ListOfFilters) -> {'or', ListOfFilters}.
  234. 'not'(Filter) when tuple(Filter) -> {'not',Filter}.
  235. %%%
  236. %%% The following Filter parameters consist of an attribute
  237. %%% and an attribute value. Example: F("uid","tobbe")
  238. %%%
  239. equalityMatch(Desc, Value) -> {equalityMatch, av_assert(Desc, Value)}.
  240. greaterOrEqual(Desc, Value) -> {greaterOrEqual, av_assert(Desc, Value)}.
  241. lessOrEqual(Desc, Value) -> {lessOrEqual, av_assert(Desc, Value)}.
  242. approxMatch(Desc, Value) -> {approxMatch, av_assert(Desc, Value)}.
  243. av_assert(Desc, Value) ->
  244. #'AttributeValueAssertion'{attributeDesc = Desc,
  245. assertionValue = Value}.
  246. %%%
  247. %%% Filter to check for the presence of an attribute
  248. %%%
  249. present(Attribute) when list(Attribute) ->
  250. {present, Attribute}.
  251. %%%
  252. %%% A substring filter seem to be based on a pattern:
  253. %%%
  254. %%% InitValue*AnyValue*FinalValue
  255. %%%
  256. %%% where all three parts seem to be optional (at least when
  257. %%% talking with an OpenLDAP server). Thus, the arguments
  258. %%% to substrings/2 looks like this:
  259. %%%
  260. %%% Type ::= string( <attribute> )
  261. %%% SubStr ::= listof( {initial,Value} | {any,Value}, {final,Value})
  262. %%%
  263. %%% Example: substrings("sn",[{initial,"To"},{any,"kv"},{final,"st"}])
  264. %%% will match entries containing: 'sn: Tornkvist'
  265. %%%
  266. substrings(Type, SubStr) when list(Type), list(SubStr) ->
  267. Ss = {'SubstringFilter_substrings',v_substr(SubStr)},
  268. {substrings,#'SubstringFilter'{type = Type,
  269. substrings = Ss}}.
  270. %%% --------------------------------------------------------------------
  271. %%% Worker process. We keep track of a controlling process to
  272. %%% be able to terminate together with it.
  273. %%% --------------------------------------------------------------------
  274. init(Hosts, Opts, Cpid) ->
  275. Data = parse_args(Opts, Cpid, #eldap{}),
  276. case try_connect(Hosts, Data) of
  277. {ok,Data2} ->
  278. send(Cpid, {ok,self()}),
  279. put(req_timeout, Data#eldap.timeout), % kludge...
  280. loop(Cpid, Data2);
  281. Else ->
  282. send(Cpid, Else),
  283. unlink(Cpid),
  284. exit(Else)
  285. end.
  286. parse_args([{port, Port}|T], Cpid, Data) when integer(Port) ->
  287. parse_args(T, Cpid, Data#eldap{port = Port});
  288. parse_args([{timeout, Timeout}|T], Cpid, Data) when integer(Timeout),Timeout>0 ->
  289. parse_args(T, Cpid, Data#eldap{timeout = Timeout});
  290. parse_args([{anon_auth, true}|T], Cpid, Data) ->
  291. parse_args(T, Cpid, Data#eldap{anon_auth = false});
  292. parse_args([{anon_auth, _}|T], Cpid, Data) ->
  293. parse_args(T, Cpid, Data);
  294. parse_args([{ssl, true}|T], Cpid, Data) ->
  295. parse_args(T, Cpid, Data#eldap{use_tls = true});
  296. parse_args([{ssl, _}|T], Cpid, Data) ->
  297. parse_args(T, Cpid, Data);
  298. parse_args([{log, F}|T], Cpid, Data) when function(F) ->
  299. parse_args(T, Cpid, Data#eldap{log = F});
  300. parse_args([{log, _}|T], Cpid, Data) ->
  301. parse_args(T, Cpid, Data);
  302. parse_args([H|_], Cpid, _) ->
  303. send(Cpid, {error,{wrong_option,H}}),
  304. exit(wrong_option);
  305. parse_args([], _, Data) ->
  306. Data.
  307. %%% Try to connect to the hosts in the listed order,
  308. %%% and stop with the first one to which a successful
  309. %%% connection is made.
  310. try_connect([Host|Hosts], Data) ->
  311. TcpOpts = [{packet, asn1}, {active,false}],
  312. case do_connect(Host, Data, TcpOpts) of
  313. {ok,Fd} -> {ok,Data#eldap{host = Host, fd = Fd}};
  314. _ -> try_connect(Hosts, Data)
  315. end;
  316. try_connect([],_) ->
  317. {error,"connect failed"}.
  318. do_connect(Host, Data, Opts) when Data#eldap.use_tls == false ->
  319. gen_tcp:connect(Host, Data#eldap.port, Opts, Data#eldap.timeout);
  320. do_connect(Host, Data, Opts) when Data#eldap.use_tls == true ->
  321. Vsn = erlang:system_info(version),
  322. if Vsn >= "5.3" ->
  323. %% In R9C, but not in R9B
  324. {_,_,X} = erlang:now(),
  325. ssl:seed("bkrlnateqqo" ++ integer_to_list(X));
  326. true -> true
  327. end,
  328. ssl:connect(Host, Data#eldap.port, [{verify,0}|Opts]).
  329. loop(Cpid, Data) ->
  330. receive
  331. {From, {search, A}} ->
  332. {Res,NewData} = do_search(Data, A),
  333. send(From,Res),
  334. loop(Cpid, NewData);
  335. {From, {modify, Obj, Mod}} ->
  336. {Res,NewData} = do_modify(Data, Obj, Mod),
  337. send(From,Res),
  338. loop(Cpid, NewData);
  339. {From, {modify_dn, Obj, NewRDN, DelOldRDN, NewSup}} ->
  340. {Res,NewData} = do_modify_dn(Data, Obj, NewRDN, DelOldRDN, NewSup),
  341. send(From,Res),
  342. loop(Cpid, NewData);
  343. {From, {add, Entry, Attrs}} ->
  344. {Res,NewData} = do_add(Data, Entry, Attrs),
  345. send(From,Res),
  346. loop(Cpid, NewData);
  347. {From, {delete, Entry}} ->
  348. {Res,NewData} = do_delete(Data, Entry),
  349. send(From,Res),
  350. loop(Cpid, NewData);
  351. {From, {simple_bind, Dn, Passwd}} ->
  352. {Res,NewData} = do_simple_bind(Data, Dn, Passwd),
  353. send(From,Res),
  354. loop(Cpid, NewData);
  355. {From, {cnt_proc, NewCpid}} ->
  356. unlink(Cpid),
  357. send(From,ok),
  358. ?PRINT("New Cpid is: ~p~n",[NewCpid]),
  359. loop(NewCpid, Data);
  360. {From, close} ->
  361. unlink(Cpid),
  362. exit(closed);
  363. {Cpid, 'EXIT', Reason} ->
  364. ?PRINT("Got EXIT from Cpid, reason=~p~n",[Reason]),
  365. exit(Reason);
  366. _XX ->
  367. ?PRINT("loop got: ~p~n",[_XX]),
  368. loop(Cpid, Data)
  369. end.
  370. %%% --------------------------------------------------------------------
  371. %%% bindRequest
  372. %%% --------------------------------------------------------------------
  373. %%% Authenticate ourselves to the directory using
  374. %%% simple authentication.
  375. do_simple_bind(Data, anon, anon) -> %% For testing
  376. do_the_simple_bind(Data, "", "");
  377. do_simple_bind(Data, Dn, _Passwd) when Dn=="",Data#eldap.anon_auth==false ->
  378. {{error,anonymous_auth},Data};
  379. do_simple_bind(Data, _Dn, Passwd) when Passwd=="",Data#eldap.anon_auth==false ->
  380. {{error,anonymous_auth},Data};
  381. do_simple_bind(Data, Dn, Passwd) ->
  382. do_the_simple_bind(Data, Dn, Passwd).
  383. do_the_simple_bind(Data, Dn, Passwd) ->
  384. case catch exec_simple_bind(Data#eldap{binddn = Dn,
  385. passwd = Passwd,
  386. id = bump_id(Data)}) of
  387. {ok,NewData} -> {ok,NewData};
  388. {error,Emsg} -> {{error,Emsg},Data};
  389. Else -> {{error,Else},Data}
  390. end.
  391. exec_simple_bind(Data) ->
  392. Req = #'BindRequest'{version = Data#eldap.version,
  393. name = Data#eldap.binddn,
  394. authentication = {simple, Data#eldap.passwd}},
  395. log2(Data, "bind request = ~p~n", [Req]),
  396. Reply = request(Data#eldap.fd, Data, Data#eldap.id, {bindRequest, Req}),
  397. log2(Data, "bind reply = ~p~n", [Reply]),
  398. exec_simple_bind_reply(Data, Reply).
  399. exec_simple_bind_reply(Data, {ok,Msg}) when
  400. Msg#'LDAPMessage'.messageID == Data#eldap.id ->
  401. case Msg#'LDAPMessage'.protocolOp of
  402. {bindResponse, Result} ->
  403. case Result#'BindResponse'.resultCode of
  404. success -> {ok,Data};
  405. Error -> {error, Error}
  406. end;
  407. Other -> {error, Other}
  408. end;
  409. exec_simple_bind_reply(_, Error) ->
  410. {error, Error}.
  411. %%% --------------------------------------------------------------------
  412. %%% searchRequest
  413. %%% --------------------------------------------------------------------
  414. do_search(Data, A) ->
  415. case catch do_search_0(Data, A) of
  416. {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data};
  417. {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data};
  418. {ok,Res,Ref,NewData} -> {{ok,polish(Res, Ref)},NewData};
  419. Else -> {ldap_closed_p(Data, Else),Data}
  420. end.
  421. %%%
  422. %%% Polish the returned search result
  423. %%%
  424. polish(Res, Ref) ->
  425. R = polish_result(Res),
  426. %%% No special treatment of referrals at the moment.
  427. #eldap_search_result{entries = R,
  428. referrals = Ref}.
  429. polish_result([H|T]) when record(H, 'SearchResultEntry') ->
  430. ObjectName = H#'SearchResultEntry'.objectName,
  431. F = fun({_,A,V}) -> {A,V} end,
  432. Attrs = lists:map(F, H#'SearchResultEntry'.attributes),
  433. [#eldap_entry{object_name = ObjectName,
  434. attributes = Attrs}|
  435. polish_result(T)];
  436. polish_result([]) ->
  437. [].
  438. do_search_0(Data, A) ->
  439. Req = #'SearchRequest'{baseObject = A#eldap_search.base,
  440. scope = v_scope(A#eldap_search.scope),
  441. derefAliases = neverDerefAliases,
  442. sizeLimit = 0, % no size limit
  443. timeLimit = v_timeout(A#eldap_search.timeout),
  444. typesOnly = v_bool(A#eldap_search.types_only),
  445. filter = v_filter(A#eldap_search.filter),
  446. attributes = v_attributes(A#eldap_search.attributes)
  447. },
  448. Id = bump_id(Data),
  449. collect_search_responses(Data#eldap{id=Id}, Req, Id).
  450. %%% The returned answers cames in one packet per entry
  451. %%% mixed with possible referals
  452. collect_search_responses(Data, Req, ID) ->
  453. S = Data#eldap.fd,
  454. log2(Data, "search request = ~p~n", [Req]),
  455. send_request(S, Data, ID, {searchRequest, Req}),
  456. Resp = recv_response(S, Data),
  457. log2(Data, "search reply = ~p~n", [Resp]),
  458. collect_search_responses(Data, S, ID, Resp, [], []).
  459. collect_search_responses(Data, S, ID, {ok,Msg}, Acc, Ref)
  460. when record(Msg,'LDAPMessage') ->
  461. case Msg#'LDAPMessage'.protocolOp of
  462. {'searchResDone',R} when R#'LDAPResult'.resultCode == success ->
  463. log2(Data, "search reply = searchResDone ~n", []),
  464. {ok,Acc,Ref,Data};
  465. {'searchResEntry',R} when record(R,'SearchResultEntry') ->
  466. Resp = recv_response(S, Data),
  467. log2(Data, "search reply = ~p~n", [Resp]),
  468. collect_search_responses(Data, S, ID, Resp, [R|Acc], Ref);
  469. {'searchResRef',R} ->
  470. %% At the moment we don't do anyting sensible here since
  471. %% I haven't been able to trigger the server to generate
  472. %% a response like this.
  473. Resp = recv_response(S, Data),
  474. log2(Data, "search reply = ~p~n", [Resp]),
  475. collect_search_responses(Data, S, ID, Resp, Acc, [R|Ref]);
  476. Else ->
  477. throw({error,Else})
  478. end;
  479. collect_search_responses(_, _, _, Else, _, _) ->
  480. throw({error,Else}).
  481. %%% --------------------------------------------------------------------
  482. %%% addRequest
  483. %%% --------------------------------------------------------------------
  484. do_add(Data, Entry, Attrs) ->
  485. case catch do_add_0(Data, Entry, Attrs) of
  486. {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data};
  487. {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data};
  488. {ok,NewData} -> {ok,NewData};
  489. Else -> {ldap_closed_p(Data, Else),Data}
  490. end.
  491. do_add_0(Data, Entry, Attrs) ->
  492. Req = #'AddRequest'{entry = Entry,
  493. attributes = Attrs},
  494. S = Data#eldap.fd,
  495. Id = bump_id(Data),
  496. log2(Data, "add request = ~p~n", [Req]),
  497. Resp = request(S, Data, Id, {addRequest, Req}),
  498. log2(Data, "add reply = ~p~n", [Resp]),
  499. check_reply(Data#eldap{id = Id}, Resp, addResponse).
  500. %%% --------------------------------------------------------------------
  501. %%% deleteRequest
  502. %%% --------------------------------------------------------------------
  503. do_delete(Data, Entry) ->
  504. case catch do_delete_0(Data, Entry) of
  505. {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data};
  506. {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data};
  507. {ok,NewData} -> {ok,NewData};
  508. Else -> {ldap_closed_p(Data, Else),Data}
  509. end.
  510. do_delete_0(Data, Entry) ->
  511. S = Data#eldap.fd,
  512. Id = bump_id(Data),
  513. log2(Data, "del request = ~p~n", [Entry]),
  514. Resp = request(S, Data, Id, {delRequest, Entry}),
  515. log2(Data, "del reply = ~p~n", [Resp]),
  516. check_reply(Data#eldap{id = Id}, Resp, delResponse).
  517. %%% --------------------------------------------------------------------
  518. %%% modifyRequest
  519. %%% --------------------------------------------------------------------
  520. do_modify(Data, Obj, Mod) ->
  521. case catch do_modify_0(Data, Obj, Mod) of
  522. {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data};
  523. {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data};
  524. {ok,NewData} -> {ok,NewData};
  525. Else -> {ldap_closed_p(Data, Else),Data}
  526. end.
  527. do_modify_0(Data, Obj, Mod) ->
  528. v_modifications(Mod),
  529. Req = #'ModifyRequest'{object = Obj,
  530. modification = Mod},
  531. S = Data#eldap.fd,
  532. Id = bump_id(Data),
  533. log2(Data, "modify request = ~p~n", [Req]),
  534. Resp = request(S, Data, Id, {modifyRequest, Req}),
  535. log2(Data, "modify reply = ~p~n", [Resp]),
  536. check_reply(Data#eldap{id = Id}, Resp, modifyResponse).
  537. %%% --------------------------------------------------------------------
  538. %%% modifyDNRequest
  539. %%% --------------------------------------------------------------------
  540. do_modify_dn(Data, Entry, NewRDN, DelOldRDN, NewSup) ->
  541. case catch do_modify_dn_0(Data, Entry, NewRDN, DelOldRDN, NewSup) of
  542. {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data};
  543. {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data};
  544. {ok,NewData} -> {ok,NewData};
  545. Else -> {ldap_closed_p(Data, Else),Data}
  546. end.
  547. do_modify_dn_0(Data, Entry, NewRDN, DelOldRDN, NewSup) ->
  548. Req = #'ModifyDNRequest'{entry = Entry,
  549. newrdn = NewRDN,
  550. deleteoldrdn = DelOldRDN,
  551. newSuperior = NewSup},
  552. S = Data#eldap.fd,
  553. Id = bump_id(Data),
  554. log2(Data, "modify DN request = ~p~n", [Req]),
  555. Resp = request(S, Data, Id, {modDNRequest, Req}),
  556. log2(Data, "modify DN reply = ~p~n", [Resp]),
  557. check_reply(Data#eldap{id = Id}, Resp, modDNResponse).
  558. %%% --------------------------------------------------------------------
  559. %%% Send an LDAP request and receive the answer
  560. %%% --------------------------------------------------------------------
  561. request(S, Data, ID, Request) ->
  562. send_request(S, Data, ID, Request),
  563. recv_response(S, Data).
  564. send_request(S, Data, ID, Request) ->
  565. Message = #'LDAPMessage'{messageID = ID,
  566. protocolOp = Request},
  567. {ok,Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message),
  568. case do_send(S, Data, Bytes) of
  569. {error,Reason} -> throw({gen_tcp_error,Reason});
  570. Else -> Else
  571. end.
  572. do_send(S, Data, Bytes) when Data#eldap.use_tls == false ->
  573. gen_tcp:send(S, Bytes);
  574. do_send(S, Data, Bytes) when Data#eldap.use_tls == true ->
  575. ssl:send(S, Bytes).
  576. do_recv(S, Data, Len, Timeout) when Data#eldap.use_tls == false ->
  577. gen_tcp:recv(S, Len, Timeout);
  578. do_recv(S, Data, Len, Timeout) when Data#eldap.use_tls == true ->
  579. ssl:recv(S, Len, Timeout).
  580. recv_response(S, Data) ->
  581. Timeout = get(req_timeout), % kludge...
  582. case do_recv(S, Data, 0, Timeout) of
  583. {ok, Packet} ->
  584. check_tag(Packet),
  585. case asn1rt:decode('ELDAPv3', 'LDAPMessage', Packet) of
  586. {ok,Resp} -> {ok,Resp};
  587. Error -> throw(Error)
  588. end;
  589. {error,Reason} ->
  590. throw({gen_tcp_error, Reason});
  591. Error ->
  592. throw(Error)
  593. end.
  594. %%% Sanity check of received packet
  595. check_tag(Data) ->
  596. case asn1rt_ber_bin:decode_tag(b2l(Data)) of
  597. {_Tag, Data1, _Rb} ->
  598. case asn1rt_ber_bin:decode_length(b2l(Data1)) of
  599. {{_Len, _Data2}, _Rb2} -> ok;
  600. _ -> throw({error,decoded_tag_length})
  601. end;
  602. _ -> throw({error,decoded_tag})
  603. end.
  604. %%% Check for expected kind of reply
  605. check_reply(Data, {ok,Msg}, Op) when
  606. Msg#'LDAPMessage'.messageID == Data#eldap.id ->
  607. case Msg#'LDAPMessage'.protocolOp of
  608. {Op, Result} ->
  609. case Result#'LDAPResult'.resultCode of
  610. success -> {ok,Data};
  611. Error -> {error, Error}
  612. end;
  613. Other -> {error, Other}
  614. end;
  615. check_reply(_, Error, _) ->
  616. {error, Error}.
  617. %%% --------------------------------------------------------------------
  618. %%% Verify the input data
  619. %%% --------------------------------------------------------------------
  620. v_filter({'and',L}) -> {'and',L};
  621. v_filter({'or', L}) -> {'or',L};
  622. v_filter({'not',L}) -> {'not',L};
  623. v_filter({equalityMatch,AV}) -> {equalityMatch,AV};
  624. v_filter({greaterOrEqual,AV}) -> {greaterOrEqual,AV};
  625. v_filter({lessOrEqual,AV}) -> {lessOrEqual,AV};
  626. v_filter({approxMatch,AV}) -> {approxMatch,AV};
  627. v_filter({present,A}) -> {present,A};
  628. v_filter({substrings,S}) when record(S,'SubstringFilter') -> {substrings,S};
  629. v_filter(_Filter) -> throw({error,concat(["unknown filter: ",_Filter])}).
  630. v_modifications(Mods) ->
  631. F = fun({_,Op,_}) ->
  632. case lists:member(Op,[add,delete,replace]) of
  633. true -> true;
  634. _ -> throw({error,{mod_operation,Op}})
  635. end
  636. end,
  637. lists:foreach(F, Mods).
  638. v_substr([{Key,Str}|T]) when list(Str),Key==initial;Key==any;Key==final ->
  639. [{Key,Str}|v_substr(T)];
  640. v_substr([H|_]) ->
  641. throw({error,{substring_arg,H}});
  642. v_substr([]) ->
  643. [].
  644. v_scope(baseObject) -> baseObject;
  645. v_scope(singleLevel) -> singleLevel;
  646. v_scope(wholeSubtree) -> wholeSubtree;
  647. v_scope(_Scope) -> throw({error,concat(["unknown scope: ",_Scope])}).
  648. v_bool(true) -> true;
  649. v_bool(false) -> false;
  650. v_bool(_Bool) -> throw({error,concat(["not Boolean: ",_Bool])}).
  651. v_timeout(I) when integer(I), I>=0 -> I;
  652. v_timeout(_I) -> throw({error,concat(["timeout not positive integer: ",_I])}).
  653. v_attributes(Attrs) ->
  654. F = fun(A) when list(A) -> A;
  655. (A) -> throw({error,concat(["attribute not String: ",A])})
  656. end,
  657. lists:map(F,Attrs).
  658. %%% --------------------------------------------------------------------
  659. %%% Log routines. Call a user provided log routine F.
  660. %%% --------------------------------------------------------------------
  661. log1(Data, Str, Args) -> log(Data, Str, Args, 1).
  662. log2(Data, Str, Args) -> log(Data, Str, Args, 2).
  663. log(Data, Str, Args, Level) when function(Data#eldap.log) ->
  664. catch (Data#eldap.log)(Level, Str, Args);
  665. log(_, _, _, _) ->
  666. ok.
  667. %%% --------------------------------------------------------------------
  668. %%% Misc. routines
  669. %%% --------------------------------------------------------------------
  670. send(To,Msg) -> To ! {self(),Msg}.
  671. recv(From) -> receive {From,Msg} -> Msg end.
  672. ldap_closed_p(Data, Emsg) when Data#eldap.use_tls == true ->
  673. %% Check if the SSL socket seems to be alive or not
  674. case catch ssl:sockname(Data#eldap.fd) of
  675. {error, _} ->
  676. ssl:close(Data#eldap.fd),
  677. {error, ldap_closed};
  678. {ok, _} ->
  679. {error, Emsg};
  680. _ ->
  681. %% sockname crashes if the socket pid is not alive
  682. {error, ldap_closed}
  683. end;
  684. ldap_closed_p(Data, Emsg) ->
  685. %% non-SSL socket
  686. case inet:port(Data#eldap.fd) of
  687. {error,_} -> {error, ldap_closed};
  688. _ -> {error,Emsg}
  689. end.
  690. bump_id(Data) -> Data#eldap.id + 1.
  691. %%% --------------------------------------------------------------------
  692. %%% parse_dn/1 - Implementation of RFC 2253:
  693. %%%
  694. %%% "UTF-8 String Representation of Distinguished Names"
  695. %%%
  696. %%% Test cases:
  697. %%%
  698. %%% The simplest case:
  699. %%%
  700. %%% 1> eldap:parse_dn("CN=Steve Kille,O=Isode Limited,C=GB").
  701. %%% {ok,[[{attribute_type_and_value,"CN","Steve Kille"}],
  702. %%% [{attribute_type_and_value,"O","Isode Limited"}],
  703. %%% [{attribute_type_and_value,"C","GB"}]]}
  704. %%%
  705. %%% The first RDN is multi-valued:
  706. %%%
  707. %%% 2> eldap:parse_dn("OU=Sales+CN=J. Smith,O=Widget Inc.,C=US").
  708. %%% {ok,[[{attribute_type_and_value,"OU","Sales"},
  709. %%% {attribute_type_and_value,"CN","J. Smith"}],
  710. %%% [{attribute_type_and_value,"O","Widget Inc."}],
  711. %%% [{attribute_type_and_value,"C","US"}]]}
  712. %%%
  713. %%% Quoting a comma:
  714. %%%
  715. %%% 3> eldap:parse_dn("CN=L. Eagle,O=Sue\\, Grabbit and Runn,C=GB").
  716. %%% {ok,[[{attribute_type_and_value,"CN","L. Eagle"}],
  717. %%% [{attribute_type_and_value,"O","Sue\\, Grabbit and Runn"}],
  718. %%% [{attribute_type_and_value,"C","GB"}]]}
  719. %%%
  720. %%% A value contains a carriage return:
  721. %%%
  722. %%% 4> eldap:parse_dn("CN=Before
  723. %%% 4> After,O=Test,C=GB").
  724. %%% {ok,[[{attribute_type_and_value,"CN","Before\nAfter"}],
  725. %%% [{attribute_type_and_value,"O","Test"}],
  726. %%% [{attribute_type_and_value,"C","GB"}]]}
  727. %%%
  728. %%% 5> eldap:parse_dn("CN=Before\\0DAfter,O=Test,C=GB").
  729. %%% {ok,[[{attribute_type_and_value,"CN","Before\\0DAfter"}],
  730. %%% [{attribute_type_and_value,"O","Test"}],
  731. %%% [{attribute_type_and_value,"C","GB"}]]}
  732. %%%
  733. %%% An RDN in OID form:
  734. %%%
  735. %%% 6> eldap:parse_dn("1.3.6.1.4.1.1466.0=#04024869,O=Test,C=GB").
  736. %%% {ok,[[{attribute_type_and_value,"1.3.6.1.4.1.1466.0","#04024869"}],
  737. %%% [{attribute_type_and_value,"O","Test"}],
  738. %%% [{attribute_type_and_value,"C","GB"}]]}
  739. %%%
  740. %%%
  741. %%% --------------------------------------------------------------------
  742. parse_dn("") -> % empty DN string
  743. {ok,[]};
  744. parse_dn([H|_] = Str) when H=/=$, -> % 1:st name-component !
  745. case catch parse_name(Str,[]) of
  746. {'EXIT',Reason} -> {parse_error,internal_error,Reason};
  747. Else -> Else
  748. end.
  749. parse_name("",Acc) ->
  750. {ok,lists:reverse(Acc)};
  751. parse_name([$,|T],Acc) -> % N:th name-component !
  752. parse_name(T,Acc);
  753. parse_name(Str,Acc) ->
  754. {Rest,NameComponent} = parse_name_component(Str),
  755. parse_name(Rest,[NameComponent|Acc]).
  756. parse_name_component(Str) ->
  757. parse_name_component(Str,[]).
  758. parse_name_component(Str,Acc) ->
  759. case parse_attribute_type_and_value(Str) of
  760. {[$+|Rest], ATV} ->
  761. parse_name_component(Rest,[ATV|Acc]);
  762. {Rest,ATV} ->
  763. {Rest,lists:reverse([ATV|Acc])}
  764. end.
  765. parse_attribute_type_and_value(Str) ->
  766. case parse_attribute_type(Str) of
  767. {Rest,[]} ->
  768. error(expecting_attribute_type,Str);
  769. {Rest,Type} ->
  770. Rest2 = parse_equal_sign(Rest),
  771. {Rest3,Value} = parse_attribute_value(Rest2),
  772. {Rest3,{attribute_type_and_value,Type,Value}}
  773. end.
  774. -define(IS_ALPHA(X) , X>=$a,X=<$z;X>=$A,X=<$Z ).
  775. -define(IS_DIGIT(X) , X>=$0,X=<$9 ).
  776. -define(IS_SPECIAL(X) , X==$,;X==$=;X==$+;X==$<;X==$>;X==$#;X==$; ).
  777. -define(IS_QUOTECHAR(X) , X=/=$\\,X=/=$" ).
  778. -define(IS_STRINGCHAR(X) ,
  779. X=/=$,,X=/=$=,X=/=$+,X=/=$<,X=/=$>,X=/=$#,X=/=$;,?IS_QUOTECHAR(X) ).
  780. -define(IS_HEXCHAR(X) , ?IS_DIGIT(X);X>=$a,X=<$f;X>=$A,X=<$F ).
  781. parse_attribute_type([H|T]) when ?IS_ALPHA(H) ->
  782. %% NB: It must be an error in the RFC in the definition
  783. %% of 'attributeType', should be: (ALPHA *keychar)
  784. {Rest,KeyChars} = parse_keychars(T),
  785. {Rest,[H|KeyChars]};
  786. parse_attribute_type([H|_] = Str) when ?IS_DIGIT(H) ->
  787. parse_oid(Str);
  788. parse_attribute_type(Str) ->
  789. error(invalid_attribute_type,Str).
  790. %%% Is a hexstring !
  791. parse_attribute_value([$#,X,Y|T]) when ?IS_HEXCHAR(X),?IS_HEXCHAR(Y) ->
  792. {Rest,HexString} = parse_hexstring(T),
  793. {Rest,[$#,X,Y|HexString]};
  794. %%% Is a "quotation-sequence" !
  795. parse_attribute_value([$"|T]) ->
  796. {Rest,Quotation} = parse_quotation(T),
  797. {Rest,[$"|Quotation]};
  798. %%% Is a stringchar , pair or Empty !
  799. parse_attribute_value(Str) ->
  800. parse_string(Str).
  801. parse_hexstring(Str) ->
  802. parse_hexstring(Str,[]).
  803. parse_hexstring([X,Y|T],Acc) when ?IS_HEXCHAR(X),?IS_HEXCHAR(Y) ->
  804. parse_hexstring(T,[Y,X|Acc]);
  805. parse_hexstring(T,Acc) ->
  806. {T,lists:reverse(Acc)}.
  807. parse_quotation([$"|T]) -> % an empty: "" is ok !
  808. {T,[$"]};
  809. parse_quotation(Str) ->
  810. parse_quotation(Str,[]).
  811. %%% Parse to end of quotation
  812. parse_quotation([$"|T],Acc) ->
  813. {T,lists:reverse([$"|Acc])};
  814. parse_quotation([X|T],Acc) when ?IS_QUOTECHAR(X) ->
  815. parse_quotation(T,[X|Acc]);
  816. parse_quotation([$\\,X|T],Acc) when ?IS_SPECIAL(X) ->
  817. parse_quotation(T,[X,$\\|Acc]);
  818. parse_quotation([$\\,$\\|T],Acc) ->
  819. parse_quotation(T,[$\\,$\\|Acc]);
  820. parse_quotation([$\\,$"|T],Acc) ->
  821. parse_quotation(T,[$",$\\|Acc]);
  822. parse_quotation([$\\,X,Y|T],Acc) when ?IS_HEXCHAR(X),?IS_HEXCHAR(Y) ->
  823. parse_quotation(T,[Y,X,$\\|Acc]);
  824. parse_quotation(T,_) ->
  825. error(expecting_double_quote_mark,T).
  826. parse_string(Str) ->
  827. parse_string(Str,[]).
  828. parse_string("",Acc) ->
  829. {"",lists:reverse(Acc)};
  830. parse_string([H|T],Acc) when ?IS_STRINGCHAR(H) ->
  831. parse_string(T,[H|Acc]);
  832. parse_string([$\\,X|T],Acc) when ?IS_SPECIAL(X) -> % is a pair !
  833. parse_string(T,[X,$\\|Acc]);
  834. parse_string([$\\,$\\|T],Acc) -> % is a pair !
  835. parse_string(T,[$\\,$\\|Acc]);
  836. parse_string([$\\,$" |T],Acc) -> % is a pair !
  837. parse_string(T,[$" ,$\\|Acc]);
  838. parse_string([$\\,X,Y|T],Acc) when ?IS_HEXCHAR(X),?IS_HEXCHAR(Y) -> % is a pair!
  839. parse_string(T,[Y,X,$\\|Acc]);
  840. parse_string(T,Acc) ->
  841. {T,lists:reverse(Acc)}.
  842. parse_equal_sign([$=|T]) -> T;
  843. parse_equal_sign(T) -> error(expecting_equal_sign,T).
  844. parse_keychars(Str) -> parse_keychars(Str,[]).
  845. parse_keychars([H|T],Acc) when ?IS_ALPHA(H) -> parse_keychars(T,[H|Acc]);
  846. parse_keychars([H|T],Acc) when ?IS_DIGIT(H) -> parse_keychars(T,[H|Acc]);
  847. parse_keychars([$-|T],Acc) -> parse_keychars(T,[$-|Acc]);
  848. parse_keychars(T,Acc) -> {T,lists:reverse(Acc)}.
  849. parse_oid(Str) -> parse_oid(Str,[]).
  850. parse_oid([H,$.|T], Acc) when ?IS_DIGIT(H) ->
  851. parse_oid(T,[$.,H|Acc]);
  852. parse_oid([H|T], Acc) when ?IS_DIGIT(H) ->
  853. parse_oid(T,[H|Acc]);
  854. parse_oid(T, Acc) ->
  855. {T,lists:reverse(Acc)}.
  856. error(Emsg,Rest) ->
  857. throw({parse_error,Emsg,Rest}).
  858. %%% --------------------------------------------------------------------
  859. %%% Parse LDAP url according to RFC 2255
  860. %%%
  861. %%% Test case:
  862. %%%
  863. %%% 2> eldap:parse_ldap_url("ldap://10.42.126.33:389/cn=Administrative%20CA,o=Post%20Danmark,c=DK?certificateRevokationList;binary").
  864. %%% {ok,{{10,42,126,33},389},
  865. %%% [[{attribute_type_and_value,"cn","Administrative%20CA"}],
  866. %%% [{attribute_type_and_value,"o","Post%20Danmark"}],
  867. %%% [{attribute_type_and_value,"c","DK"}]],
  868. %%% {attributes,["certificateRevokationList;binary"]}}
  869. %%%
  870. %%% --------------------------------------------------------------------
  871. parse_ldap_url("ldap://" ++ Rest1 = Str) ->
  872. {Rest2,HostPort} = parse_hostport(Rest1),
  873. %% Split the string into DN and Attributes+etc
  874. {Sdn,Rest3} = split_string(rm_leading_slash(Rest2),$?),
  875. case parse_dn(Sdn) of
  876. {parse_error,internal_error,_Reason} ->
  877. {parse_error,internal_error,{Str,[]}};
  878. {parse_error,Emsg,Tail} ->
  879. Head = get_head(Str,Tail),
  880. {parse_error,Emsg,{Head,Tail}};
  881. {ok,DN} ->
  882. %% We stop parsing here for now and leave
  883. %% 'scope', 'filter' and 'extensions' to
  884. %% be implemented later if needed.
  885. {_Rest4,Attributes} = parse_attributes(Rest3),
  886. {ok,HostPort,DN,Attributes}
  887. end.
  888. rm_leading_slash([$/|Tail]) -> Tail;
  889. rm_leading_slash(Tail) -> Tail.
  890. parse_attributes([$?|Tail]) ->
  891. case split_string(Tail,$?) of
  892. {[],Attributes} ->
  893. {[],{attributes,string:tokens(Attributes,",")}};
  894. {Attributes,Rest} ->
  895. {Rest,{attributes,string:tokens(Attributes,",")}}
  896. end.
  897. parse_hostport(Str) ->
  898. {HostPort,Rest} = split_string(Str,$/),
  899. case split_string(HostPort,$:) of
  900. {Shost,[]} ->
  901. {Rest,{parse_host(Rest,Shost),?LDAP_PORT}};
  902. {Shost,[$:|Sport]} ->
  903. {Rest,{parse_host(Rest,Shost),
  904. parse_port(Rest,Sport)}}
  905. end.
  906. parse_port(Rest,Sport) ->
  907. case list_to_integer(Sport) of
  908. Port when integer(Port) -> Port;
  909. _ -> error(parsing_port,Rest)
  910. end.
  911. parse_host(Rest,Shost) ->
  912. case catch validate_host(Shost) of
  913. {parse_error,Emsg,_} -> error(Emsg,Rest);
  914. Host -> Host
  915. end.
  916. validate_host(Shost) ->
  917. case inet_parse:address(Shost) of
  918. {ok,Host} -> Host;
  919. _ ->
  920. case inet_parse:domain(Shost) of
  921. true -> Shost;
  922. _ -> error(parsing_host,Shost)
  923. end
  924. end.
  925. split_string(Str,Key) ->
  926. Pred = fun(X) when X==Key -> false; (_) -> true end,
  927. lists:splitwith(Pred, Str).
  928. get_head(Str,Tail) ->
  929. get_head(Str,Tail,[]).
  930. %%% Should always succeed !
  931. get_head([H|Tail],Tail,Rhead) -> lists:reverse([H|Rhead]);
  932. get_head([H|Rest],Tail,Rhead) -> get_head(Rest,Tail,[H|Rhead]).
  933. b2l(B) when binary(B) -> B;
  934. b2l(L) when list(L) -> list_to_binary(L).