34
34
%%% Modified by Alexey Shchepin <alexey@sevcom.net>
36
%%% Modified by Evgeniy Khramtsov <xram@jabber.ru>
36
%%% Modified by Evgeniy Khramtsov <ekhramtsov@process-one.net>
37
37
%%% Implemented queue for bind() requests to prevent pending binds.
38
%%% Implemented extensibleMatch/2 function.
39
%%% Implemented LDAP Extended Operations (currently only Password Modify
40
%%% is supported - RFC 3062).
39
42
%%% Modified by Christophe Romain <christophe.romain@process-one.net>
40
43
%%% Improve error case handling
72
75
-export([baseObject/0,singleLevel/0,wholeSubtree/0,close/1,
73
76
equalityMatch/2,greaterOrEqual/2,lessOrEqual/2,
74
approxMatch/2,search/2,substrings/2,present/1,
77
approxMatch/2,search/2,substrings/2,present/1,extensibleMatch/2,
75
78
'and'/1,'or'/1,'not'/1,modify/3, mod_add/2, mod_delete/2,
76
mod_replace/2, add/3, delete/2, modify_dn/5, bind/3]).
79
mod_replace/2, add/3, delete/2, modify_dn/5, modify_passwd/3, bind/3]).
77
80
-export([get_status/1]).
79
82
%% gen_fsm callbacks
127
130
Reg_name = list_to_atom("eldap_" ++ Name),
128
131
gen_fsm:start_link({local, Reg_name}, ?MODULE, [], []).
130
start_link(Name, Hosts, Port, Rootdn, Passwd, Encrypt) ->
133
start_link(Name, Hosts, Port, Rootdn, Passwd, Opts) ->
131
134
Reg_name = list_to_atom("eldap_" ++ Name),
132
gen_fsm:start_link({local, Reg_name}, ?MODULE, {Hosts, Port, Rootdn, Passwd, Encrypt}, []).
135
gen_fsm:start_link({local, Reg_name}, ?MODULE,
136
{Hosts, Port, Rootdn, Passwd, Opts}, []).
134
138
%%% --------------------------------------------------------------------
135
139
%%% Get status of connection.
239
243
{modify_dn, Entry, NewRDN, bool_p(DelOldRDN), optional(NewSup)},
246
modify_passwd(Handle, DN, Passwd) when is_list(DN), is_list(Passwd) ->
247
Handle1 = get_handle(Handle),
248
gen_fsm:sync_send_event(
249
Handle1, {modify_passwd, DN, Passwd}, ?CALL_TIMEOUT).
243
251
%%% --------------------------------------------------------------------
374
382
{substrings,#'SubstringFilter'{type = Type,
375
383
substrings = Ss}}.
386
%%% extensibleMatch filter.
387
%%% FIXME: Describe the purpose of this filter.
389
%%% Value ::= string( <attribute> )
390
%%% Opts ::= listof( {matchingRule, Str} | {type, Str} | {dnAttributes, true} )
392
%%% Example: extensibleMatch("Fred", [{matchingRule, "1.2.3.4.5"}, {type, "cn"}]).
394
extensibleMatch(Value, Opts) when is_list(Value), is_list(Opts) ->
395
MRA = #'MatchingRuleAssertion'{matchValue=Value},
396
{extensibleMatch, extensibleMatch_opts(Opts, MRA)}.
398
extensibleMatch_opts([{matchingRule, Rule} | Opts], MRA) when is_list(Rule) ->
399
extensibleMatch_opts(Opts, MRA#'MatchingRuleAssertion'{matchingRule=Rule});
400
extensibleMatch_opts([{type, Desc} | Opts], MRA) when is_list(Desc) ->
401
extensibleMatch_opts(Opts, MRA#'MatchingRuleAssertion'{type=Desc});
402
extensibleMatch_opts([{dnAttributes, true} | Opts], MRA) ->
403
extensibleMatch_opts(Opts, MRA#'MatchingRuleAssertion'{dnAttributes=true});
404
extensibleMatch_opts([_ | Opts], MRA) ->
405
extensibleMatch_opts(Opts, MRA);
406
extensibleMatch_opts([], MRA) ->
378
409
get_handle(Pid) when is_pid(Pid) -> Pid;
379
410
get_handle(Atom) when is_atom(Atom) -> Atom;
393
424
%%----------------------------------------------------------------------
395
426
case get_config() of
396
{ok, Hosts, Rootdn, Passwd, Encrypt} ->
397
init({Hosts, Rootdn, Passwd, Encrypt});
427
{ok, Hosts, Rootdn, Passwd, Opts} ->
428
init({Hosts, Rootdn, Passwd, Opts});
398
429
{error, Reason} ->
401
init({Hosts, Port, Rootdn, Passwd, Encrypt}) ->
432
init({Hosts, Port, Rootdn, Passwd, Opts}) ->
402
433
catch ssl:start(),
403
{X1,X2,X3} = erlang:now(),
404
ssl:seed(integer_to_list(X1) ++ integer_to_list(X2) ++ integer_to_list(X3)),
434
ssl:seed(randoms:get_string()),
435
Encrypt = case proplists:get_value(encrypt, Opts) of
405
439
PortTemp = case Port of
671
712
deleteoldrdn = DelOldRDN,
672
713
newSuperior = NewSup}};
715
gen_req({modify_passwd, DN, Passwd}) ->
716
{ok, ReqVal} = asn1rt:encode(
717
'ELDAPv3', 'PasswdModifyRequestValue',
718
#'PasswdModifyRequestValue'{
720
newPasswd = Passwd}),
722
#'ExtendedRequest'{requestName = ?passwdModifyOID,
723
requestValue = list_to_binary(ReqVal)}};
674
725
gen_req({bind, RootDN, Passwd}) ->
676
727
#'BindRequest'{version = ?LDAP_VERSION,
745
796
cancel_timer(Timer),
746
797
Reply = check_bind_reply(Result, From),
747
798
{reply, Reply, From, S#eldap{dict = New_dict}};
799
{extendedReq, {extendedResp, Result}} ->
800
New_dict = dict:erase(Id, Dict),
802
Reply = check_extended_reply(Result, From),
803
{reply, Reply, From, S#eldap{dict = New_dict}};
748
804
{OtherName, OtherResult} ->
749
805
New_dict = dict:erase(Id, Dict),
750
806
cancel_timer(Timer),
769
825
check_bind_reply(Other, _From) ->
828
%% TODO: process reply depending on requestName:
829
%% this requires BER-decoding of #'ExtendedResponse'.response
830
check_extended_reply(#'ExtendedResponse'{resultCode = success}, _From) ->
832
check_extended_reply(#'ExtendedResponse'{resultCode = Reason}, _From) ->
834
check_extended_reply(Other, _From) ->
772
837
get_op_rec(Id, Dict) ->
773
838
case dict:find(Id, Dict) of
774
839
{ok, [{Timer, _Command, From, Name}|Res]} ->
906
971
SslOpts = [{packet, asn1}, {active, true}, {keepalive, true},
972
binary | S#eldap.tls_options],
908
973
ssl:connect(Host, S#eldap.port, SslOpts);
909
974
%% starttls -> %% TODO: Implement STARTTLS;
973
1038
v_filter({approxMatch,AV}) -> {approxMatch,AV};
974
1039
v_filter({present,A}) -> {present,A};
975
1040
v_filter({substrings,S}) when is_record(S,'SubstringFilter') -> {substrings,S};
1041
v_filter({extensibleMatch, S}) when is_record(S, 'MatchingRuleAssertion') ->
1042
{extensibleMatch, S};
976
1043
v_filter(_Filter) -> throw({error,concat(["unknown filter: ",_Filter])}).
978
1045
v_modifications(Mods) ->
1018
1085
case file:consult(File) of
1019
1086
{ok, Entries} ->
1020
1087
case catch parse(Entries) of
1021
{ok, Hosts, Port, Rootdn, Passwd, Encrypt} ->
1022
{ok, Hosts, Port, Rootdn, Passwd, Encrypt};
1088
{ok, Hosts, Port, Rootdn, Passwd, Opts} ->
1089
{ok, Hosts, Port, Rootdn, Passwd, Opts};
1023
1090
{error, Reason} ->
1024
1091
{error, Reason};
1025
1092
{'EXIT', Reason} ->
1035
1102
get_integer(port, Entries),
1036
1103
get_list(rootdn, Entries),
1037
1104
get_list(passwd, Entries),
1038
get_atom(encrypt, Entries)}.
1105
get_list(options, Entries)}.
1040
1107
get_integer(Key, List) ->
1041
1108
case lists:keysearch(Key, 1, List) of
1057
1124
throw({error, "No Entry in Config for " ++ atom_to_list(Key)})
1060
get_atom(Key, List) ->
1061
case lists:keysearch(Key, 1, List) of
1062
{value, {Key, Value}} when is_atom(Value) ->
1064
{value, {Key, _Value}} ->
1065
throw({error, "Bad Value in Config for " ++ atom_to_list(Key)});
1067
throw({error, "No Entry in Config for " ++ atom_to_list(Key)})
1127
%% get_atom(Key, List) ->
1128
%% case lists:keysearch(Key, 1, List) of
1129
%% {value, {Key, Value}} when is_atom(Value) ->
1131
%% {value, {Key, _Value}} ->
1132
%% throw({error, "Bad Value in Config for " ++ atom_to_list(Key)});
1134
%% throw({error, "No Entry in Config for " ++ atom_to_list(Key)})
1070
1137
get_hosts(Key, List) ->
1071
1138
lists:map(fun({Key1, {A,B,C,D}}) when is_integer(A),