]> granicus.if.org Git - ejabberd/commitdiff
* src/mod_vcard_ldap.erl: LDAP server pool support (thanks to Evgeniy
authorMickaël Rémond <mickael.remond@process-one.net>
Sat, 27 Jan 2007 16:40:37 +0000 (16:40 +0000)
committerMickaël Rémond <mickael.remond@process-one.net>
Sat, 27 Jan 2007 16:40:37 +0000 (16:40 +0000)
Khramtsov) (EJAB-175)
* src/eldap/Makefile.in: Likewise
* src/ejabberd_auth_ldap.erl: Likewise
* src/eldap_pool.erl: Likewise

* src/eldap/eldap_utils.erl: Implemented LDAP domain substitution (EJAB-177)

* src/eldap/eldap.erl: Implemented queue to avoid bind deadlock under
heavy load (thanks to Evgeniy Khramtsov)  (EJAB-176)
* src/eldap/eldap.hrl: Likewise

SVN Revision: 716

ChangeLog
src/ejabberd_auth_ldap.erl
src/eldap/Makefile.in
src/eldap/Makefile.win32
src/eldap/eldap.erl
src/eldap/eldap.hrl
src/eldap/eldap_pool.erl [new file with mode: 0644]
src/eldap/eldap_utils.erl
src/mod_vcard_ldap.erl

index 9f5f92c7515b57b482cfad883972b4155f4eb20b..ca2cfa6df24671fb171f0149c8d5e7ed5d71d49e 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+2007-01-27  Mickael Remond  <mickael.remond@process-one.net>
+
+       * src/mod_vcard_ldap.erl: LDAP server pool support (thanks to Evgeniy
+       Khramtsov)
+       * src/eldap/Makefile.in: Likewise
+       * src/ejabberd_auth_ldap.erl: Likewise
+       * src/eldap_pool.erl: Likewise
+
+       * src/eldap/eldap_utils.erl: Implemented LDAP domain substitution
+
+       * src/eldap/eldap.erl: Implemented queue to avoid bind deadlock under
+       heavy load (thanks to Evgeniy Khramtsov)
+       * src/eldap/eldap.hrl: Likewise
+
 2007-01-26  Mickael Remond  <mickael.remond@process-one.net>
 
        * doc/guide.tex: Fixed typos in labels.
index 19b0c9e9d7b03c7123a1015fd804f083371b3b9a..78f229821f79f4b24722c85e464be3db1265ad85 100644 (file)
@@ -46,6 +46,7 @@
                eldap_id,
                bind_eldap_id,
                servers,
+               backups,
                port,
                dn,
                password,
@@ -74,7 +75,7 @@ start(Host) ->
     Proc = gen_mod:get_module_proc(Host, ?MODULE),
     ChildSpec = {
       Proc, {?MODULE, start_link, [Host]},
-      permanent, 1000, worker, [?MODULE]
+      transient, 1000, worker, [?MODULE]
      },
     supervisor:start_child(ejabberd_sup, ChildSpec).
 
@@ -96,13 +97,15 @@ terminate(_Reason, State) ->
 
 init(Host) ->
     State = parse_options(Host),
