PageRenderTime 88ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/eldap/src/eldap.erl

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