diff options
Diffstat (limited to 'src/eldap.erl')
-rw-r--r-- | src/eldap.erl | 257 |
1 files changed, 154 insertions, 103 deletions
diff --git a/src/eldap.erl b/src/eldap.erl index f48b2d840..34c20228f 100644 --- a/src/eldap.erl +++ b/src/eldap.erl @@ -6,7 +6,7 @@ %%% draft-ietf-asid-ldap-c-api-00.txt %%% %%% Copyright (C) 2000 Torbjorn Tornkvist, tnt@home.se -%%% +%%% %%% %%% This program is free software; you can redistribute it and/or modify %%% it under the terms of the GNU General Public License as published by @@ -63,9 +63,8 @@ %%% active_bind - sent bind() request and waiting for response %%%---------------------------------------------------------------------- --behaviour(gen_fsm). +-behaviour(p1_fsm). --include("ejabberd.hrl"). -include("logger.hrl"). %% External exports @@ -88,7 +87,7 @@ -export_type([filter/0]). -include("ELDAPv3.hrl"). - +-include_lib("kernel/include/inet.hrl"). -include("eldap.hrl"). -define(LDAP_VERSION, 3). @@ -126,36 +125,37 @@ -record(eldap, {version = ?LDAP_VERSION :: non_neg_integer(), hosts = [] :: [binary()], - host :: binary(), + host = undefined :: binary() | undefined, port = 389 :: inet:port_number(), sockmod = gen_tcp :: ssl | gen_tcp, tls = none :: none | tls, - tls_options = [] :: [{cacertfile, string()} | + tls_options = [] :: [{certfile, string()} | + {cacertfile, string()} | {depth, non_neg_integer()} | {verify, non_neg_integer()}], - fd, + fd :: gen_tcp:socket() | undefined, rootdn = <<"">> :: binary(), passwd = <<"">> :: binary(), id = 0 :: non_neg_integer(), bind_timer = make_ref() :: reference(), - dict = dict:new() :: ?TDICT, - req_q = queue:new() :: ?TQUEUE}). + dict = dict:new() :: dict:dict(), + req_q = queue:new() :: queue:queue()}). %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- start_link(Name) -> - Reg_name = jlib:binary_to_atom(<<"eldap_", + Reg_name = misc:binary_to_atom(<<"eldap_", Name/binary>>), - gen_fsm:start_link({local, Reg_name}, ?MODULE, [], []). + p1_fsm:start_link({local, Reg_name}, ?MODULE, [], []). -spec start_link(binary(), [binary()], inet:port_number(), binary(), binary(), tlsopts()) -> any(). start_link(Name, Hosts, Port, Rootdn, Passwd, Opts) -> - Reg_name = jlib:binary_to_atom(<<"eldap_", + Reg_name = misc:binary_to_atom(<<"eldap_", Name/binary>>), - gen_fsm:start_link({local, Reg_name}, ?MODULE, + p1_fsm:start_link({local, Reg_name}, ?MODULE, [Hosts, Port, Rootdn, Passwd, Opts], []). -spec get_status(handle()) -> any(). @@ -165,7 +165,7 @@ start_link(Name, Hosts, Port, Rootdn, Passwd, Opts) -> %%% -------------------------------------------------------------------- get_status(Handle) -> Handle1 = get_handle(Handle), - gen_fsm:sync_send_all_state_event(Handle1, get_status). + p1_fsm:sync_send_all_state_event(Handle1, get_status). %%% -------------------------------------------------------------------- %%% Shutdown connection (and process) asynchronous. @@ -174,14 +174,14 @@ get_status(Handle) -> close(Handle) -> Handle1 = get_handle(Handle), - gen_fsm:send_all_state_event(Handle1, close). + p1_fsm:send_all_state_event(Handle1, close). %%% -------------------------------------------------------------------- %%% Add an entry. The entry field MUST NOT exist for the AddRequest %%% to succeed. The parent of the entry MUST exist. %%% Example: %%% -%%% add(Handle, +%%% add(Handle, %%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com", %%% [{"objectclass", ["person"]}, %%% {"cn", ["Bill Valentine"]}, @@ -191,7 +191,7 @@ close(Handle) -> %%% -------------------------------------------------------------------- add(Handle, Entry, Attributes) -> Handle1 = get_handle(Handle), - gen_fsm:sync_send_event(Handle1, + p1_fsm:sync_send_event(Handle1, {add, Entry, add_attrs(Attributes)}, ?CALL_TIMEOUT). %%% Do sanity check ! @@ -205,17 +205,17 @@ add_attrs(Attrs) -> end. %%% -------------------------------------------------------------------- -%%% Delete an entry. The entry consists of the DN of +%%% Delete an entry. The entry consists of the DN of %%% the entry to be deleted. %%% Example: %%% -%%% delete(Handle, +%%% delete(Handle, %%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com" %%% ) %%% -------------------------------------------------------------------- delete(Handle, Entry) -> Handle1 = get_handle(Handle), - gen_fsm:sync_send_event(Handle1, {delete, Entry}, + p1_fsm:sync_send_event(Handle1, {delete, Entry}, ?CALL_TIMEOUT). %%% -------------------------------------------------------------------- @@ -223,21 +223,21 @@ delete(Handle, Entry) -> %%% operations can be performed as one atomic operation. %%% Example: %%% -%%% modify(Handle, +%%% modify(Handle, %%% "cn=Torbjorn Tornkvist, ou=people, o=Bluetail AB, dc=bluetail, dc=com", %%% [replace("telephoneNumber", ["555 555 00"]), -%%% add("description", ["LDAP hacker"])] +%%% add("description", ["LDAP hacker"])] %%% ) %%% -------------------------------------------------------------------- -spec modify(handle(), any(), [add | delete | replace]) -> any(). modify(Handle, Object, Mods) -> Handle1 = get_handle(Handle), - gen_fsm:sync_send_event(Handle1, {modify, Object, Mods}, + p1_fsm:sync_send_event(Handle1, {modify, Object, Mods}, ?CALL_TIMEOUT). %%% -%%% Modification operations. +%%% Modification operations. %%% Example: %%% replace("telephoneNumber", ["555 555 00"]) %%% @@ -252,7 +252,7 @@ mod_delete(Type, Values) -> %%% operations can be performed as one atomic operation. %%% Example: %%% -%%% modify_dn(Handle, +%%% modify_dn(Handle, %%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com", %%% "cn=Ben Emerson", %%% true, @@ -273,7 +273,7 @@ m(Operation, Type, Values) -> modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) -> Handle1 = get_handle(Handle), - gen_fsm:sync_send_event(Handle1, + p1_fsm:sync_send_event(Handle1, {modify_dn, Entry, NewRDN, bool_p(DelOldRDN), optional(NewSup)}, ?CALL_TIMEOUT). @@ -282,22 +282,22 @@ modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) -> modify_passwd(Handle, DN, Passwd) -> Handle1 = get_handle(Handle), - gen_fsm:sync_send_event(Handle1, + p1_fsm:sync_send_event(Handle1, {modify_passwd, DN, Passwd}, ?CALL_TIMEOUT). %%% -------------------------------------------------------------------- %%% Bind. %%% Example: %%% -%%% bind(Handle, +%%% bind(Handle, %%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com", %%% "secret") %%% -------------------------------------------------------------------- -spec bind(handle(), binary(), binary()) -> any(). - + bind(Handle, RootDN, Passwd) -> Handle1 = get_handle(Handle), - gen_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd}, + p1_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd}, ?CALL_TIMEOUT). %%% Sanity checks ! @@ -308,7 +308,7 @@ optional([]) -> asn1_NOVALUE; optional(Value) -> Value. %%% -------------------------------------------------------------------- -%%% Synchronous search of the Directory returning a +%%% Synchronous search of the Directory returning a %%% requested set of attributes. %%% %%% Example: @@ -355,7 +355,7 @@ search(Handle, L) when is_list(L) -> call_search(Handle, A) -> Handle1 = get_handle(Handle), - gen_fsm:sync_send_event(Handle1, {search, A}, + p1_fsm:sync_send_event(Handle1, {search, A}, ?CALL_TIMEOUT). -spec parse_search_args(search_args()) -> eldap_search(). @@ -548,7 +548,7 @@ extensibleMatch_opts([], MRA) -> MRA. get_handle(Pid) when is_pid(Pid) -> Pid; get_handle(Atom) when is_atom(Atom) -> Atom; get_handle(Name) when is_binary(Name) -> - jlib:binary_to_atom(<<"eldap_", + misc:binary_to_atom(<<"eldap_", Name/binary>>). %%%---------------------------------------------------------------------- @@ -560,16 +560,12 @@ get_handle(Name) when is_binary(Name) -> %% Returns: {ok, StateName, StateData} | %% {ok, StateName, StateData, Timeout} | %% ignore | -%% {stop, StopReason} +%% {stop, StopReason} %% I use the trick of setting a timeout of 0 to pass control into the -%% process. +%% process. %%---------------------------------------------------------------------- init([Hosts, Port, Rootdn, Passwd, Opts]) -> - Encrypt = case gen_mod:get_opt(encrypt, Opts, - fun(tls) -> tls; - (starttls) -> starttls; - (none) -> none - end) of + Encrypt = case proplists:get_value(encrypt, Opts) of tls -> tls; _ -> none end, @@ -581,46 +577,36 @@ init([Hosts, Port, Rootdn, Passwd, Opts]) -> end; PT -> PT end, - CacertOpts = case gen_mod:get_opt( - tls_cacertfile, Opts, - fun(S) when is_binary(S) -> - binary_to_list(S); - (undefined) -> - undefined - end) of + CertOpts = case proplists:get_value(tls_certfile, Opts) of + undefined -> + []; + Path1 -> + [{certfile, Path1}] + end, + CacertOpts = case proplists:get_value(tls_cacertfile, Opts) of undefined -> []; - Path -> - [{cacertfile, Path}] + Path2 -> + [{cacertfile, Path2}] end, - DepthOpts = case gen_mod:get_opt( - tls_depth, Opts, - fun(I) when is_integer(I), I>=0 -> - I; - (undefined) -> - undefined - end) of + DepthOpts = case proplists:get_value(tls_depth, Opts) of undefined -> []; Depth -> [{depth, Depth}] end, - Verify = gen_mod:get_opt(tls_verify, Opts, - fun(hard) -> hard; - (soft) -> soft; - (false) -> false - end, false), + Verify = proplists:get_value(tls_verify, Opts, false), TLSOpts = if (Verify == hard orelse Verify == soft) andalso CacertOpts == [] -> ?WARNING_MSG("TLS verification is enabled but no CA " "certfiles configured, so verification " "is disabled.", []), - []; + CertOpts; Verify == soft -> - [{verify, 1}] ++ CacertOpts ++ DepthOpts; + [{verify, 1}] ++ CertOpts ++ CacertOpts ++ DepthOpts; Verify == hard -> - [{verify, 2}] ++ CacertOpts ++ DepthOpts; + [{verify, 2}] ++ CertOpts ++ CacertOpts ++ DepthOpts; true -> [] end, {ok, connecting, @@ -650,10 +636,10 @@ active(Event, From, S) -> %%---------------------------------------------------------------------- %% Func: handle_event/3 -%% Called when gen_fsm:send_all_state_event/2 is invoked. +%% Called when p1_fsm:send_all_state_event/2 is invoked. %% Returns: {next_state, NextStateName, NextStateData} | %% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} +%% {stop, Reason, NewStateData} %%---------------------------------------------------------------------- handle_event(close, _StateName, S) -> catch (S#eldap.sockmod):close(S#eldap.fd), @@ -669,11 +655,11 @@ handle_sync_event(_Event, _From, StateName, S) -> %% handle_info({Tag, _Socket, Data}, connecting, S) when Tag == tcp; Tag == ssl -> - ?DEBUG("tcp packet received when disconnected!~n~p", [Data]), + ?DEBUG("TCP packet received when disconnected!~n~p", [Data]), {next_state, connecting, S}; handle_info({Tag, _Socket, Data}, wait_bind_response, S) when Tag == tcp; Tag == ssl -> - cancel_timer(S#eldap.bind_timer), + misc:cancel_timer(S#eldap.bind_timer), case catch recvd_wait_bind_response(Data, S) of bound -> dequeue_commands(S); {fail_bind, Reason} -> @@ -693,7 +679,7 @@ handle_info({Tag, _Socket, Data}, StateName, S) case catch recvd_packet(Data, S) of {response, Response, RequestType} -> NewS = case Response of - {reply, Reply, To, S1} -> gen_fsm:reply(To, Reply), S1; + {reply, Reply, To, S1} -> p1_fsm:reply(To, Reply), S1; {ok, S1} -> S1 end, if StateName == active_bind andalso @@ -706,7 +692,7 @@ handle_info({Tag, _Socket, Data}, StateName, S) end; handle_info({Tag, _Socket}, Fsm_state, S) when Tag == tcp_closed; Tag == ssl_closed -> - ?WARNING_MSG("LDAP server closed the connection: ~s:~p~nIn " + ?WARNING_MSG("LDAP server closed the connection: ~ts:~p~nIn " "State: ~p", [S#eldap.host, S#eldap.port, Fsm_state]), {next_state, connecting, close_and_retry(S)}; @@ -722,7 +708,7 @@ handle_info({timeout, Timer, {cmd_timeout, Id}}, StateName, S) -> case cmd_timeout(Timer, Id, S) of {reply, To, Reason, NewS} -> - gen_fsm:reply(To, Reason), + p1_fsm:reply(To, Reason), {next_state, StateName, NewS}; {error, _Reason} -> {next_state, StateName, S} end; @@ -735,7 +721,7 @@ handle_info({timeout, _Timer, bind_timeout}, wait_bind_response, S) -> %% Make sure we don't fill the message queue with rubbish %% handle_info(Info, StateName, S) -> - ?DEBUG("eldap. Unexpected Info: ~p~nIn state: " + ?DEBUG("Unexpected Info: ~p~nIn state: " "~p~n when StateData is: ~p", [Info, StateName, S]), {next_state, StateName, S}. @@ -836,7 +822,7 @@ gen_req({bind, RootDN, Passwd}) -> %% recvd_packet %% Deals with incoming packets in the active state %% Will return one of: -%% {ok, NewS} - Don't reply to client yet as this is part of a search +%% {ok, NewS} - Don't reply to client yet as this is part of a search %% result and we haven't got all the answers yet. %% {reply, Result, From, NewS} - Reply with result to client From %% {error, Reason} @@ -861,14 +847,14 @@ recvd_packet(Pkt, S) -> if Reason == success; Reason == sizeLimitExceeded -> {Res, Ref} = polish(Result_so_far), New_dict = dict:erase(Id, Dict), - cancel_timer(Timer), + misc:cancel_timer(Timer), {reply, #eldap_search_result{entries = Res, referrals = Ref}, From, S#eldap{dict = New_dict}}; true -> New_dict = dict:erase(Id, Dict), - cancel_timer(Timer), + misc:cancel_timer(Timer), {reply, {error, Reason}, From, S#eldap{dict = New_dict}} end; @@ -877,37 +863,37 @@ recvd_packet(Pkt, S) -> {ok, S#eldap{dict = New_dict}}; {addRequest, {addResponse, Result}} -> New_dict = dict:erase(Id, Dict), - cancel_timer(Timer), + misc:cancel_timer(Timer), Reply = check_reply(Result, From), {reply, Reply, From, S#eldap{dict = New_dict}}; {delRequest, {delResponse, Result}} -> New_dict = dict:erase(Id, Dict), - cancel_timer(Timer), + misc:cancel_timer(Timer), Reply = check_reply(Result, From), {reply, Reply, From, S#eldap{dict = New_dict}}; {modifyRequest, {modifyResponse, Result}} -> New_dict = dict:erase(Id, Dict), - cancel_timer(Timer), + misc:cancel_timer(Timer), Reply = check_reply(Result, From), {reply, Reply, From, S#eldap{dict = New_dict}}; {modDNRequest, {modDNResponse, Result}} -> New_dict = dict:erase(Id, Dict), - cancel_timer(Timer), + misc:cancel_timer(Timer), Reply = check_reply(Result, From), {reply, Reply, From, S#eldap{dict = New_dict}}; {bindRequest, {bindResponse, Result}} -> New_dict = dict:erase(Id, Dict), - cancel_timer(Timer), + misc:cancel_timer(Timer), Reply = check_bind_reply(Result, From), {reply, Reply, From, S#eldap{dict = New_dict}}; {extendedReq, {extendedResp, Result}} -> New_dict = dict:erase(Id, Dict), - cancel_timer(Timer), + misc:cancel_timer(Timer), Reply = check_extended_reply(Result, From), {reply, Reply, From, S#eldap{dict = New_dict}}; {OtherName, OtherResult} -> New_dict = dict:erase(Id, Dict), - cancel_timer(Timer), + misc:cancel_timer(Timer), {reply, {error, {invalid_result, OtherName, OtherResult}}, From, S#eldap{dict = New_dict}} @@ -982,16 +968,11 @@ check_id(_, _) -> throw({error, wrong_bind_id}). %% General Helpers %%----------------------------------------------------------------------- -cancel_timer(Timer) -> - erlang:cancel_timer(Timer), - receive {timeout, Timer, _} -> ok after 0 -> ok end. - - close_and_retry(S, Timeout) -> catch (S#eldap.sockmod):close(S#eldap.fd), Queue = dict:fold(fun (_Id, [{Timer, Command, From, _Name} | _], Q) -> - cancel_timer(Timer), + misc:cancel_timer(Timer), queue:in_r({Command, From}, Q); (_, _, Q) -> Q end, @@ -1004,7 +985,7 @@ close_and_retry(S) -> close_and_retry(S, ?RETRY_TIMEOUT). report_bind_failure(Host, Port, Reason) -> - ?WARNING_MSG("LDAP bind failed on ~s:~p~nReason: ~p", + ?WARNING_MSG("LDAP bind failed on ~ts:~p~nReason: ~p", [Host, Port, Reason]). %%----------------------------------------------------------------------- @@ -1059,8 +1040,6 @@ polish([], Res, Ref) -> {Res, Ref}. %%----------------------------------------------------------------------- connect_bind(S) -> Host = next_host(S#eldap.host, S#eldap.hosts), - ?INFO_MSG("LDAP connection on ~s:~p", - [Host, S#eldap.port]), Opts = if S#eldap.tls == tls -> [{packet, asn1}, {active, true}, {keepalive, true}, binary @@ -1069,16 +1048,14 @@ connect_bind(S) -> [{packet, asn1}, {active, true}, {keepalive, true}, {send_timeout, ?SEND_TIMEOUT}, binary] end, + ?DEBUG("Connecting to LDAP server at ~ts:~p with options ~p", + [Host, S#eldap.port, Opts]), HostS = binary_to_list(Host), - SocketData = case S#eldap.tls of - tls -> - SockMod = ssl, ssl:connect(HostS, S#eldap.port, Opts); - %% starttls -> %% TODO: Implement STARTTLS; - _ -> - SockMod = gen_tcp, - gen_tcp:connect(HostS, S#eldap.port, Opts) - end, - case SocketData of + SockMod = case S#eldap.tls of + tls -> ssl; + _ -> gen_tcp + end, + case connect(HostS, S#eldap.port, SockMod, Opts) of {ok, Socket} -> case bind_request(Socket, S#eldap{sockmod = SockMod}) of {ok, NewS} -> @@ -1093,9 +1070,8 @@ connect_bind(S) -> {ok, connecting, NewS#eldap{host = Host}} end; {error, Reason} -> - ?ERROR_MSG("LDAP connection failed:~n** Server: " - "~s:~p~n** Reason: ~p~n** Socket options: ~p", - [Host, S#eldap.port, Reason, Opts]), + ?ERROR_MSG("LDAP connection to ~ts:~b failed: ~ts", + [Host, S#eldap.port, format_error(SockMod, Reason)]), NewS = close_and_retry(S), {ok, connecting, NewS#eldap{host = Host}} end. @@ -1134,3 +1110,78 @@ bump_id(#eldap{id = Id}) when Id > (?MAX_TRANSACTION_ID) -> ?MIN_TRANSACTION_ID; bump_id(#eldap{id = Id}) -> Id + 1. + +format_error(SockMod, Reason) -> + Txt = case SockMod of + ssl -> ssl:format_error(Reason); + gen_tcp -> inet:format_error(Reason) + end, + case Txt of + "unknown POSIX error" -> + lists:flatten(io_lib:format("~p", [Reason])); + _ -> + Txt + end. + +%%-------------------------------------------------------------------- +%% Connecting stuff +%%-------------------------------------------------------------------- +-define(CONNECT_TIMEOUT, timer:seconds(15)). +-define(DNS_TIMEOUT, timer:seconds(5)). + +connect(Host, Port, Mod, Opts) -> + case lookup(Host) of + {ok, AddrsFamilies} -> + do_connect(AddrsFamilies, Port, Mod, Opts, {error, nxdomain}); + {error, _} = Err -> + Err + end. + +do_connect([{IP, Family}|AddrsFamilies], Port, Mod, Opts, _Err) -> + case Mod:connect(IP, Port, [Family|Opts], ?CONNECT_TIMEOUT) of + {ok, Sock} -> + {ok, Sock}; + {error, _} = Err -> + do_connect(AddrsFamilies, Port, Mod, Opts, Err) + end; +do_connect([], _Port, _Mod, _Opts, Err) -> + Err. + +lookup(Host) -> + case inet:parse_address(Host) of + {ok, IP} -> + {ok, [{IP, get_addr_type(IP)}]}; + {error, _} -> + do_lookup([{Host, Family} || Family <- [inet6, inet]], + [], {error, nxdomain}) + end. + +do_lookup([{Host, Family}|HostFamilies], AddrFamilies, Err) -> + case inet:gethostbyname(Host, Family, ?DNS_TIMEOUT) of + {ok, HostEntry} -> + Addrs = host_entry_to_addrs(HostEntry), + AddrFamilies1 = [{Addr, Family} || Addr <- Addrs], + do_lookup(HostFamilies, + AddrFamilies ++ AddrFamilies1, + Err); + {error, _} = Err1 -> + do_lookup(HostFamilies, AddrFamilies, Err1) + end; +do_lookup([], [], Err) -> + Err; +do_lookup([], AddrFamilies, _Err) -> + {ok, AddrFamilies}. + +host_entry_to_addrs(#hostent{h_addr_list = AddrList}) -> + lists:filter( + fun(Addr) -> + try get_addr_type(Addr) of + _ -> true + catch _:badarg -> + false + end + end, AddrList). + +get_addr_type({_, _, _, _}) -> inet; +get_addr_type({_, _, _, _, _, _, _, _}) -> inet6; +get_addr_type(_) -> erlang:error(badarg). |