+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.
eldap_id,
bind_eldap_id,
servers,
+ backups,
port,
dn,
password,
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).
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),
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 ->
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.
"".
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 ->
%%%----------------------------------------------------------------------
%%% 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} ->
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};
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,
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
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
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
eldap_id = Eldap_ID,
bind_eldap_id = Bind_Eldap_ID,
servers = LDAPServers,
+ backups = LDAPBackups,
port = LDAPPort,
dn = RootDN,
password = Password,
$(OUTDIR)/eldap.beam \
$(OUTDIR)/ELDAPv3.beam \
$(OUTDIR)/eldap_filter.beam \
- $(OUTDIR)/eldap_utils.beam
+ $(OUTDIR)/eldap_utils.beam \
+ $(OUTDIR)/eldap_pool.beam
all: $(OBJS)
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
$(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
%%% 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
%%% 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$ ').
%%% 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).
%% 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
}).
%%%----------------------------------------------------------------------
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
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 !
%%% --------------------------------------------------------------------
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};
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) ->
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) ->
id = 0,
log = Log,
dict = dict:new(),
+ bind_q = queue:new(),
debug_level = 0}, 0}.
%%----------------------------------------------------------------------
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.
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}.
{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) ->
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),
%%
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}.
%%----------------------------------------------------------------------
#'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),
{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}};
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.
{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}}
-record(eldap_search, {scope = wholeSubtree,
base = [],
filter,
+ limit = 0,
attributes = [],
types_only = false,
timeout = 0}).
--- /dev/null
+%%%-------------------------------------------------------------------
+%%% 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).
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
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
get_sm_features/5,
process_local_iq/3,
process_sm_iq/3,
- remove_user/1
+ remove_user/1,
+ route/4
]).
-include("ejabberd.hrl").
eldap_id,
search,
servers,
+ backups,
port,
dn,
base,
search_filter,
search_fields,
search_reported,
- search_reported_attrs
+ search_reported_attrs,
+ matches
}).
-define(VCARD_MAP,
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).
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),
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}.
]}]}
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}.
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 | _]} ->
] ++ 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 /= "") ->
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);
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
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 ->
eldap_id = Eldap_ID,
search = Search,
servers = LDAPServers,
+ backups = LDAPBackups,
port = LDAPPort,
dn = RootDN,
base = LDAPBase,
search_filter = SearchFilter,
search_fields = SearchFields,
search_reported = SearchReported,
- search_reported_attrs = SearchReportedAttrs
+ search_reported_attrs = SearchReportedAttrs,
+ matches = Matches
}.