mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-24 16:23:40 +01:00
* 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:
parent
32c0253871
commit
d9e8e07ffd
14
ChangeLog
14
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.
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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}}
|
||||
|
@ -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
57
src/eldap/eldap_pool.erl
Normal 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).
|
@ -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).
|
@ -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
|
||||
}.
|
||||
|
Loading…
Reference in New Issue
Block a user