~ubuntu-branches/ubuntu/raring/tsung/raring-proposed

« back to all changes in this revision

Viewing changes to src/lib/eldap.erl

  • Committer: Package Import Robot
  • Author(s): Ignace Mouzannar
  • Date: 2011-09-20 05:21:15 UTC
  • Revision ID: package-import@ubuntu.com-20110920052115-nqhu0na28zgm78ei
Tags: upstream-1.4.1
ImportĀ upstreamĀ versionĀ 1.4.1

Show diffs side-by-side

added added

removed removed

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