Introduce option 'captcha' for mod_block_strangers
When the option is set to `true`, the module will generate CAPTCHA challenges for incoming subscription requests. The option also implies that option `drop` is set to `true`. Note that the module won't generate CAPTCHA challenges for messages: they will still be rejected if `drop` is set to `true`. Fixes #2246
This commit is contained in:
parent
7e561dd20a
commit
4b012a99d2
|
@ -643,8 +643,8 @@ route_probe_reply(_, _) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
-spec process_presence_out(state(), presence()) -> state().
|
-spec process_presence_out(state(), presence()) -> state().
|
||||||
process_presence_out(#{user := User, server := Server, lserver := LServer,
|
process_presence_out(#{lserver := LServer, jid := JID,
|
||||||
jid := JID, lang := Lang, pres_a := PresA} = State,
|
lang := Lang, pres_a := PresA} = State,
|
||||||
#presence{from = From, to = To, type = Type} = Pres) ->
|
#presence{from = From, to = To, type = Type} = Pres) ->
|
||||||
if Type == subscribe; Type == subscribed;
|
if Type == subscribe; Type == subscribed;
|
||||||
Type == unsubscribe; Type == unsubscribed ->
|
Type == unsubscribe; Type == unsubscribed ->
|
||||||
|
@ -656,9 +656,7 @@ process_presence_out(#{user := User, server := Server, lserver := LServer,
|
||||||
AccessErr = xmpp:err_forbidden(AccessErrTxt, Lang),
|
AccessErr = xmpp:err_forbidden(AccessErrTxt, Lang),
|
||||||
send_error(State, Pres, AccessErr);
|
send_error(State, Pres, AccessErr);
|
||||||
allow ->
|
allow ->
|
||||||
ejabberd_hooks:run(roster_out_subscription,
|
ejabberd_hooks:run(roster_out_subscription, LServer, [Pres])
|
||||||
LServer,
|
|
||||||
[User, Server, To, Type])
|
|
||||||
end;
|
end;
|
||||||
true -> ok
|
true -> ok
|
||||||
end,
|
end,
|
||||||
|
@ -839,9 +837,9 @@ route_multiple(#{lserver := LServer}, JIDs, Pkt) ->
|
||||||
ejabberd_router_multicast:route_multicast(From, LServer, JIDs, Pkt).
|
ejabberd_router_multicast:route_multicast(From, LServer, JIDs, Pkt).
|
||||||
|
|
||||||
get_subscription(#jid{luser = LUser, lserver = LServer}, JID) ->
|
get_subscription(#jid{luser = LUser, lserver = LServer}, JID) ->
|
||||||
{Subscription, _} = ejabberd_hooks:run_fold(
|
{Subscription, _, _} = ejabberd_hooks:run_fold(
|
||||||
roster_get_jid_info, LServer, {none, []},
|
roster_get_jid_info, LServer, {none, none, []},
|
||||||
[LUser, LServer, JID]),
|
[LUser, LServer, JID]),
|
||||||
Subscription.
|
Subscription.
|
||||||
|
|
||||||
-spec resource_conflict_action(binary(), binary(), binary()) ->
|
-spec resource_conflict_action(binary(), binary(), binary()) ->
|
||||||
|
|
|
@ -41,7 +41,8 @@
|
||||||
-export([create_captcha/6, build_captcha_html/2,
|
-export([create_captcha/6, build_captcha_html/2,
|
||||||
check_captcha/2, process_reply/1, process/2,
|
check_captcha/2, process_reply/1, process/2,
|
||||||
is_feature_available/0, create_captcha_x/5,
|
is_feature_available/0, create_captcha_x/5,
|
||||||
opt_type/1]).
|
opt_type/1, host_up/1, host_down/1,
|
||||||
|
config_reloaded/0, process_iq/1]).
|
||||||
|
|
||||||
-include("xmpp.hrl").
|
-include("xmpp.hrl").
|
||||||
-include("ejabberd.hrl").
|
-include("ejabberd.hrl").
|
||||||
|
@ -53,7 +54,8 @@
|
||||||
|
|
||||||
-type image_error() :: efbig | enodata | limit | malformed_image | timeout.
|
-type image_error() :: efbig | enodata | limit | malformed_image | timeout.
|
||||||
|
|
||||||
-record(state, {limits = treap:empty() :: treap:treap()}).
|
-record(state, {limits = treap:empty() :: treap:treap(),
|
||||||
|
enabled = false :: boolean()}).
|
||||||
|
|
||||||
-record(captcha, {id :: binary(),
|
-record(captcha, {id :: binary(),
|
||||||
pid :: pid() | undefined,
|
pid :: pid() | undefined,
|
||||||
|
@ -214,6 +216,25 @@ process_reply(#xcaptcha{xdata = #xdata{} = X}) ->
|
||||||
process_reply(_) ->
|
process_reply(_) ->
|
||||||
{error, malformed}.
|
{error, malformed}.
|
||||||
|
|
||||||
|
process_iq(#iq{type = set, lang = Lang, sub_els = [#xcaptcha{} = El]} = IQ) ->
|
||||||
|
case process_reply(El) of
|
||||||
|
ok ->
|
||||||
|
xmpp:make_iq_result(IQ);
|
||||||
|
{error, malformed} ->
|
||||||
|
Txt = <<"Incorrect CAPTCHA submit">>,
|
||||||
|
xmpp:make_error(IQ, xmpp:err_bad_request(Txt, Lang));
|
||||||
|
{error, _} ->
|
||||||
|
Txt = <<"The CAPTCHA verification has failed">>,
|
||||||
|
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang))
|
||||||
|
end;
|
||||||
|
process_iq(#iq{type = get, lang = Lang} = IQ) ->
|
||||||
|
Txt = <<"Value 'get' of 'type' attribute is not allowed">>,
|
||||||
|
xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
|
||||||
|
process_iq(#iq{lang = Lang} = IQ) ->
|
||||||
|
?INFO_MSG("IQ = ~p", [IQ]),
|
||||||
|
Txt = <<"No module is handling this query">>,
|
||||||
|
xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
|
||||||
|
|
||||||
process(_Handlers,
|
process(_Handlers,
|
||||||
#request{method = 'GET', lang = Lang,
|
#request{method = 'GET', lang = Lang,
|
||||||
path = [_, Id]}) ->
|
path = [_, Id]}) ->
|
||||||
|
@ -261,12 +282,30 @@ process(_Handlers,
|
||||||
process(_Handlers, _Request) ->
|
process(_Handlers, _Request) ->
|
||||||
ejabberd_web:error(not_found).
|
ejabberd_web:error(not_found).
|
||||||
|
|
||||||
|
host_up(Host) ->
|
||||||
|
IQDisc = gen_iq_handler:iqdisc(Host),
|
||||||
|
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_CAPTCHA,
|
||||||
|
?MODULE, process_iq, IQDisc).
|
||||||
|
|
||||||
|
host_down(Host) ->
|
||||||
|
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_CAPTCHA).
|
||||||
|
|
||||||
|
config_reloaded() ->
|
||||||
|
gen_server:call(?MODULE, config_reloaded, timer:minutes(1)).
|
||||||
|
|
||||||
init([]) ->
|
init([]) ->
|
||||||
mnesia:delete_table(captcha),
|
mnesia:delete_table(captcha),
|
||||||
ets:new(captcha,
|
ets:new(captcha, [named_table, public, {keypos, #captcha.id}]),
|
||||||
[named_table, public, {keypos, #captcha.id}]),
|
case check_captcha_setup() of
|
||||||
check_captcha_setup(),
|
true ->
|
||||||
{ok, #state{}}.
|
register_handlers(),
|
||||||
|
ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 50),
|
||||||
|
{ok, #state{enabled = true}};
|
||||||
|
false ->
|
||||||
|
{ok, #state{enabled = false}};
|
||||||
|
{error, Reason} ->
|
||||||
|
{stop, Reason}
|
||||||
|
end.
|
||||||
|
|
||||||
handle_call({is_limited, Limiter, RateLimit}, _From,
|
handle_call({is_limited, Limiter, RateLimit}, _From,
|
||||||
State) ->
|
State) ->
|
||||||
|
@ -285,6 +324,23 @@ handle_call({is_limited, Limiter, RateLimit}, _From,
|
||||||
Limits),
|
Limits),
|
||||||
{reply, false, State#state{limits = NewLimits}}
|
{reply, false, State#state{limits = NewLimits}}
|
||||||
end;
|
end;
|
||||||
|
handle_call(config_reloaded, _From, #state{enabled = Enabled} = State) ->
|
||||||
|
State1 = case is_feature_available() of
|
||||||
|
true when not Enabled ->
|
||||||
|
case check_captcha_setup() of
|
||||||
|
true ->
|
||||||
|
register_handlers(),
|
||||||
|
State#state{enabled = true};
|
||||||
|
_ ->
|
||||||
|
State
|
||||||
|
end;
|
||||||
|
false when Enabled ->
|
||||||
|
unregister_handlers(),
|
||||||
|
State#state{enabled = false};
|
||||||
|
_ ->
|
||||||
|
State
|
||||||
|
end,
|
||||||
|
{reply, ok, State1};
|
||||||
handle_call(_Request, _From, State) ->
|
handle_call(_Request, _From, State) ->
|
||||||
{reply, bad_request, State}.
|
{reply, bad_request, State}.
|
||||||
|
|
||||||
|
@ -293,17 +349,29 @@ handle_cast(_Msg, State) -> {noreply, State}.
|
||||||
handle_info({remove_id, Id}, State) ->
|
handle_info({remove_id, Id}, State) ->
|
||||||
?DEBUG("captcha ~p timed out", [Id]),
|
?DEBUG("captcha ~p timed out", [Id]),
|
||||||
case ets:lookup(captcha, Id) of
|
case ets:lookup(captcha, Id) of
|
||||||
[#captcha{args = Args, pid = Pid}] ->
|
[#captcha{args = Args, pid = Pid}] ->
|
||||||
if is_pid(Pid) -> Pid ! {captcha_failed, Args};
|
callback(captcha_failed, Pid, Args),
|
||||||
true -> ok
|
ets:delete(captcha, Id);
|
||||||
end,
|
_ -> ok
|
||||||
ets:delete(captcha, Id);
|
|
||||||
_ -> ok
|
|
||||||
end,
|
end,
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
handle_info(_Info, State) -> {noreply, State}.
|
handle_info(_Info, State) -> {noreply, State}.
|
||||||
|
|
||||||
terminate(_Reason, _State) -> ok.
|
terminate(_Reason, #state{enabled = Enabled}) ->
|
||||||
|
if Enabled -> unregister_handlers();
|
||||||
|
true -> ok
|
||||||
|
end,
|
||||||
|
ejabberd_hooks:delete(config_reloaded, ?MODULE, config_reloaded, 50).
|
||||||
|
|
||||||
|
register_handlers() ->
|
||||||
|
ejabberd_hooks:add(host_up, ?MODULE, host_up, 50),
|
||||||
|
ejabberd_hooks:add(host_down, ?MODULE, host_down, 50),
|
||||||
|
lists:foreach(fun host_up/1, ?MYHOSTS).
|
||||||
|
|
||||||
|
unregister_handlers() ->
|
||||||
|
ejabberd_hooks:delete(host_up, ?MODULE, host_up, 50),
|
||||||
|
ejabberd_hooks:delete(host_down, ?MODULE, host_down, 50),
|
||||||
|
lists:foreach(fun host_down/1, ?MYHOSTS).
|
||||||
|
|
||||||
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||||
|
|
||||||
|
@ -467,16 +535,18 @@ is_feature_available() ->
|
||||||
|
|
||||||
check_captcha_setup() ->
|
check_captcha_setup() ->
|
||||||
case is_feature_available() of
|
case is_feature_available() of
|
||||||
true ->
|
true ->
|
||||||
case create_image() of
|
case create_image() of
|
||||||
{ok, _, _, _} -> ok;
|
{ok, _, _, _} ->
|
||||||
_Err ->
|
true;
|
||||||
?CRITICAL_MSG("Captcha is enabled in the option captcha_cmd, "
|
Err ->
|
||||||
"but it can't generate images.",
|
?CRITICAL_MSG("Captcha is enabled in the option captcha_cmd, "
|
||||||
[]),
|
"but it can't generate images.",
|
||||||
throw({error, captcha_cmd_enabled_but_fails})
|
[]),
|
||||||
end;
|
Err
|
||||||
false -> ok
|
end;
|
||||||
|
false ->
|
||||||
|
false
|
||||||
end.
|
end.
|
||||||
|
|
||||||
lookup_captcha(Id) ->
|
lookup_captcha(Id) ->
|
||||||
|
@ -491,22 +561,18 @@ lookup_captcha(Id) ->
|
||||||
|
|
||||||
check_captcha(Id, ProvidedKey) ->
|
check_captcha(Id, ProvidedKey) ->
|
||||||
case ets:lookup(captcha, Id) of
|
case ets:lookup(captcha, Id) of
|
||||||
[#captcha{pid = Pid, args = Args, key = ValidKey,
|
[#captcha{pid = Pid, args = Args, key = ValidKey, tref = Tref}] ->
|
||||||
tref = Tref}] ->
|
ets:delete(captcha, Id),
|
||||||
ets:delete(captcha, Id),
|
erlang:cancel_timer(Tref),
|
||||||
erlang:cancel_timer(Tref),
|
if ValidKey == ProvidedKey ->
|
||||||
if ValidKey == ProvidedKey ->
|
callback(captcha_succeed, Pid, Args),
|
||||||
if is_pid(Pid) -> Pid ! {captcha_succeed, Args};
|
captcha_valid;
|
||||||
true -> ok
|
true ->
|
||||||
end,
|
callback(captcha_failed, Pid, Args),
|
||||||
captcha_valid;
|
captcha_non_valid
|
||||||
true ->
|
end;
|
||||||
if is_pid(Pid) -> Pid ! {captcha_failed, Args};
|
_ ->
|
||||||
true -> ok
|
captcha_not_found
|
||||||
end,
|
|
||||||
captcha_non_valid
|
|
||||||
end;
|
|
||||||
_ -> captcha_not_found
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
clean_treap(Treap, CleanPriority) ->
|
clean_treap(Treap, CleanPriority) ->
|
||||||
|
@ -520,6 +586,14 @@ clean_treap(Treap, CleanPriority) ->
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
-spec callback(captcha_succeed | captcha_failed, pid(), term()) -> any().
|
||||||
|
callback(Result, _Pid, F) when is_function(F) ->
|
||||||
|
F(Result);
|
||||||
|
callback(Result, Pid, Args) when is_pid(Pid) ->
|
||||||
|
Pid ! {Result, Args};
|
||||||
|
callback(_, _, _) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
now_priority() ->
|
now_priority() ->
|
||||||
-p1_time_compat:system_time(micro_seconds).
|
-p1_time_compat:system_time(micro_seconds).
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
open_session/5,
|
open_session/5,
|
||||||
open_session/6,
|
open_session/6,
|
||||||
close_session/4,
|
close_session/4,
|
||||||
check_in_subscription/6,
|
check_in_subscription/2,
|
||||||
bounce_offline_message/1,
|
bounce_offline_message/1,
|
||||||
disconnect_removed_user/2,
|
disconnect_removed_user/2,
|
||||||
get_user_resources/2,
|
get_user_resources/2,
|
||||||
|
@ -183,10 +183,9 @@ close_session(SID, User, Server, Resource) ->
|
||||||
ejabberd_hooks:run(sm_remove_connection_hook,
|
ejabberd_hooks:run(sm_remove_connection_hook,
|
||||||
JID#jid.lserver, [SID, JID, Info]).
|
JID#jid.lserver, [SID, JID, Info]).
|
||||||
|
|
||||||
-spec check_in_subscription(boolean(), binary(), binary(), jid(),
|
-spec check_in_subscription(boolean(), presence()) -> boolean() | {stop, false}.
|
||||||
subscribe | subscribed | unsubscribe | unsubscribed,
|
check_in_subscription(Acc, #presence{to = To}) ->
|
||||||
binary()) -> boolean() | {stop, false}.
|
#jid{user = User, server = Server} = To,
|
||||||
check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
|
|
||||||
case ejabberd_auth:user_exists(User, Server) of
|
case ejabberd_auth:user_exists(User, Server) of
|
||||||
true -> Acc;
|
true -> Acc;
|
||||||
false -> {stop, false}
|
false -> {stop, false}
|
||||||
|
@ -627,19 +626,14 @@ do_route(To, Term) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec do_route(stanza()) -> any().
|
-spec do_route(stanza()) -> any().
|
||||||
do_route(#presence{from = From, to = To, type = T, status = Status} = Packet)
|
do_route(#presence{to = To, type = T} = Packet)
|
||||||
when T == subscribe; T == subscribed; T == unsubscribe; T == unsubscribed ->
|
when T == subscribe; T == subscribed; T == unsubscribe; T == unsubscribed ->
|
||||||
?DEBUG("processing subscription:~n~s", [xmpp:pp(Packet)]),
|
?DEBUG("processing subscription:~n~s", [xmpp:pp(Packet)]),
|
||||||
#jid{user = User, server = Server,
|
#jid{luser = LUser, lserver = LServer} = To,
|
||||||
luser = LUser, lserver = LServer} = To,
|
|
||||||
Reason = if T == subscribe -> xmpp:get_text(Status);
|
|
||||||
true -> <<"">>
|
|
||||||
end,
|
|
||||||
case is_privacy_allow(Packet) andalso
|
case is_privacy_allow(Packet) andalso
|
||||||
ejabberd_hooks:run_fold(
|
ejabberd_hooks:run_fold(
|
||||||
roster_in_subscription,
|
roster_in_subscription,
|
||||||
LServer, false,
|
LServer, false, [Packet]) of
|
||||||
[User, Server, From, T, Reason]) of
|
|
||||||
true ->
|
true ->
|
||||||
Mod = get_sm_backend(LServer),
|
Mod = get_sm_backend(LServer),
|
||||||
lists:foreach(
|
lists:foreach(
|
||||||
|
|
|
@ -166,7 +166,6 @@ init([]) ->
|
||||||
ACME,
|
ACME,
|
||||||
Listener,
|
Listener,
|
||||||
S2S,
|
S2S,
|
||||||
Captcha,
|
|
||||||
S2SInSupervisor,
|
S2SInSupervisor,
|
||||||
S2SOutSupervisor,
|
S2SOutSupervisor,
|
||||||
ServiceSupervisor,
|
ServiceSupervisor,
|
||||||
|
@ -182,6 +181,7 @@ init([]) ->
|
||||||
RouterMulticast,
|
RouterMulticast,
|
||||||
Local,
|
Local,
|
||||||
SM,
|
SM,
|
||||||
|
Captcha,
|
||||||
ExtMod,
|
ExtMod,
|
||||||
GenModSupervisor,
|
GenModSupervisor,
|
||||||
Auth,
|
Auth,
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
-export([start/2, stop/1, reload/3,
|
-export([start/2, stop/1, reload/3,
|
||||||
depends/2, mod_opt_type/1, mod_options/1]).
|
depends/2, mod_opt_type/1, mod_options/1]).
|
||||||
|
|
||||||
-export([filter_packet/1, filter_offline_msg/1]).
|
-export([filter_packet/1, filter_offline_msg/1, filter_subscription/2]).
|
||||||
|
|
||||||
-include("xmpp.hrl").
|
-include("xmpp.hrl").
|
||||||
-include("ejabberd.hrl").
|
-include("ejabberd.hrl").
|
||||||
|
@ -40,15 +40,22 @@
|
||||||
|
|
||||||
-define(SETS, gb_sets).
|
-define(SETS, gb_sets).
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% Callbacks and hooks
|
||||||
|
%%%===================================================================
|
||||||
start(Host, _Opts) ->
|
start(Host, _Opts) ->
|
||||||
ejabberd_hooks:add(user_receive_packet, Host,
|
ejabberd_hooks:add(user_receive_packet, Host,
|
||||||
?MODULE, filter_packet, 25),
|
?MODULE, filter_packet, 25),
|
||||||
|
ejabberd_hooks:add(roster_in_subscription, Host,
|
||||||
|
?MODULE, filter_subscription, 25),
|
||||||
ejabberd_hooks:add(offline_message_hook, Host,
|
ejabberd_hooks:add(offline_message_hook, Host,
|
||||||
?MODULE, filter_offline_msg, 25).
|
?MODULE, filter_offline_msg, 25).
|
||||||
|
|
||||||
stop(Host) ->
|
stop(Host) ->
|
||||||
ejabberd_hooks:delete(user_receive_packet, Host,
|
ejabberd_hooks:delete(user_receive_packet, Host,
|
||||||
?MODULE, filter_packet, 25),
|
?MODULE, filter_packet, 25),
|
||||||
|
ejabberd_hooks:delete(roster_in_subscription, Host,
|
||||||
|
?MODULE, filter_subscription, 25),
|
||||||
ejabberd_hooks:delete(offline_message_hook, Host,
|
ejabberd_hooks:delete(offline_message_hook, Host,
|
||||||
?MODULE, filter_offline_msg, 25).
|
?MODULE, filter_offline_msg, 25).
|
||||||
|
|
||||||
|
@ -79,17 +86,72 @@ filter_offline_msg({_Action, #message{} = Msg} = Acc) ->
|
||||||
deny -> {stop, {drop, Msg}}
|
deny -> {stop, {drop, Msg}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
filter_subscription(Acc, #presence{meta = #{captcha := passed}}) ->
|
||||||
|
Acc;
|
||||||
|
filter_subscription(Acc, #presence{from = From, to = To, lang = Lang,
|
||||||
|
id = SID, type = subscribe} = Pres) ->
|
||||||
|
LServer = To#jid.lserver,
|
||||||
|
case gen_mod:get_module_opt(LServer, ?MODULE, drop) andalso
|
||||||
|
gen_mod:get_module_opt(LServer, ?MODULE, captcha) andalso
|
||||||
|
need_check(Pres) of
|
||||||
|
true ->
|
||||||
|
case check_subscription(From, To) of
|
||||||
|
false ->
|
||||||
|
BFrom = jid:remove_resource(From),
|
||||||
|
BTo = jid:remove_resource(To),
|
||||||
|
Limiter = jid:tolower(BFrom),
|
||||||
|
case ejabberd_captcha:create_captcha(
|
||||||
|
SID, BFrom, BTo, Lang, Limiter,
|
||||||
|
fun(Res) -> handle_captcha_result(Res, Pres) end) of
|
||||||
|
{ok, ID, Body, CaptchaEls} ->
|
||||||
|
Msg = #message{from = BTo, to = From,
|
||||||
|
id = ID, body = Body,
|
||||||
|
sub_els = CaptchaEls},
|
||||||
|
case gen_mod:get_module_opt(LServer, ?MODULE, log) of
|
||||||
|
true ->
|
||||||
|
?INFO_MSG("Challenge subscription request "
|
||||||
|
"from stranger ~s to ~s with "
|
||||||
|
"CAPTCHA",
|
||||||
|
[jid:encode(From), jid:encode(To)]);
|
||||||
|
false ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
|
ejabberd_router:route(Msg);
|
||||||
|
{error, limit} ->
|
||||||
|
ErrText = <<"Too many CAPTCHA requests">>,
|
||||||
|
Err = xmpp:err_resource_constraint(ErrText, Lang),
|
||||||
|
ejabberd_router:route_error(Pres, Err);
|
||||||
|
_ ->
|
||||||
|
ErrText = <<"Unable to generate a CAPTCHA">>,
|
||||||
|
Err = xmpp:err_internal_server_error(ErrText, Lang),
|
||||||
|
ejabberd_router:route_error(Pres, Err)
|
||||||
|
end,
|
||||||
|
{stop, false};
|
||||||
|
true ->
|
||||||
|
Acc
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
Acc
|
||||||
|
end;
|
||||||
|
filter_subscription(Acc, _) ->
|
||||||
|
Acc.
|
||||||
|
|
||||||
|
handle_captcha_result(captcha_succeed, Pres) ->
|
||||||
|
Pres1 = xmpp:put_meta(Pres, captcha, passed),
|
||||||
|
ejabberd_router:route(Pres1);
|
||||||
|
handle_captcha_result(captcha_failed, #presence{lang = Lang} = Pres) ->
|
||||||
|
Txt = <<"The CAPTCHA verification has failed">>,
|
||||||
|
ejabberd_router:route_error(Pres, xmpp:err_not_allowed(Txt, Lang)).
|
||||||
|
|
||||||
|
%%%===================================================================
|
||||||
|
%%% Internal functions
|
||||||
|
%%%===================================================================
|
||||||
check_message(#message{from = From, to = To, lang = Lang} = Msg) ->
|
check_message(#message{from = From, to = To, lang = Lang} = Msg) ->
|
||||||
LServer = To#jid.lserver,
|
LServer = To#jid.lserver,
|
||||||
AllowLocalUsers =
|
case need_check(Msg) of
|
||||||
gen_mod:get_module_opt(LServer, ?MODULE, allow_local_users),
|
true ->
|
||||||
case (Msg#message.body == [] andalso
|
|
||||||
Msg#message.subject == [])
|
|
||||||
orelse ((AllowLocalUsers orelse From#jid.luser == <<"">>) andalso
|
|
||||||
ejabberd_router:is_my_host(From#jid.lserver)) of
|
|
||||||
false ->
|
|
||||||
case check_subscription(From, To) of
|
case check_subscription(From, To) of
|
||||||
none ->
|
false ->
|
||||||
Drop = gen_mod:get_module_opt(LServer, ?MODULE, drop),
|
Drop = gen_mod:get_module_opt(LServer, ?MODULE, drop),
|
||||||
Log = gen_mod:get_module_opt(LServer, ?MODULE, log),
|
Log = gen_mod:get_module_opt(LServer, ?MODULE, log),
|
||||||
if
|
if
|
||||||
|
@ -112,10 +174,10 @@ check_message(#message{from = From, to = To, lang = Lang} = Msg) ->
|
||||||
true ->
|
true ->
|
||||||
allow
|
allow
|
||||||
end;
|
end;
|
||||||
some ->
|
true ->
|
||||||
allow
|
allow
|
||||||
end;
|
end;
|
||||||
true ->
|
false ->
|
||||||
allow
|
allow
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -125,34 +187,46 @@ maybe_adjust_from(#message{type = groupchat, from = From} = Msg) ->
|
||||||
maybe_adjust_from(#message{} = Msg) ->
|
maybe_adjust_from(#message{} = Msg) ->
|
||||||
Msg.
|
Msg.
|
||||||
|
|
||||||
-spec check_subscription(jid(), jid()) -> none | some.
|
-spec need_check(presence() | message()) -> boolean().
|
||||||
|
need_check(Pkt) ->
|
||||||
|
To = xmpp:get_to(Pkt),
|
||||||
|
From = xmpp:get_from(Pkt),
|
||||||
|
LServer = To#jid.lserver,
|
||||||
|
IsEmpty = case Pkt of
|
||||||
|
#message{body = [], subject = []} ->
|
||||||
|
true;
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end,
|
||||||
|
AllowLocalUsers = gen_mod:get_module_opt(LServer, ?MODULE, allow_local_users),
|
||||||
|
not (IsEmpty orelse ((AllowLocalUsers orelse From#jid.luser == <<"">>)
|
||||||
|
andalso ejabberd_router:is_my_host(From#jid.lserver))).
|
||||||
|
|
||||||
|
-spec check_subscription(jid(), jid()) -> boolean().
|
||||||
check_subscription(From, To) ->
|
check_subscription(From, To) ->
|
||||||
{LocalUser, LocalServer, _} = jid:tolower(To),
|
{LocalUser, LocalServer, _} = jid:tolower(To),
|
||||||
{RemoteUser, RemoteServer, _} = jid:tolower(From),
|
{RemoteUser, RemoteServer, _} = jid:tolower(From),
|
||||||
case ejabberd_hooks:run_fold(
|
case has_subscription(LocalUser, LocalServer, From) of
|
||||||
roster_get_jid_info, LocalServer,
|
false when RemoteUser == <<"">> ->
|
||||||
{none, []}, [LocalUser, LocalServer, From]) of
|
false;
|
||||||
{none, _} when RemoteUser == <<"">> ->
|
false ->
|
||||||
none;
|
%% Check if the contact's server is in the roster
|
||||||
{none, _} ->
|
gen_mod:get_module_opt(LocalServer, ?MODULE, allow_transports)
|
||||||
case gen_mod:get_module_opt(LocalServer, ?MODULE,
|
andalso has_subscription(LocalUser, LocalServer,
|
||||||
allow_transports) of
|
jid:make(RemoteServer));
|
||||||
true ->
|
true ->
|
||||||
%% Check if the contact's server is in the roster
|
true
|
||||||
case ejabberd_hooks:run_fold(
|
|
||||||
roster_get_jid_info, LocalServer,
|
|
||||||
{none, []},
|
|
||||||
[LocalUser, LocalServer, jid:make(RemoteServer)]) of
|
|
||||||
{none, _} -> none;
|
|
||||||
_ -> some
|
|
||||||
end;
|
|
||||||
false ->
|
|
||||||
none
|
|
||||||
end;
|
|
||||||
_ ->
|
|
||||||
some
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
-spec has_subscription(binary(), binary(), jid()) -> boolean().
|
||||||
|
has_subscription(User, Server, JID) ->
|
||||||
|
{Sub, Ask, _} = ejabberd_hooks:run_fold(
|
||||||
|
roster_get_jid_info, Server,
|
||||||
|
{none, none, []},
|
||||||
|
[User, Server, JID]),
|
||||||
|
(Sub /= none) orelse (Ask == subscribe)
|
||||||
|
orelse (Ask == out) orelse (Ask == both).
|
||||||
|
|
||||||
sets_bare_member({U, S, <<"">>} = LBJID, Set) ->
|
sets_bare_member({U, S, <<"">>} = LBJID, Set) ->
|
||||||
case ?SETS:next(sets_iterator_from(LBJID, Set)) of
|
case ?SETS:next(sets_iterator_from(LBJID, Set)) of
|
||||||
{{U, S, _}, _} -> true;
|
{{U, S, _}, _} -> true;
|
||||||
|
@ -189,10 +263,13 @@ mod_opt_type(log) ->
|
||||||
mod_opt_type(allow_local_users) ->
|
mod_opt_type(allow_local_users) ->
|
||||||
fun (B) when is_boolean(B) -> B end;
|
fun (B) when is_boolean(B) -> B end;
|
||||||
mod_opt_type(allow_transports) ->
|
mod_opt_type(allow_transports) ->
|
||||||
|
fun (B) when is_boolean(B) -> B end;
|
||||||
|
mod_opt_type(captcha) ->
|
||||||
fun (B) when is_boolean(B) -> B end.
|
fun (B) when is_boolean(B) -> B end.
|
||||||
|
|
||||||
mod_options(_) ->
|
mod_options(_) ->
|
||||||
[{drop, true},
|
[{drop, true},
|
||||||
{log, false},
|
{log, false},
|
||||||
|
{captcha, false},
|
||||||
{allow_local_users, true},
|
{allow_local_users, true},
|
||||||
{allow_transports, true}].
|
{allow_transports, true}].
|
||||||
|
|
|
@ -203,9 +203,10 @@ disco_info(Acc, _, _, _Node, _Lang) ->
|
||||||
-spec c2s_presence_in(ejabberd_c2s:state(), presence()) -> ejabberd_c2s:state().
|
-spec c2s_presence_in(ejabberd_c2s:state(), presence()) -> ejabberd_c2s:state().
|
||||||
c2s_presence_in(C2SState,
|
c2s_presence_in(C2SState,
|
||||||
#presence{from = From, to = To, type = Type} = Presence) ->
|
#presence{from = From, to = To, type = Type} = Presence) ->
|
||||||
{Subscription, _} = ejabberd_hooks:run_fold(
|
{Subscription, _, _} = ejabberd_hooks:run_fold(
|
||||||
roster_get_jid_info, To#jid.lserver,
|
roster_get_jid_info, To#jid.lserver,
|
||||||
{none, []}, [To#jid.luser, To#jid.lserver, From]),
|
{none, none, []},
|
||||||
|
[To#jid.luser, To#jid.lserver, From]),
|
||||||
ToSelf = (From#jid.luser == To#jid.luser)
|
ToSelf = (From#jid.luser == To#jid.luser)
|
||||||
and (From#jid.lserver == To#jid.lserver),
|
and (From#jid.lserver == To#jid.lserver),
|
||||||
Insert = (Type == available)
|
Insert = (Type == available)
|
||||||
|
|
|
@ -142,9 +142,9 @@ process_sm_iq(#iq{type = set, lang = Lang} = IQ) ->
|
||||||
process_sm_iq(#iq{from = From, to = To, lang = Lang} = IQ) ->
|
process_sm_iq(#iq{from = From, to = To, lang = Lang} = IQ) ->
|
||||||
User = To#jid.luser,
|
User = To#jid.luser,
|
||||||
Server = To#jid.lserver,
|
Server = To#jid.lserver,
|
||||||
{Subscription, _Groups} =
|
{Subscription, _Ask, _Groups} =
|
||||||
ejabberd_hooks:run_fold(roster_get_jid_info, Server,
|
ejabberd_hooks:run_fold(roster_get_jid_info, Server,
|
||||||
{none, []}, [User, Server, From]),
|
{none, none, []}, [User, Server, From]),
|
||||||
if (Subscription == both) or (Subscription == from) or
|
if (Subscription == both) or (Subscription == from) or
|
||||||
(From#jid.luser == To#jid.luser) and
|
(From#jid.luser == To#jid.luser) and
|
||||||
(From#jid.lserver == To#jid.lserver) ->
|
(From#jid.lserver == To#jid.lserver) ->
|
||||||
|
|
|
@ -672,10 +672,10 @@ should_archive_peer(LUser, LServer,
|
||||||
always -> true;
|
always -> true;
|
||||||
never -> false;
|
never -> false;
|
||||||
roster ->
|
roster ->
|
||||||
{Sub, _} = ejabberd_hooks:run_fold(
|
{Sub, _, _} = ejabberd_hooks:run_fold(
|
||||||
roster_get_jid_info,
|
roster_get_jid_info,
|
||||||
LServer, {none, []},
|
LServer, {none, none, []},
|
||||||
[LUser, LServer, Peer]),
|
[LUser, LServer, Peer]),
|
||||||
Sub == both orelse Sub == from orelse Sub == to
|
Sub == both orelse Sub == from orelse Sub == to
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -589,9 +589,10 @@ do_check_packet(#jid{luser = LUser, lserver = LServer}, List, Packet, Dir) ->
|
||||||
in -> jid:tolower(From);
|
in -> jid:tolower(From);
|
||||||
out -> jid:tolower(To)
|
out -> jid:tolower(To)
|
||||||
end,
|
end,
|
||||||
{Subscription, Groups} = ejabberd_hooks:run_fold(
|
{Subscription, _Ask, Groups} = ejabberd_hooks:run_fold(
|
||||||
roster_get_jid_info, LServer,
|
roster_get_jid_info, LServer,
|
||||||
{none, []}, [LUser, LServer, LJID]),
|
{none, none, []},
|
||||||
|
[LUser, LServer, LJID]),
|
||||||
check_packet_aux(List, PType2, LJID, Subscription, Groups)
|
check_packet_aux(List, PType2, LJID, Subscription, Groups)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
|
|
||||||
%% exports for hooks
|
%% exports for hooks
|
||||||
-export([presence_probe/3, caps_add/3, caps_update/3,
|
-export([presence_probe/3, caps_add/3, caps_update/3,
|
||||||
in_subscription/6, out_subscription/4,
|
in_subscription/2, out_subscription/1,
|
||||||
on_self_presence/1, on_user_offline/2, remove_user/2,
|
on_self_presence/1, on_user_offline/2, remove_user/2,
|
||||||
disco_local_identity/5, disco_local_features/5,
|
disco_local_identity/5, disco_local_features/5,
|
||||||
disco_local_items/5, disco_sm_identity/5,
|
disco_local_items/5, disco_sm_identity/5,
|
||||||
|
@ -605,22 +605,17 @@ on_user_offline(C2SState, _Reason) ->
|
||||||
%% subscription hooks handling functions
|
%% subscription hooks handling functions
|
||||||
%%
|
%%
|
||||||
|
|
||||||
-spec out_subscription(
|
-spec out_subscription(presence()) -> any().
|
||||||
binary(), binary(), jid(),
|
out_subscription(#presence{type = subscribed, from = From, to = To}) ->
|
||||||
subscribed | unsubscribed | subscribe | unsubscribe) -> boolean().
|
send_last_pep(jid:remove_resource(From), To);
|
||||||
out_subscription(User, Server, To, subscribed) ->
|
out_subscription(_) ->
|
||||||
send_last_pep(jid:make(User, Server), To),
|
ok.
|
||||||
true;
|
|
||||||
out_subscription(_, _, _, _) ->
|
|
||||||
true.
|
|
||||||
|
|
||||||
-spec in_subscription(boolean(), binary(), binary(), jid(),
|
-spec in_subscription(boolean(), presence()) -> true.
|
||||||
subscribe | subscribed | unsubscribe | unsubscribed,
|
in_subscription(_, #presence{to = To, from = Owner, type = unsubscribed}) ->
|
||||||
binary()) -> true.
|
unsubscribe_user(jid:remove_resource(To), Owner),
|
||||||
in_subscription(_, User, Server, Owner, unsubscribed, _) ->
|
|
||||||
unsubscribe_user(jid:make(User, Server), Owner),
|
|
||||||
true;
|
true;
|
||||||
in_subscription(_, _, _, _, _, _) ->
|
in_subscription(_, _) ->
|
||||||
true.
|
true.
|
||||||
|
|
||||||
unsubscribe_user(Entity, Owner) ->
|
unsubscribe_user(Entity, Owner) ->
|
||||||
|
@ -2513,8 +2508,8 @@ get_roster_info(_, _, {<<>>, <<>>, _}, _) ->
|
||||||
{false, false};
|
{false, false};
|
||||||
get_roster_info(OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, _}, AllowedGroups) ->
|
get_roster_info(OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, _}, AllowedGroups) ->
|
||||||
LJID = {SubscriberUser, SubscriberServer, <<>>},
|
LJID = {SubscriberUser, SubscriberServer, <<>>},
|
||||||
{Subscription, Groups} = ejabberd_hooks:run_fold(roster_get_jid_info,
|
{Subscription, _Ask, Groups} = ejabberd_hooks:run_fold(roster_get_jid_info,
|
||||||
OwnerServer, {none, []},
|
OwnerServer, {none, none, []},
|
||||||
[OwnerUser, OwnerServer, LJID]),
|
[OwnerUser, OwnerServer, LJID]),
|
||||||
PresenceSubscription = Subscription == both orelse
|
PresenceSubscription = Subscription == both orelse
|
||||||
Subscription == from orelse
|
Subscription == from orelse
|
||||||
|
|
|
@ -44,8 +44,8 @@
|
||||||
import_info/0, process_local_iq/1, get_user_roster/2,
|
import_info/0, process_local_iq/1, get_user_roster/2,
|
||||||
import/5, get_roster/2,
|
import/5, get_roster/2,
|
||||||
import_start/2, import_stop/2,
|
import_start/2, import_stop/2,
|
||||||
c2s_self_presence/1, in_subscription/6,
|
c2s_self_presence/1, in_subscription/2,
|
||||||
out_subscription/4, set_items/3, remove_user/2,
|
out_subscription/1, set_items/3, remove_user/2,
|
||||||
get_jid_info/4, encode_item/1, webadmin_page/3,
|
get_jid_info/4, encode_item/1, webadmin_page/3,
|
||||||
webadmin_user/4, get_versioning_feature/2,
|
webadmin_user/4, get_versioning_feature/2,
|
||||||
roster_versioning_enabled/1, roster_version/2,
|
roster_versioning_enabled/1, roster_version/2,
|
||||||
|
@ -76,7 +76,7 @@
|
||||||
-callback get_roster(binary(), binary()) -> {ok, [#roster{}]} | error.
|
-callback get_roster(binary(), binary()) -> {ok, [#roster{}]} | error.
|
||||||
-callback get_roster_item(binary(), binary(), ljid()) -> {ok, #roster{}} | error.
|
-callback get_roster_item(binary(), binary(), ljid()) -> {ok, #roster{}} | error.
|
||||||
-callback read_subscription_and_groups(binary(), binary(), ljid())
|
-callback read_subscription_and_groups(binary(), binary(), ljid())
|
||||||
-> {ok, {subscription(), [binary()]}} | error.
|
-> {ok, {subscription(), ask(), [binary()]}} | error.
|
||||||
-callback roster_subscribe(binary(), binary(), ljid(), #roster{}) -> any().
|
-callback roster_subscribe(binary(), binary(), ljid(), #roster{}) -> any().
|
||||||
-callback transaction(binary(), function()) -> {atomic, any()} | {aborted, any()}.
|
-callback transaction(binary(), function()) -> {atomic, any()} | {aborted, any()}.
|
||||||
-callback remove_user(binary(), binary()) -> any().
|
-callback remove_user(binary(), binary()) -> any().
|
||||||
|
@ -383,20 +383,28 @@ get_subscription_and_groups(LUser, LServer, LJID) ->
|
||||||
fun() ->
|
fun() ->
|
||||||
Items = get_roster(LUser, LServer),
|
Items = get_roster(LUser, LServer),
|
||||||
case lists:keyfind(LBJID, #roster.jid, Items) of
|
case lists:keyfind(LBJID, #roster.jid, Items) of
|
||||||
#roster{subscription = Sub, groups = Groups} ->
|
#roster{subscription = Sub,
|
||||||
{ok, {Sub, Groups}};
|
ask = Ask,
|
||||||
|
groups = Groups} ->
|
||||||
|
{ok, {Sub, Ask, Groups}};
|
||||||
false ->
|
false ->
|
||||||
error
|
error
|
||||||
end
|
end
|
||||||
end);
|
end);
|
||||||
false ->
|
false ->
|
||||||
Mod:read_subscription_and_groups(LUser, LServer, LBJID)
|
case Mod:read_subscription_and_groups(LUser, LServer, LBJID) of
|
||||||
|
{ok, {Sub, Groups}} ->
|
||||||
|
%% Backward compatibility for third-party backends
|
||||||
|
{ok, {Sub, none, Groups}};
|
||||||
|
Other ->
|
||||||
|
Other
|
||||||
|
end
|
||||||
end,
|
end,
|
||||||
case Res of
|
case Res of
|
||||||
{ok, SubAndGroups} ->
|
{ok, SubAndGroups} ->
|
||||||
SubAndGroups;
|
SubAndGroups;
|
||||||
error ->
|
error ->
|
||||||
{none, []}
|
{none, none, []}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
set_roster(#roster{us = {LUser, LServer}, jid = LJID} = Item) ->
|
set_roster(#roster{us = {LUser, LServer}, jid = LJID} = Item) ->
|
||||||
|
@ -555,17 +563,19 @@ transaction(LUser, LServer, LJIDs, F) ->
|
||||||
Err
|
Err
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec in_subscription(boolean(), binary(), binary(), jid(),
|
-spec in_subscription(boolean(), presence()) -> boolean().
|
||||||
subscribe | subscribed | unsubscribe | unsubscribed,
|
in_subscription(_, #presence{from = JID, to = To,
|
||||||
binary()) -> boolean().
|
type = Type, status = Status}) ->
|
||||||
in_subscription(_, User, Server, JID, Type, Reason) ->
|
#jid{user = User, server = Server} = To,
|
||||||
|
Reason = if Type == subscribe -> xmpp:get_text(Status);
|
||||||
|
true -> <<"">>
|
||||||
|
end,
|
||||||
process_subscription(in, User, Server, JID, Type,
|
process_subscription(in, User, Server, JID, Type,
|
||||||
Reason).
|
Reason).
|
||||||
|
|
||||||
-spec out_subscription(
|
-spec out_subscription(presence()) -> boolean().
|
||||||
binary(), binary(), jid(),
|
out_subscription(#presence{from = From, to = JID, type = Type}) ->
|
||||||
subscribed | unsubscribed | subscribe | unsubscribe) -> boolean().
|
#jid{user = User, server = Server} = From,
|
||||||
out_subscription(User, Server, JID, Type) ->
|
|
||||||
process_subscription(out, User, Server, JID, Type, <<"">>).
|
process_subscription(out, User, Server, JID, Type, <<"">>).
|
||||||
|
|
||||||
process_subscription(Direction, User, Server, JID1,
|
process_subscription(Direction, User, Server, JID1,
|
||||||
|
@ -878,8 +888,8 @@ get_priority_from_presence(#presence{priority = Prio}) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
-spec get_jid_info({subscription(), [binary()]}, binary(), binary(), jid())
|
-spec get_jid_info({subscription(), ask(), [binary()]}, binary(), binary(), jid())
|
||||||
-> {subscription(), [binary()]}.
|
-> {subscription(), ask(), [binary()]}.
|
||||||
get_jid_info(_, User, Server, JID) ->
|
get_jid_info(_, User, Server, JID) ->
|
||||||
LUser = jid:nodeprep(User),
|
LUser = jid:nodeprep(User),
|
||||||
LServer = jid:nameprep(Server),
|
LServer = jid:nameprep(Server),
|
||||||
|
@ -1029,9 +1039,10 @@ user_roster_parse_query(User, Server, Items, Query) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
user_roster_subscribe_jid(User, Server, JID) ->
|
user_roster_subscribe_jid(User, Server, JID) ->
|
||||||
out_subscription(User, Server, JID, subscribe),
|
|
||||||
UJID = jid:make(User, Server),
|
UJID = jid:make(User, Server),
|
||||||
ejabberd_router:route(#presence{from = UJID, to = JID, type = subscribe}).
|
Presence = #presence{from = UJID, to = JID, type = subscribe},
|
||||||
|
out_subscription(Presence),
|
||||||
|
ejabberd_router:route(Presence).
|
||||||
|
|
||||||
user_roster_item_parse_query(User, Server, Items,
|
user_roster_item_parse_query(User, Server, Items,
|
||||||
Query) ->
|
Query) ->
|
||||||
|
@ -1043,12 +1054,11 @@ user_roster_item_parse_query(User, Server, Items,
|
||||||
of
|
of
|
||||||
{value, _} ->
|
{value, _} ->
|
||||||
JID1 = jid:make(JID),
|
JID1 = jid:make(JID),
|
||||||
out_subscription(User, Server, JID1,
|
|
||||||
subscribed),
|
|
||||||
UJID = jid:make(User, Server),
|
UJID = jid:make(User, Server),
|
||||||
ejabberd_router:route(
|
Pres = #presence{from = UJID, to = JID1,
|
||||||
#presence{from = UJID, to = JID1,
|
type = subscribed},
|
||||||
type = subscribed}),
|
out_subscription(Pres),
|
||||||
|
ejabberd_router:route(Pres),
|
||||||
throw(submitted);
|
throw(submitted);
|
||||||
false ->
|
false ->
|
||||||
case lists:keysearch(<<"remove",
|
case lists:keysearch(<<"remove",
|
||||||
|
|
|
@ -102,8 +102,8 @@ del_roster(LUser, LServer, LJID) ->
|
||||||
|
|
||||||
read_subscription_and_groups(LUser, LServer, LJID) ->
|
read_subscription_and_groups(LUser, LServer, LJID) ->
|
||||||
case mnesia:dirty_read(roster, {LUser, LServer, LJID}) of
|
case mnesia:dirty_read(roster, {LUser, LServer, LJID}) of
|
||||||
[#roster{subscription = Subscription, groups = Groups}] ->
|
[#roster{subscription = Subscription, ask = Ask, groups = Groups}] ->
|
||||||
{ok, {Subscription, Groups}};
|
{ok, {Subscription, Ask, Groups}};
|
||||||
_ ->
|
_ ->
|
||||||
error
|
error
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -88,8 +88,9 @@ del_roster(LUser, LServer, LJID) ->
|
||||||
read_subscription_and_groups(LUser, LServer, LJID) ->
|
read_subscription_and_groups(LUser, LServer, LJID) ->
|
||||||
case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
|
case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
|
||||||
{ok, #roster{subscription = Subscription,
|
{ok, #roster{subscription = Subscription,
|
||||||
|
ask = Ask,
|
||||||
groups = Groups}} ->
|
groups = Groups}} ->
|
||||||
{ok, {Subscription, Groups}};
|
{ok, {Subscription, Ask, Groups}};
|
||||||
_ ->
|
_ ->
|
||||||
error
|
error
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -171,25 +171,15 @@ del_roster(LUser, LServer, LJID) ->
|
||||||
read_subscription_and_groups(LUser, LServer, LJID) ->
|
read_subscription_and_groups(LUser, LServer, LJID) ->
|
||||||
SJID = jid:encode(LJID),
|
SJID = jid:encode(LJID),
|
||||||
case get_subscription(LServer, LUser, SJID) of
|
case get_subscription(LServer, LUser, SJID) of
|
||||||
{selected, [{SSubscription}]} ->
|
{selected, [{SSubscription, SAsk}]} ->
|
||||||
Subscription = case SSubscription of
|
Subscription = decode_subscription(LUser, LServer, SSubscription),
|
||||||
<<"B">> -> both;
|
Ask = decode_ask(LUser, LServer, SAsk),
|
||||||
<<"T">> -> to;
|
|
||||||
<<"F">> -> from;
|
|
||||||
<<"N">> -> none;
|
|
||||||
<<"">> -> none;
|
|
||||||
_ ->
|
|
||||||
?ERROR_MSG("~s", [format_row_error(
|
|
||||||
LUser, LServer,
|
|
||||||
{subscription, SSubscription})]),
|
|
||||||
none
|
|
||||||
end,
|
|
||||||
Groups = case get_rostergroup_by_jid(LServer, LUser, SJID) of
|
Groups = case get_rostergroup_by_jid(LServer, LUser, SJID) of
|
||||||
{selected, JGrps} when is_list(JGrps) ->
|
{selected, JGrps} when is_list(JGrps) ->
|
||||||
[JGrp || {JGrp} <- JGrps];
|
[JGrp || {JGrp} <- JGrps];
|
||||||
_ -> []
|
_ -> []
|
||||||
end,
|
end,
|
||||||
{ok, {Subscription, Groups}};
|
{ok, {Subscription, Ask, Groups}};
|
||||||
_ ->
|
_ ->
|
||||||
error
|
error
|
||||||
end.
|
end.
|
||||||
|
@ -272,7 +262,7 @@ get_rostergroup_by_jid(LServer, LUser, SJID) ->
|
||||||
get_subscription(LServer, LUser, SJID) ->
|
get_subscription(LServer, LUser, SJID) ->
|
||||||
ejabberd_sql:sql_query(
|
ejabberd_sql:sql_query(
|
||||||
LServer,
|
LServer,
|
||||||
?SQL("select @(subscription)s from rosterusers "
|
?SQL("select @(subscription)s, @(ask)s from rosterusers "
|
||||||
"where username=%(LUser)s and %(LServer)H and jid=%(SJID)s")).
|
"where username=%(LUser)s and %(LServer)H and jid=%(SJID)s")).
|
||||||
|
|
||||||
update_roster_sql({LUser, LServer, SJID, Name, SSubscription, SAsk, AskMessage},
|
update_roster_sql({LUser, LServer, SJID, Name, SSubscription, SAsk, AskMessage},
|
||||||
|
@ -320,30 +310,8 @@ raw_to_record(LServer,
|
||||||
try jid:decode(SJID) of
|
try jid:decode(SJID) of
|
||||||
JID ->
|
JID ->
|
||||||
LJID = jid:tolower(JID),
|
LJID = jid:tolower(JID),
|
||||||
Subscription = case SSubscription of
|
Subscription = decode_subscription(User, LServer, SSubscription),
|
||||||
<<"B">> -> both;
|
Ask = decode_ask(User, LServer, SAsk),
|
||||||
<<"T">> -> to;
|
|
||||||
<<"F">> -> from;
|
|
||||||
<<"N">> -> none;
|
|
||||||
<<"">> -> none;
|
|
||||||
_ ->
|
|
||||||
?ERROR_MSG("~s", [format_row_error(
|
|
||||||
User, LServer,
|
|
||||||
{subscription, SSubscription})]),
|
|
||||||
none
|
|
||||||
end,
|
|
||||||
Ask = case SAsk of
|
|
||||||
<<"S">> -> subscribe;
|
|
||||||
<<"U">> -> unsubscribe;
|
|
||||||
<<"B">> -> both;
|
|
||||||
<<"O">> -> out;
|
|
||||||
<<"I">> -> in;
|
|
||||||
<<"N">> -> none;
|
|
||||||
<<"">> -> none;
|
|
||||||
_ ->
|
|
||||||
?ERROR_MSG("~s", [format_row_error(User, LServer, {ask, SAsk})]),
|
|
||||||
none
|
|
||||||
end,
|
|
||||||
#roster{usj = {User, LServer, LJID},
|
#roster{usj = {User, LServer, LJID},
|
||||||
us = {User, LServer}, jid = LJID, name = Nick,
|
us = {User, LServer}, jid = LJID, name = Nick,
|
||||||
subscription = Subscription, ask = Ask,
|
subscription = Subscription, ask = Ask,
|
||||||
|
@ -374,6 +342,32 @@ record_to_row(
|
||||||
end,
|
end,
|
||||||
{LUser, LServer, SJID, Name, SSubscription, SAsk, AskMessage}.
|
{LUser, LServer, SJID, Name, SSubscription, SAsk, AskMessage}.
|
||||||
|
|
||||||
|
decode_subscription(User, Server, S) ->
|
||||||
|
case S of
|
||||||
|
<<"B">> -> both;
|
||||||
|
<<"T">> -> to;
|
||||||
|
<<"F">> -> from;
|
||||||
|
<<"N">> -> none;
|
||||||
|
<<"">> -> none;
|
||||||
|
_ ->
|
||||||
|
?ERROR_MSG("~s", [format_row_error(User, Server, {subscription, S})]),
|
||||||
|
none
|
||||||
|
end.
|
||||||
|
|
||||||
|
decode_ask(User, Server, A) ->
|
||||||
|
case A of
|
||||||
|
<<"S">> -> subscribe;
|
||||||
|
<<"U">> -> unsubscribe;
|
||||||
|
<<"B">> -> both;
|
||||||
|
<<"O">> -> out;
|
||||||
|
<<"I">> -> in;
|
||||||
|
<<"N">> -> none;
|
||||||
|
<<"">> -> none;
|
||||||
|
_ ->
|
||||||
|
?ERROR_MSG("~s", [format_row_error(User, Server, {ask, A})]),
|
||||||
|
none
|
||||||
|
end.
|
||||||
|
|
||||||
format_row_error(User, Server, Why) ->
|
format_row_error(User, Server, Why) ->
|
||||||
[case Why of
|
[case Why of
|
||||||
{jid, JID} -> ["Malformed 'jid' field with value '", JID, "'"];
|
{jid, JID} -> ["Malformed 'jid' field with value '", JID, "'"];
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
import_info/0, webadmin_menu/3, webadmin_page/3,
|
import_info/0, webadmin_menu/3, webadmin_page/3,
|
||||||
get_user_roster/2,
|
get_user_roster/2,
|
||||||
get_jid_info/4, import/5, process_item/2, import_start/2,
|
get_jid_info/4, import/5, process_item/2, import_start/2,
|
||||||
in_subscription/6, out_subscription/4, c2s_self_presence/1,
|
in_subscription/2, out_subscription/1, c2s_self_presence/1,
|
||||||
unset_presence/4, register_user/2, remove_user/2,
|
unset_presence/4, register_user/2, remove_user/2,
|
||||||
list_groups/1, create_group/2, create_group/3,
|
list_groups/1, create_group/2, create_group/3,
|
||||||
delete_group/2, get_group_opts/2, set_group_opts/3,
|
delete_group/2, get_group_opts/2, set_group_opts/3,
|
||||||
|
@ -235,12 +235,11 @@ process_item(RosterItem, Host) ->
|
||||||
%% If it doesn't, then remove this user from any
|
%% If it doesn't, then remove this user from any
|
||||||
%% existing roster groups.
|
%% existing roster groups.
|
||||||
[] ->
|
[] ->
|
||||||
mod_roster:out_subscription(UserTo, ServerTo,
|
Pres = #presence{from = jid:make(UserTo, ServerTo),
|
||||||
jid:make(UserFrom, ServerFrom),
|
to = jid:make(UserFrom, ServerFrom),
|
||||||
unsubscribe),
|
type = unsubscribe},
|
||||||
mod_roster:in_subscription(false, UserFrom, ServerFrom,
|
mod_roster:out_subscription(Pres),
|
||||||
jid:make(UserTo, ServerTo),
|
mod_roster:in_subscription(false, Pres),
|
||||||
unsubscribe, <<"">>),
|
|
||||||
RosterItem#roster{subscription = both, ask = none};
|
RosterItem#roster{subscription = both, ask = none};
|
||||||
%% If so, it means the user wants to add that contact
|
%% If so, it means the user wants to add that contact
|
||||||
%% to his personal roster
|
%% to his personal roster
|
||||||
|
@ -268,22 +267,22 @@ set_new_rosteritems(UserFrom, ServerFrom, UserTo,
|
||||||
RITo = build_roster_record(UserTo, ServerTo, UserFrom,
|
RITo = build_roster_record(UserTo, ServerTo, UserFrom,
|
||||||
ServerFrom, UserFrom, []),
|
ServerFrom, UserFrom, []),
|
||||||
set_item(UserTo, ServerTo, <<"">>, RITo),
|
set_item(UserTo, ServerTo, <<"">>, RITo),
|
||||||
mod_roster:out_subscription(UserFrom, ServerFrom, JIDTo,
|
mod_roster:out_subscription(
|
||||||
subscribe),
|
#presence{from = JIDFrom, to = JIDTo, type = subscribe}),
|
||||||
mod_roster:in_subscription(false, UserTo, ServerTo,
|
mod_roster:in_subscription(
|
||||||
JIDFrom, subscribe, <<"">>),
|
false, #presence{to = JIDTo, from = JIDFrom, type = subscribe}),
|
||||||
mod_roster:out_subscription(UserTo, ServerTo, JIDFrom,
|
mod_roster:out_subscription(
|
||||||
subscribed),
|
#presence{from = JIDTo, to = JIDFrom, type = subscribed}),
|
||||||
mod_roster:in_subscription(false, UserFrom, ServerFrom,
|
mod_roster:in_subscription(
|
||||||
JIDTo, subscribed, <<"">>),
|
false, #presence{to = JIDFrom, from = JIDTo, type = subscribed}),
|
||||||
mod_roster:out_subscription(UserTo, ServerTo, JIDFrom,
|
mod_roster:out_subscription(
|
||||||
subscribe),
|
#presence{from = JIDTo, to = JIDFrom, type = subscribe}),
|
||||||
mod_roster:in_subscription(false, UserFrom, ServerFrom,
|
mod_roster:in_subscription(
|
||||||
JIDTo, subscribe, <<"">>),
|
false, #presence{to = JIDFrom, from = JIDTo, type = subscribe}),
|
||||||
mod_roster:out_subscription(UserFrom, ServerFrom, JIDTo,
|
mod_roster:out_subscription(
|
||||||
subscribed),
|
#presence{from = JIDFrom, to = JIDTo, type = subscribed}),
|
||||||
mod_roster:in_subscription(false, UserTo, ServerTo,
|
mod_roster:in_subscription(
|
||||||
JIDFrom, subscribed, <<"">>),
|
false, #presence{to = JIDTo, from = JIDFrom, type = subscribed}),
|
||||||
RIFrom.
|
RIFrom.
|
||||||
|
|
||||||
set_item(User, Server, Resource, Item) ->
|
set_item(User, Server, Resource, Item) ->
|
||||||
|
@ -294,9 +293,9 @@ set_item(User, Server, Resource, Item) ->
|
||||||
items = [mod_roster:encode_item(Item)]}]},
|
items = [mod_roster:encode_item(Item)]}]},
|
||||||
ejabberd_router:route(ResIQ).
|
ejabberd_router:route(ResIQ).
|
||||||
|
|
||||||
-spec get_jid_info({subscription(), [binary()]}, binary(), binary(), jid())
|
-spec get_jid_info({subscription(), ask(), [binary()]}, binary(), binary(), jid())
|
||||||
-> {subscription(), [binary()]}.
|
-> {subscription(), ask(), [binary()]}.
|
||||||
get_jid_info({Subscription, Groups}, User, Server,
|
get_jid_info({Subscription, Ask, Groups}, User, Server,
|
||||||
JID) ->
|
JID) ->
|
||||||
LUser = jid:nodeprep(User),
|
LUser = jid:nodeprep(User),
|
||||||
LServer = jid:nameprep(Server),
|
LServer = jid:nameprep(Server),
|
||||||
|
@ -320,33 +319,26 @@ get_jid_info({Subscription, Groups}, User, Server,
|
||||||
NewGroups = if Groups == [] -> GroupNames;
|
NewGroups = if Groups == [] -> GroupNames;
|
||||||
true -> Groups
|
true -> Groups
|
||||||
end,
|
end,
|
||||||
{both, NewGroups};
|
{both, none, NewGroups};
|
||||||
error -> {Subscription, Groups}
|
error -> {Subscription, Ask, Groups}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec in_subscription(boolean(), binary(), binary(), jid(),
|
-spec in_subscription(boolean(), presence()) -> boolean().
|
||||||
subscribe | subscribed | unsubscribe | unsubscribed,
|
in_subscription(Acc, #presence{to = To, from = JID, type = Type}) ->
|
||||||
binary()) -> boolean().
|
#jid{user = User, server = Server} = To,
|
||||||
in_subscription(Acc, User, Server, JID, Type,
|
|
||||||
_Reason) ->
|
|
||||||
process_subscription(in, User, Server, JID, Type, Acc).
|
process_subscription(in, User, Server, JID, Type, Acc).
|
||||||
|
|
||||||
-spec out_subscription(
|
-spec out_subscription(presence()) -> boolean().
|
||||||
binary(), binary(), jid(),
|
out_subscription(#presence{from = From, to = To, type = unsubscribed} = Pres) ->
|
||||||
subscribed | unsubscribed | subscribe | unsubscribe) -> boolean().
|
#jid{user = User, server = Server} = From,
|
||||||
out_subscription(UserFrom, ServerFrom, JIDTo,
|
mod_roster:out_subscription(Pres#presence{type = unsubscribe}),
|
||||||
unsubscribed) ->
|
mod_roster:in_subscription(false, xmpp:set_from_to(
|
||||||
#jid{luser = UserTo, lserver = ServerTo} = JIDTo,
|
Pres#presence{type = unsubscribe},
|
||||||
JIDFrom = jid:make(UserFrom, ServerFrom),
|
To, From)),
|
||||||
mod_roster:out_subscription(UserTo, ServerTo, JIDFrom,
|
process_subscription(out, User, Server, To, unsubscribed, false);
|
||||||
unsubscribe),
|
out_subscription(#presence{from = From, to = To, type = Type}) ->
|
||||||
mod_roster:in_subscription(false, UserFrom, ServerFrom,
|
#jid{user = User, server = Server} = From,
|
||||||
JIDTo, unsubscribe, <<"">>),
|
process_subscription(out, User, Server, To, Type, false).
|
||||||
process_subscription(out, UserFrom, ServerFrom, JIDTo,
|
|
||||||
unsubscribed, false);
|
|
||||||
out_subscription(User, Server, JID, Type) ->
|
|
||||||
process_subscription(out, User, Server, JID, Type,
|
|
||||||
false).
|
|
||||||
|
|
||||||
process_subscription(Direction, User, Server, JID,
|
process_subscription(Direction, User, Server, JID,
|
||||||
_Type, Acc) ->
|
_Type, Acc) ->
|
||||||
|
|
|
@ -40,8 +40,8 @@
|
||||||
handle_info/2, terminate/2, code_change/3]).
|
handle_info/2, terminate/2, code_change/3]).
|
||||||
|
|
||||||
-export([get_user_roster/2,
|
-export([get_user_roster/2,
|
||||||
get_jid_info/4, process_item/2, in_subscription/6,
|
get_jid_info/4, process_item/2, in_subscription/2,
|
||||||
out_subscription/4, mod_opt_type/1, mod_options/1,
|
out_subscription/1, mod_opt_type/1, mod_options/1,
|
||||||
opt_type/1, depends/2, transform_module_options/1]).
|
opt_type/1, depends/2, transform_module_options/1]).
|
||||||
|
|
||||||
-include("ejabberd.hrl").
|
-include("ejabberd.hrl").
|
||||||
|
@ -159,9 +159,9 @@ process_item(RosterItem, _Host) ->
|
||||||
_ -> RosterItem#roster{subscription = both, ask = none}
|
_ -> RosterItem#roster{subscription = both, ask = none}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec get_jid_info({subscription(), [binary()]}, binary(), binary(), jid())
|
-spec get_jid_info({subscription(), ask(), [binary()]}, binary(), binary(), jid())
|
||||||
-> {subscription(), [binary()]}.
|
-> {subscription(), ask(), [binary()]}.
|
||||||
get_jid_info({Subscription, Groups}, User, Server,
|
get_jid_info({Subscription, Ask, Groups}, User, Server,
|
||||||
JID) ->
|
JID) ->
|
||||||
LUser = jid:nodeprep(User),
|
LUser = jid:nodeprep(User),
|
||||||
LServer = jid:nameprep(Server),
|
LServer = jid:nameprep(Server),
|
||||||
|
@ -174,23 +174,19 @@ get_jid_info({Subscription, Groups}, User, Server,
|
||||||
NewGroups = if Groups == [] -> GroupNames;
|
NewGroups = if Groups == [] -> GroupNames;
|
||||||
true -> Groups
|
true -> Groups
|
||||||
end,
|
end,
|
||||||
{both, NewGroups};
|
{both, none, NewGroups};
|
||||||
error -> {Subscription, Groups}
|
error -> {Subscription, Ask, Groups}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec in_subscription(boolean(), binary(), binary(), jid(),
|
-spec in_subscription(boolean(), presence()) -> boolean().
|
||||||
subscribe | subscribed | unsubscribe | unsubscribed,
|
in_subscription(Acc, #presence{to = To, from = JID, type = Type}) ->
|
||||||
binary()) -> boolean().
|
#jid{user = User, server = Server} = To,
|
||||||
in_subscription(Acc, User, Server, JID, Type,
|
|
||||||
_Reason) ->
|
|
||||||
process_subscription(in, User, Server, JID, Type, Acc).
|
process_subscription(in, User, Server, JID, Type, Acc).
|
||||||
|
|
||||||
-spec out_subscription(
|
-spec out_subscription(presence()) -> boolean().
|
||||||
binary(), binary(), jid(),
|
out_subscription(#presence{from = From, to = JID, type = Type}) ->
|
||||||
subscribed | unsubscribed | subscribe | unsubscribe) -> boolean().
|
#jid{user = User, server = Server} = From,
|
||||||
out_subscription(User, Server, JID, Type) ->
|
process_subscription(out, User, Server, JID, Type, false).
|
||||||
process_subscription(out, User, Server, JID, Type,
|
|
||||||
false).
|
|
||||||
|
|
||||||
process_subscription(Direction, User, Server, JID,
|
process_subscription(Direction, User, Server, JID,
|
||||||
_Type, Acc) ->
|
_Type, Acc) ->
|
||||||
|
|
Loading…
Reference in New Issue