* 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
This commit is contained in:
Mickaël Rémond 2007-01-27 16:40:37 +00:00
parent 32c0253871
commit d9e8e07ffd
9 changed files with 284 additions and 138 deletions

View File

@ -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.

View 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};
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,

View 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)

View 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

View 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<EFBFBD>n T<EFBFBD>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]).
@ -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}}

View File

@ -1,6 +1,7 @@
-record(eldap_search, {scope = wholeSubtree,
base = [],
filter,
limit = 0,
attributes = [],
types_only = false,
timeout = 0}).

57
src/eldap/eldap_pool.erl Normal file
View File

@ -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).

View 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).

View 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
}.