-    eldap:start_link(State#state.eldap_id,
+    eldap_pool:start_link(State#state.eldap_id,
                     State#state.servers,
+                    State#state.backups,
                     State#state.port,
                     State#state.dn,
                     State#state.password),
-    eldap:start_link(State#state.bind_eldap_id,
+    eldap_pool:start_link(State#state.bind_eldap_id,
                     State#state.servers,
+                    State#state.backups,
                     State#state.port,
                     State#state.dn,
                     State#state.password),
@@ -112,15 +115,11 @@ init(Host) ->
       ejabberd_auth, ctl_process_get_registered),
     {ok, State}.
 
--define(REPLY_TIMEOUT, 10000).
-
 plain_password_required() ->
     true.
 
 check_password(User, Server, Password) ->
-    Proc = gen_mod:get_module_proc(Server, ?MODULE),
-    case catch gen_server:call(Proc,
-                              {check_pass, User, Password}, ?REPLY_TIMEOUT) of
+    case catch check_password_ldap(User, Server, Password) of
        {'EXIT', _} ->
            false;
        Result ->
@@ -140,14 +139,10 @@ dirty_get_registered_users() ->
     get_vh_registered_users(?MYNAME).
 
 get_vh_registered_users(Server) ->
-    Proc = gen_mod:get_module_proc(Server, ?MODULE),
-    case catch gen_server:call(Proc,
-                              get_vh_registered_users, ?REPLY_TIMEOUT) of
-       {'EXIT', _} ->
-           [];
-       Result ->
-           Result
-    end.
+    case catch get_vh_registered_users_ldap(Server) of
+       {'EXIT', _} -> [];
+       Result -> Result
+       end.
 
 get_password(_User, _Server) ->
     false.
@@ -156,9 +151,7 @@ get_password_s(_User, _Server) ->
     "".
 
 is_user_exists(User, Server) ->
-    Proc = gen_mod:get_module_proc(Server, ?MODULE),
-    case catch gen_server:call(Proc,
-                              {is_user_exists, User}, ?REPLY_TIMEOUT) of
+    case catch is_user_exists_ldap(User, Server) of
        {'EXIT', _} ->
            false;
        Result ->
@@ -174,26 +167,27 @@ remove_user(_User, _Server, _Password) ->
 %%%----------------------------------------------------------------------
 %%% Internal functions
 %%%----------------------------------------------------------------------
-handle_call({check_pass, User, Password}, _From, State) ->
-    Reply = case find_user_dn(User, State) of
-               false ->
-                   false;
-               DN ->
-                   case eldap:bind(State#state.bind_eldap_id, DN, Password) of
-                       ok -> true;
-                       _ -> false
-                   end
-           end,
-    {reply, Reply, State};
-
-handle_call(get_vh_registered_users, _From, State) ->
+check_password_ldap(User, Server, Password) ->
+       {ok, State} = eldap_utils:get_state(Server, ?MODULE),
+       case find_user_dn(User, State) of
+       false ->
+           false;
+       DN ->
+           case eldap_pool:bind(State#state.bind_eldap_id, DN, Password) of
+               ok -> true;
+               _ -> false
+           end
+       end.
+
+get_vh_registered_users_ldap(Server) ->
+    {ok, State} = eldap_utils:get_state(Server, ?MODULE),
     UIDs = State#state.uids,
     Eldap_ID = State#state.eldap_id,
     Server = State#state.host,
     SortedDNAttrs = eldap_utils:usort_attrs(State#state.dn_filter_attrs),
-    Reply = case eldap_filter:parse(State#state.sfilter) of
+    case eldap_filter:parse(State#state.sfilter) of
                {ok, EldapFilter} ->
-                   case eldap:search(Eldap_ID, [{base, State#state.base},
+                   case eldap_pool:search(Eldap_ID, [{base, State#state.base},
                                                 {filter, EldapFilter},
                                                 {attributes, SortedDNAttrs}]) of
                        #eldap_search_result{entries = Entries} ->
@@ -222,15 +216,17 @@ handle_call(get_vh_registered_users, _From, State) ->
                    end;
                _ ->
                    []
-           end,
-    {reply, Reply, State};
+       end.
 
-handle_call({is_user_exists, User}, _From, State) ->
-    Reply = case find_user_dn(User, State) of
+is_user_exists_ldap(User, Server) ->
+    {ok, State} = eldap_utils:get_state(Server, ?MODULE),
+    case find_user_dn(User, State) of
                false -> false;
                _DN -> true
-           end,
-    {reply, Reply, State};
+       end.
+
+handle_call(get_state, _From, State) ->
+       {reply, {ok, State}, State};
 
 handle_call(stop, _From, State) ->
     {stop, normal, ok, State};
@@ -242,7 +238,7 @@ find_user_dn(User, State) ->
     DNAttrs = eldap_utils:usort_attrs(State#state.dn_filter_attrs),
     case eldap_filter:parse(State#state.ufilter, [{"%u", User}]) of
        {ok, Filter} ->
-           case eldap:search(State#state.eldap_id, [{base, State#state.base},
+           case eldap_pool:search(State#state.eldap_id, [{base, State#state.base},
                                                     {filter, Filter},
                                                     {attributes, DNAttrs}]) of
                #eldap_search_result{entries = [#eldap_entry{attributes = Attrs,
@@ -272,7 +268,7 @@ is_valid_dn(DN, Attrs, State) ->
                  end ++ [{"%d", State#state.host}, {"%D", DN}],
     case eldap_filter:parse(State#state.dn_filter, SubstValues) of
        {ok, EldapFilter} ->
-           case eldap:search(State#state.eldap_id, [
+           case eldap_pool:search(State#state.eldap_id, [
                                                     {base, State#state.base},
                                                     {filter, EldapFilter},
                                                     {attributes, ["dn"]}]) of
@@ -292,6 +288,10 @@ parse_options(Host) ->
     Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, ?MODULE)),
     Bind_Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, bind_ejabberd_auth_ldap)),
     LDAPServers = ejabberd_config:get_local_option({ldap_servers, Host}),
+    LDAPBackups = case ejabberd_config:get_local_option({ldap_backups, Host}) of
+                  undefined -> [];
+                  Backups -> Backups
+                  end,
     LDAPPort = case ejabberd_config:get_local_option({ldap_port, Host}) of
                   undefined -> 389;
                   P -> P
@@ -306,7 +306,7 @@ parse_options(Host) ->
               end,
     UIDs = case ejabberd_config:get_local_option({ldap_uids, Host}) of
               undefined -> [{"uid", "%u"}];
-              UI -> UI
+              UI -> eldap_utils:uids_domain_subst(Host, UI)
           end,
     SubFilter = lists:flatten(eldap_utils:generate_subfilter(UIDs)),
     UserFilter = case ejabberd_config:get_local_option({ldap_filter, Host}) of
@@ -325,6 +325,7 @@ parse_options(Host) ->
           eldap_id = Eldap_ID,
           bind_eldap_id = Bind_Eldap_ID,
           servers = LDAPServers,
+          backups = LDAPBackups,
           port = LDAPPort,
           dn = RootDN,
           password = Password,
index de79338311bfb2f31e2b18dff50131b617441e92..f244fe1b583992d68f0ae04d71c0d02948ec1a82 100644 (file)
@@ -14,7 +14,8 @@ OBJS   = \
        $(OUTDIR)/eldap.beam \
        $(OUTDIR)/ELDAPv3.beam \
        $(OUTDIR)/eldap_filter.beam \
-       $(OUTDIR)/eldap_utils.beam
+       $(OUTDIR)/eldap_utils.beam \
+       $(OUTDIR)/eldap_pool.beam
 
 all:    $(OBJS)
 
index 04b0e24e8f304377b760a5942693e092d58d066c..1e4c503e60ed62679ef69074a5499606c4f2b86b 100644 (file)
@@ -7,11 +7,13 @@ EFLAGS = -I .. -pz ..
 OBJS = \
        $(OUTDIR)\eldap.beam \
        $(OUTDIR)\ELDAPv3.beam \
-       $(OUTDIR)\eldap_filter.beam
+       $(OUTDIR)\eldap_filter.beam \
+       $(OUTDIR)\eldap_utils.beam \
+       $(OUTDIR)\eldap_pool.beam
 
 ALL : $(OBJS)
 
-CLEAN :
+Clean :
        -@erase ELDAPv3.asn1db
        -@erase ELDAPv3.erl
        -@erase ELDAPv3.hrl
@@ -29,3 +31,9 @@ $(OUTDIR)\ELDAPv3.beam : ELDAPv3.erl
 
 $(OUTDIR)\eldap_filter.beam : eldap_filter.erl
        erlc -W $(EFLAGS) -o $(OUTDIR) eldap_filter.erl
+
+$(OUTDIR)\eldap_utils.beam : eldap_utils.erl
+       erlc -W $(EFLAGS) -o $(OUTDIR) eldap_utils.erl
+
+$(OUTDIR)\eldap_pool.beam : eldap_pool.erl
+       erlc -W $(EFLAGS) -o $(OUTDIR) eldap_pool.erl
index 1bf64b2b45d0af70dc31472786c47d3f1157e8bf..95bf3706027b2f46fcb27ecb91f1f77f0b2d86cc 100644 (file)
@@ -5,7 +5,7 @@
 %%%           The interface is based on RFC 1823, and
 %%%           draft-ietf-asid-ldap-c-api-00.txt
 %%%
-%%% Copyright (C) 2000  Torbjörn Törnkvist, tnt@home.se
+%%% Copyright (C) 2000  Torbj�n T�nkvist, 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
@@ -32,6 +32,9 @@
 
 
 %%% Modified by Alexey Shchepin <alexey@sevcom.net>
+
+%%% Modified by Evgeniy Khramtsov <xram@jabber.ru>
+%%% Implemented queue for bind() requests to prevent pending binds.
 %%% --------------------------------------------------------------------
 -vc('$Id$ ').
 
@@ -42,6 +45,7 @@
 %%%     connecting - actually disconnected, but retrying periodically
 %%%     wait_bind_response  - connected and sent bind request
 %%%     active - bound to LDAP Server and ready to handle commands
+%%%     active_bind - sent bind() request and waiting for response
 %%%----------------------------------------------------------------------
 
 %%-compile(export_all).
@@ -61,7 +65,7 @@
 
 %% gen_fsm callbacks
 -export([init/1, connecting/2,
-        connecting/3, wait_bind_response/3, active/3, handle_event/3,
+        connecting/3, wait_bind_response/3, active/3, active_bind/3, handle_event/3,
         handle_sync_event/4, handle_info/3, terminate/3, code_change/4]).
 
 
 -define(LDAP_VERSION, 3).
 -define(RETRY_TIMEOUT, 5000).
 -define(BIND_TIMEOUT, 10000).
--define(CMD_TIMEOUT, 5000).
+-define(CMD_TIMEOUT, 100000).
 -define(MAX_TRANSACTION_ID, 65535).
 -define(MIN_TRANSACTION_ID, 0).
 
 -record(eldap, {version = ?LDAP_VERSION,
-               hosts,        % Possible hosts running LDAP servers
-               host = null,  % Connected Host LDAP server
-               port = 389 ,  % The LDAP server port
-               fd = null,    % Socket filedescriptor.
-               rootdn = "",  % Name of the entry to bind as
-               passwd,       % Password for (above) entry
-               id = 0,       % LDAP Request ID 
-               log,          % User provided log function
-               bind_timer,   % Ref to bind timeout
-               dict,         % dict holding operation params and results
-               debug_level   % Integer debug/logging level
+               hosts,         % Possible hosts running LDAP servers
+               host = null,   % Connected Host LDAP server
+               port = 389 ,   % The LDAP server port
+               fd = null,     % Socket filedescriptor.
+               rootdn = "",   % Name of the entry to bind as
+               passwd,        % Password for (above) entry
+               id = 0,        % LDAP Request ID 
+               log,           % User provided log function
+               bind_timer,    % Ref to bind timeout
+               dict,          % dict holding operation params and results
+               bind_q,        % Queue for bind() requests
+               debug_level    % Integer debug/logging level
               }).
 
 %%%----------------------------------------------------------------------
@@ -196,10 +201,10 @@ mod_replace(Type, Values) when list(Type), list(Values) -> m(replace, Type, Valu
 
 m(Operation, Type, Values) ->
     #'ModifyRequest_modification_SEQOF'{
-       operation = Operation,
-       modification = #'AttributeTypeAndValues'{
-        type = Type,
-        vals = Values}}.
+   operation = Operation,
+   modification = #'AttributeTypeAndValues'{
+     type = Type,
+     vals = Values}}.
 
 %%% --------------------------------------------------------------------
 %%% Modify an entry. Given an entry a number of modification
@@ -230,7 +235,7 @@ modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup)
 bind(Handle, RootDN, Passwd) 
   when list(RootDN),list(Passwd) ->
     Handle1 = get_handle(Handle),
-    gen_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd}).
+    gen_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd}, infinity).
 
 %%% Sanity checks !
 
@@ -266,7 +271,7 @@ optional(Value) -> Value.
 %%% --------------------------------------------------------------------
 search(Handle, A) when record(A, eldap_search) ->
     call_search(Handle, A);
-search(Handle, L) when list(Handle), list(L) ->
+search(Handle, L) when list(L) ->
     case catch parse_search_args(L) of
        {error, Emsg}                  -> {error, Emsg};
        {'EXIT', Emsg}                 -> {error, Emsg};
@@ -275,11 +280,11 @@ search(Handle, L) when list(Handle), list(L) ->
 
 call_search(Handle, A) ->
     Handle1 = get_handle(Handle),
-    gen_fsm:sync_send_event(Handle1, {search, A}).
+    gen_fsm:sync_send_event(Handle1, {search, A}, infinity).
 
 parse_search_args(Args) ->
     parse_search_args(Args, #eldap_search{scope = wholeSubtree}).
-                      
+
 parse_search_args([{base, Base}|T],A) ->
     parse_search_args(T,A#eldap_search{base = Base});
 parse_search_args([{filter, Filter}|T],A) ->
@@ -292,6 +297,8 @@ parse_search_args([{types_only, TypesOnly}|T],A) ->
     parse_search_args(T,A#eldap_search{types_only = TypesOnly});
 parse_search_args([{timeout, Timeout}|T],A) when integer(Timeout) ->
     parse_search_args(T,A#eldap_search{timeout = Timeout});
+parse_search_args([{limit, Limit}|T],A) when is_integer(Limit) ->
+    parse_search_args(T,A#eldap_search{limit = Limit});
 parse_search_args([H|T],A) ->
     throw({error,{unknown_arg, H}});
 parse_search_args([],A) ->
@@ -383,6 +390,7 @@ init({Hosts, Port, Rootdn, Passwd, Log}) ->
                            id = 0,
                            log = Log,
                            dict = dict:new(),
+                           bind_q = queue:new(),
                            debug_level = 0}, 0}.
 
 %%----------------------------------------------------------------------
@@ -417,13 +425,28 @@ wait_bind_response(Event, From, S) ->
 active(Event, From, S) ->
     case catch send_command(Event, From, S) of
        {ok, NewS} ->
-           {next_state, active, NewS};
+           case Event of
+               {bind, _, _} ->
+                   {next_state, active_bind, NewS};
+               _ ->
+                   {next_state, active, NewS}
+           end;
        {error, Reason} ->
            {reply, {error, Reason}, active, S};
        {'EXIT', Reason} ->
            {reply, {error, Reason}, active, S}
     end.
 
+active_bind({bind, RootDN, Passwd}, From, #eldap{bind_q=Q} = S) ->
+    NewQ = queue:in({{bind, RootDN, Passwd}, From}, Q),
+    {next_state, active_bind, S#eldap{bind_q=NewQ}};
+active_bind(Event, From, S) ->
+    case catch send_command(Event, From, S) of
+       {ok, NewS}       -> {next_state, active_bind, NewS};
+       {error, Reason}  -> {reply, {error, Reason}, active_bind, S};
+       {'EXIT', Reason} -> {reply, {error, Reason}, active_bind, S}
+    end.
+
 %%----------------------------------------------------------------------
 %% Func: handle_event/3
 %% Called when gen_fsm:send_all_state_event/2 is invoked.
@@ -435,6 +458,19 @@ handle_event(close, StateName, S) ->
     gen_tcp:close(S#eldap.fd),
     {stop, closed, S};
 
+handle_event(process_bind_q, active_bind, #eldap{bind_q=Q} = S) ->
+    case queue:out(Q) of
+       {{value, {BindEvent, To}}, NewQ} ->
+           NewStateData = case catch send_command(BindEvent, To, S) of
+                              {ok, NewS}       -> NewS;
+                              {error, Reason}  -> gen_fsm:reply(To, {error, Reason}), S;
+                              {'EXIT', Reason} -> gen_fsm:reply(To, {error, Reason}), S
+                          end,
+           {next_state, active_bind, NewStateData#eldap{bind_q=NewQ}};
+       {empty, Q} ->
+           {next_state, active, S}
+    end;
+
 handle_event(Event, StateName, S) ->
     {next_state, StateName, S}.
 
@@ -484,13 +520,14 @@ handle_info({tcp, Socket, Data}, wait_bind_response, S) ->
                                {next_state, connecting, S#eldap{fd = null}}
     end;
 
-handle_info({tcp, Socket, Data}, active, S) ->
+handle_info({tcp, Socket, Data}, StateName, S)
+  when StateName==active; StateName==active_bind ->
     case catch recvd_packet(Data, S) of
        {reply, Reply, To, NewS} -> gen_fsm:reply(To, Reply),
-                                   {next_state, active, NewS};
-       {ok, NewS}               -> {next_state, active, NewS};
-       {'EXIT', Reason}         -> {next_state, active, S};
-       {error, Reason}          -> {next_state, active, S}
+                                   {next_state, StateName, NewS};
+       {ok, NewS}               -> {next_state, StateName, NewS};
+       {'EXIT', Reason}         -> {next_state, StateName, S};
+       {error, Reason}          -> {next_state, StateName, S}
     end;
 
 handle_info({tcp_closed, Socket}, All_fsm_states, S) ->
@@ -501,7 +538,7 @@ handle_info({tcp_closed, Socket}, All_fsm_states, S) ->
     dict:map(F, S#eldap.dict),
     retry_connect(),
     {next_state, connecting, S#eldap{fd = null,
-                                    dict = dict:new()}};
+                                    dict = dict:new(), bind_q=queue:new()}};
 
 handle_info({tcp_error, Socket, Reason}, Fsm_state, S) ->
     log1("eldap received tcp_error: ~p~nIn State: ~p~n", [Reason, Fsm_state], S),
@@ -529,7 +566,7 @@ handle_info({timeout, Timer, bind_timeout}, wait_bind_response, S) ->
 %%
 handle_info(Info, StateName, S) ->
     log1("eldap. Unexpected Info: ~p~nIn state: ~p~n when StateData is: ~p~n",
-                       [Info, StateName, S], S),
+        [Info, StateName, S], S),
     {next_state, StateName, S}.
 
 %%----------------------------------------------------------------------
@@ -569,7 +606,7 @@ gen_req({search, A}) ->
      #'SearchRequest'{baseObject   = A#eldap_search.base,
                      scope        = v_scope(A#eldap_search.scope),
                      derefAliases = neverDerefAliases,
-                     sizeLimit    = 0, % no size limit
+                     sizeLimit    = A#eldap_search.limit,
                      timeLimit    = v_timeout(A#eldap_search.timeout),
                      typesOnly    = v_bool(A#eldap_search.types_only),
                      filter       = v_filter(A#eldap_search.filter),
@@ -620,23 +657,24 @@ recvd_packet(Pkt, S) ->
            {Timer, From, Name, Result_so_far} = get_op_rec(Id, Dict),
            case {Name, Op} of
                {searchRequest, {searchResEntry, R}} when
-                     record(R,'SearchResultEntry') ->
+               record(R,'SearchResultEntry') ->
                    New_dict = dict:append(Id, R, Dict),
                    {ok, S#eldap{dict = New_dict}};
                {searchRequest, {searchResDone, Result}} ->
-                   case Result#'LDAPResult'.resultCode of
-                       success ->
+                   Reason = Result#'LDAPResult'.resultCode,
+                   if
+                       Reason==success; Reason=='sizeLimitExceeded' ->
                            {Res, Ref} = polish(Result_so_far),
                            New_dict = dict:erase(Id, Dict),
                            cancel_timer(Timer),
                            {reply, #eldap_search_result{entries = Res,
                                                         referrals = Ref}, From,
-                                                     S#eldap{dict = New_dict}};
-                       Reason ->
+                            S#eldap{dict = New_dict}};
+                       true ->
                            New_dict = dict:erase(Id, Dict),
                            cancel_timer(Timer),
                            {reply, {error, Reason}, From, S#eldap{dict = New_dict}}
-                       end;
+                   end;
                {searchRequest, {searchResRef, R}} ->
                    New_dict = dict:append(Id, R, Dict),
                    {ok, S#eldap{dict = New_dict}};
@@ -664,12 +702,13 @@ recvd_packet(Pkt, S) ->
                    New_dict = dict:erase(Id, Dict),
                    cancel_timer(Timer),
                    Reply = check_bind_reply(Result, From),
+                   gen_fsm:send_all_state_event(self(), process_bind_q),
                    {reply, Reply, From, S#eldap{dict = New_dict}};
                {OtherName, OtherResult} ->
                    New_dict = dict:erase(Id, Dict),
                    cancel_timer(Timer),
                    {reply, {error, {invalid_result, OtherName, OtherResult}},
-                           From, S#eldap{dict = New_dict}}
+                    From, S#eldap{dict = New_dict}}
            end;
        Error -> Error
     end.
@@ -773,7 +812,7 @@ cmd_timeout(Timer, Id, S) ->
                    {reply, From, {timeout,
                                   #eldap_search_result{entries = Res1,
                                                        referrals = Ref1}},
-                                  S#eldap{dict = New_dict}};
+                    S#eldap{dict = New_dict}};
                Others ->
                    New_dict = dict:erase(Id, Dict),
                    {reply, From, {error, timeout}, S#eldap{dict = New_dict}}
index e31a34991f4ae5c381215dbb225700759ffec8da..1f1a4e1a01ce0d2f80640b73fad37428fd908283 100644 (file)
@@ -1,6 +1,7 @@
 -record(eldap_search, {scope = wholeSubtree,
                       base = [],
                       filter,
+                      limit = 0,
                       attributes = [],
                       types_only = false,
                       timeout = 0}).
diff --git a/src/eldap/eldap_pool.erl b/src/eldap/eldap_pool.erl
new file mode 100644 (file)
index 0000000..959b16a
--- /dev/null
@@ -0,0 +1,57 @@
+%%%-------------------------------------------------------------------
+%%% File    : eldap_pool.erl
+%%% Author  : Evgeniy Khramtsov <xram@jabber.ru>
+%%% Purpose : LDAP connections pool
+%%% Created : 12 Nov 2006 by Evgeniy Khramtsov <xram@jabber.ru>
+%%% Id      : $Id$
+%%%-------------------------------------------------------------------
+-module(eldap_pool).
+-author('xram@jabber.ru').
+
+%% API
+-export([
+        start_link/6,
+        bind/3,
+        search/2
+       ]).
+
+%%====================================================================
+%% API
+%%====================================================================
+bind(PoolName, DN, Passwd) ->
+    do_request(PoolName, {bind, [DN, Passwd]}).
+
+search(PoolName, Opts) ->
+    do_request(PoolName, {search, [Opts]}).
+
+start_link(Name, Hosts, Backups, Port, Rootdn, Passwd) ->
+    PoolName = make_id(Name),
+    pg2:create(PoolName),
+    lists:foreach(fun(Host) ->
+                         ID = erlang:ref_to_list(make_ref()),
+                         case catch eldap:start_link(ID, [Host|Backups], Port, Rootdn, Passwd) of
+                             {ok, Pid} ->
+                                 pg2:join(PoolName, Pid);
+                             _ ->
+                                 error
+                         end
+                 end, Hosts).
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+do_request(Name, {F, Args}) ->
+    case pg2:get_closest_pid(make_id(Name)) of
+       Pid when is_pid(Pid) ->
+           case catch apply(eldap, F, [Pid | Args]) of
+               {'EXIT', Reason} ->
+                   {error, Reason};
+               Reply ->
+                   Reply
+           end;
+       Err ->
+           Err
+    end.
+
+make_id(Name) ->
+    list_to_atom("eldap_pool_" ++ Name).
index 50a3108592d974847087fbca10455758f1592ed5..64a70af411867fe7047813b1a07bf912fd511eb4 100644 (file)
@@ -16,7 +16,9 @@
         usort_attrs/1,
         get_user_part/2,
         make_filter/2,
-        case_insensitive_match/2]).
+        get_state/2,
+        case_insensitive_match/2,
+        uids_domain_subst/2]).
 
 %% Generate an 'or' LDAP query on one or several attributes
 %% If there is only one attribute
@@ -109,3 +111,17 @@ case_insensitive_match(X, Y) ->
        true -> false
     end.
 
+get_state(Server, Module) ->
+    Proc = gen_mod:get_module_proc(Server, Module),
+    gen_server:call(Proc, get_state).
+
+%% From the list of uids attribute:
+%% we look from alias domain (%d) and make the substitution
+%% with the actual host domain
+%% This help when you need to configure many virtual domains.
+uids_domain_subst(Host, UIDs) ->
+    lists:map(fun({U,V}) ->
+                      {U, eldap_filter:do_sub(V,[{"%d", Host}])};
+                  (A) -> A 
+              end,
+              UIDs).
\ No newline at end of file
index f0ec35462e5ae93ac934dac6ec7a5812c18f0867..ddcfc5373dfbb0360cebaa74d4c26169ee752a06 100644 (file)
@@ -28,7 +28,8 @@
         get_sm_features/5,
         process_local_iq/3,
         process_sm_iq/3,
-        remove_user/1
+        remove_user/1,
+        route/4
        ]).
 
 -include("ejabberd.hrl").
@@ -42,6 +43,7 @@
                eldap_id,
                search,
                servers,
+               backups,
                port,
                dn,
                base,
@@ -53,7 +55,8 @@
                search_filter,
                search_fields,
                search_reported,
-               search_reported_attrs
+               search_reported_attrs,
+               matches
               }).
 
 -define(VCARD_MAP,
@@ -120,7 +123,7 @@ start(Host, Opts) ->
     Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
     ChildSpec = {
       Proc, {?MODULE, start_link, [Host, Opts]},
-      permanent, 1000, worker, [?MODULE]
+      transient, 1000, worker, [?MODULE]
      },
     supervisor:start_child(ejabberd_sup, ChildSpec).
 
@@ -148,14 +151,15 @@ start_link(Host, Opts) ->
 
 init([Host, Opts]) ->
     State = parse_options(Host, Opts),
-    IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
+    IQDisc = gen_mod:get_opt(iqdisc, Opts, parallel),
     gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
                                  ?MODULE, process_local_iq, IQDisc),
     gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_VCARD,
                                  ?MODULE, process_sm_iq, IQDisc),
     ejabberd_hooks:add(disco_sm_features, Host, ?MODULE, get_sm_features, 50),
-    eldap:start_link(State#state.eldap_id,
+    eldap_pool:start_link(State#state.eldap_id,
                     State#state.servers,
+                    State#state.backups,
                     State#state.port,
                     State#state.dn,
                     State#state.password),
@@ -169,14 +173,13 @@ init([Host, Opts]) ->
 
 handle_info({route, From, To, Packet}, State) ->
     case catch do_route(State, From, To, Packet) of
-       {'EXIT', Reason} ->
-           Err = jlib:make_error_reply(Packet, ?ERR_INTERNAL_SERVER_ERROR),
-           ejabberd_router:route(To, From, Err),
-           %% Fail-Stop. Let the supervisor restarts us
-           {stop, Reason, State};
+       Pid when is_pid(Pid) ->
+           ok;
        _ ->
-           {noreply, State}
-    end;
+           Err = jlib:make_error_reply(Packet, ?ERR_INTERNAL_SERVER_ERROR),
+           ejabberd_router:route(To, From, Err)
+    end,
+    {noreply, State};
 
 handle_info(_Info, State) ->
     {noreply, State}.
@@ -220,52 +223,42 @@ process_local_iq(_From, _To, #iq{type = Type, lang = Lang, sub_el = SubEl} = IQ)
                             ]}]}
     end.
 
--define(SM_IQ_TIMEOUT, 20000).
-
-process_sm_iq(From, #jid{lserver=LServer} = To, #iq{sub_el = SubEl} = IQ) ->
-    Proc = gen_mod:get_module_proc(LServer, ?PROCNAME),
-    case catch gen_server:call(Proc,
-                              {process_sm_iq, From, To, IQ}, ?SM_IQ_TIMEOUT) of
-       {'EXIT', Reason} ->
-           case Reason of
-               {timeout, _} ->
-                   IQ#iq{type = error,
-                         sub_el = [SubEl, ?ERR_REMOTE_SERVER_TIMEOUT]};
-               _ ->
-                   IQ#iq{type = error,
-                         sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
-           end;
+process_sm_iq(_From, #jid{lserver=LServer} = To, #iq{sub_el = SubEl} = IQ) ->
+    case catch process_vcard_ldap(To, IQ, LServer) of
+       {'EXIT', _} ->
+           IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]};
        Other ->
            Other
     end.
 
-handle_call({process_sm_iq, _From, To, IQ}, _FromPid, State) ->
+process_vcard_ldap(To, IQ, Server) ->
+    {ok, State} = eldap_utils:get_state(Server, ?PROCNAME),
     #iq{type = Type, sub_el = SubEl} = IQ,
-    Reply = case Type of
-               set ->
-                   IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
-               get ->
-                   #jid{luser = LUser} = To,
-                   LServer = State#state.serverhost,
-                   case ejabberd_auth:is_user_exists(LUser, LServer) of
-                       true ->
-                           VCardMap = State#state.vcard_map,
-                           case find_ldap_user(LUser, State) of
-                               #eldap_entry{attributes = Attributes} ->
-                                   Vcard = ldap_attributes_to_vcard(Attributes, VCardMap, {LUser, LServer}),
-                                   IQ#iq{type = result, sub_el = Vcard};
-                               _ ->
-                                   IQ#iq{type = result, sub_el = []}
-                           end;
+    case Type of
+       set ->
+           IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]};
+       get ->
+           #jid{luser = LUser} = To,
+           LServer = State#state.serverhost,
+           case ejabberd_auth:is_user_exists(LUser, LServer) of
+               true ->
+                   VCardMap = State#state.vcard_map,
+                   case find_ldap_user(LUser, State) of
+                       #eldap_entry{attributes = Attributes} ->
+                           Vcard = ldap_attributes_to_vcard(Attributes, VCardMap, {LUser, LServer}),
+                           IQ#iq{type = result, sub_el = Vcard};
                        _ ->
                            IQ#iq{type = result, sub_el = []}
-                   end
-           end,
-    {reply, Reply, State};
+                   end;
+               _ ->
+                   IQ#iq{type = result, sub_el = []}
+           end
+       end.
 
+handle_call(get_state, _From, State) ->
+    {reply, {ok, State}, State};
 handle_call(stop, _From, State) ->
     {stop, normal, ok, State};
-
 handle_call(_Request, _From, State) ->
     {reply, bad_request, State}.
 
@@ -276,7 +269,7 @@ find_ldap_user(User, State) ->
     VCardAttrs = State#state.vcard_map_attrs,
     case eldap_filter:parse(RFC2254_Filter, [{"%u", User}]) of
        {ok, EldapFilter} ->
-           case eldap:search(Eldap_ID, [{base, Base},
+           case eldap_pool:search(Eldap_ID, [{base, Base},
                                         {filter, EldapFilter},
                                         {attributes, VCardAttrs}]) of
                #eldap_search_result{entries = [E | _]} ->
@@ -391,6 +384,9 @@ ldap_attribute_to_vcard(_, _) ->
          ] ++ lists:map(fun({X,Y}) -> ?TLFIELD("text-single", X, Y) end, SearchFields)}]).
 
 do_route(State, From, To, Packet) ->
+    spawn(?MODULE, route, [State, From, To, Packet]).
+
+route(State, From, To, Packet) ->
     #jid{user = User, resource = Resource} = To,
     if
        (User /= "") or (Resource /= "") ->
@@ -552,10 +548,12 @@ search(State, Data) ->
     SearchFilter = State#state.search_filter,
     Eldap_ID = State#state.eldap_id,
     UIDs = State#state.uids,
+    Limit = State#state.matches,
     ReportedAttrs = State#state.search_reported_attrs,
     Filter = eldap:'and'([SearchFilter, eldap_utils:make_filter(Data, UIDs)]),
-    case eldap:search(Eldap_ID, [{base, Base},
+    case eldap_pool:search(Eldap_ID, [{base, Base},
                                 {filter, Filter},
+                                {limit, Limit},
                                 {attributes, ReportedAttrs}]) of
        #eldap_search_result{entries = E} ->
            search_items(E, State);
@@ -645,12 +643,21 @@ find_xdata_el1([_ | Els]) ->
 parse_options(Host, Opts) ->
     MyHost = gen_mod:get_opt(host, Opts, "vjud." ++ Host),
     Search = gen_mod:get_opt(search, Opts, true),
+    Matches = case gen_mod:get_opt(matches, Opts, 30) of
+               infinity  -> 0;
+               N         -> N
+           end,
     Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, ?PROCNAME)),
     LDAPServers = case gen_mod:get_opt(ldap_servers, Opts, undefined) of
                      undefined ->
                          ejabberd_config:get_local_option({ldap_servers, Host});
                      S -> S
                  end,
+    LDAPBackups = case gen_mod:get_opt(ldap_backups, Opts, undefined) of
+                     undefined ->
+                         ejabberd_config:get_local_option({ldap_servers, Host});
+                     Backups -> Backups
+                 end,
     LDAPPort = case gen_mod:get_opt(ldap_port, Opts, undefined) of
                   undefined ->
                       case ejabberd_config:get_local_option({ldap_port, Host}) of
@@ -668,9 +675,9 @@ parse_options(Host, Opts) ->
            undefined ->
                    case ejabberd_config:get_local_option({ldap_uids, Host}) of
                        undefined -> [{"uid", "%u"}];
-                       UI -> UI
+                       UI -> eldap_utils:uids_domain_subst(Host, UI)
                        end;
-               UI -> UI
+               UI -> eldap_utils:uids_domain_subst(Host, UI)
                end,
     RootDN = case gen_mod:get_opt(ldap_rootdn, Opts, undefined) of
                 undefined ->
@@ -723,6 +730,7 @@ parse_options(Host, Opts) ->
           eldap_id = Eldap_ID,
           search = Search,
           servers = LDAPServers,
+          backups = LDAPBackups,
           port = LDAPPort,
           dn = RootDN,
           base = LDAPBase,
@@ -734,5 +742,6 @@ parse_options(Host, Opts) ->
           search_filter = SearchFilter,
           search_fields = SearchFields,
           search_reported = SearchReported,
-          search_reported_attrs = SearchReportedAttrs
+          search_reported_attrs = SearchReportedAttrs,
+          matches = Matches
          }.