From d9e8e07ffde6733f7fbb882f58265a84df157c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20R=C3=A9mond?= Date: Sat, 27 Jan 2007 16:40:37 +0000 Subject: [PATCH] * src/mod_vcard_ldap.erl: LDAP server pool support (thanks to Evgeniy 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 | 14 +++++ src/ejabberd_auth_ldap.erl | 85 ++++++++++++++------------- src/eldap/Makefile.in | 3 +- src/eldap/Makefile.win32 | 12 +++- src/eldap/eldap.erl | 117 ++++++++++++++++++++++++------------- src/eldap/eldap.hrl | 1 + src/eldap/eldap_pool.erl | 57 ++++++++++++++++++ src/eldap/eldap_utils.erl | 18 +++++- src/mod_vcard_ldap.erl | 115 +++++++++++++++++++----------------- 9 files changed, 284 insertions(+), 138 deletions(-) create mode 100644 src/eldap/eldap_pool.erl diff --git a/ChangeLog b/ChangeLog index 9f5f92c75..ca2cfa6df 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,17 @@ +2007-01-27 Mickael Remond + + * 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 * doc/guide.tex: Fixed typos in labels. diff --git a/src/ejabberd_auth_ldap.erl b/src/ejabberd_auth_ldap.erl index 19b0c9e9d..78f229821 100644 --- a/src/ejabberd_auth_ldap.erl +++ b/src/ejabberd_auth_ldap.erl @@ -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}; +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. -handle_call(get_vh_registered_users, _From, State) -> +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, diff --git a/src/eldap/Makefile.in b/src/eldap/Makefile.in index de7933831..f244fe1b5 100644 --- a/src/eldap/Makefile.in +++ b/src/eldap/Makefile.in @@ -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) diff --git a/src/eldap/Makefile.win32 b/src/eldap/Makefile.win32 index 04b0e24e8..1e4c503e6 100644 --- a/src/eldap/Makefile.win32 +++ b/src/eldap/Makefile.win32 @@ -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 diff --git a/src/eldap/eldap.erl b/src/eldap/eldap.erl index 1bf64b2b4..95bf37060 100644 --- a/src/eldap/eldap.erl +++ b/src/eldap/eldap.erl @@ -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 + +%%% Modified by Evgeniy Khramtsov +%%% 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]). @@ -73,22 +77,23 @@ -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}} diff --git a/src/eldap/eldap.hrl b/src/eldap/eldap.hrl index e31a34991..1f1a4e1a0 100644 --- a/src/eldap/eldap.hrl +++ b/src/eldap/eldap.hrl @@ -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 index 000000000..959b16ac7 --- /dev/null +++ b/src/eldap/eldap_pool.erl @@ -0,0 +1,57 @@ +%%%------------------------------------------------------------------- +%%% File : eldap_pool.erl +%%% Author : Evgeniy Khramtsov +%%% Purpose : LDAP connections pool +%%% Created : 12 Nov 2006 by Evgeniy Khramtsov +%%% 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). diff --git a/src/eldap/eldap_utils.erl b/src/eldap/eldap_utils.erl index 50a310859..64a70af41 100644 --- a/src/eldap/eldap_utils.erl +++ b/src/eldap/eldap_utils.erl @@ -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 diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl index f0ec35462..ddcfc5373 100644 --- a/src/mod_vcard_ldap.erl +++ b/src/mod_vcard_ldap.erl @@ -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 }.