aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ejabberd_c2s.erl14
-rw-r--r--src/ejabberd_captcha.erl152
-rw-r--r--src/ejabberd_sm.erl20
-rw-r--r--src/ejabberd_sup.erl2
-rw-r--r--src/mod_block_strangers.erl145
-rw-r--r--src/mod_caps.erl7
-rw-r--r--src/mod_last.erl4
-rw-r--r--src/mod_mam.erl8
-rw-r--r--src/mod_privacy.erl7
-rw-r--r--src/mod_pubsub.erl29
-rw-r--r--src/mod_roster.erl58
-rw-r--r--src/mod_roster_mnesia.erl4
-rw-r--r--src/mod_roster_riak.erl3
-rw-r--r--src/mod_roster_sql.erl72
-rw-r--r--src/mod_shared_roster.erl90
-rw-r--r--src/mod_shared_roster_ldap.erl32
16 files changed, 390 insertions, 257 deletions
diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl
index 6e7ba1e67..06d4cf12d 100644
--- a/src/ejabberd_c2s.erl
+++ b/src/ejabberd_c2s.erl
@@ -643,8 +643,8 @@ route_probe_reply(_, _) ->
ok.
-spec process_presence_out(state(), presence()) -> state().
-process_presence_out(#{user := User, server := Server, lserver := LServer,
- jid := JID, lang := Lang, pres_a := PresA} = State,
+process_presence_out(#{lserver := LServer, jid := JID,
+ lang := Lang, pres_a := PresA} = State,
#presence{from = From, to = To, type = Type} = Pres) ->
if Type == subscribe; Type == subscribed;
Type == unsubscribe; Type == unsubscribed ->
@@ -656,9 +656,7 @@ process_presence_out(#{user := User, server := Server, lserver := LServer,
AccessErr = xmpp:err_forbidden(AccessErrTxt, Lang),
send_error(State, Pres, AccessErr);
allow ->
- ejabberd_hooks:run(roster_out_subscription,
- LServer,
- [User, Server, To, Type])
+ ejabberd_hooks:run(roster_out_subscription, LServer, [Pres])
end;
true -> ok
end,
@@ -839,9 +837,9 @@ route_multiple(#{lserver := LServer}, JIDs, Pkt) ->
ejabberd_router_multicast:route_multicast(From, LServer, JIDs, Pkt).
get_subscription(#jid{luser = LUser, lserver = LServer}, JID) ->
- {Subscription, _} = ejabberd_hooks:run_fold(
- roster_get_jid_info, LServer, {none, []},
- [LUser, LServer, JID]),
+ {Subscription, _, _} = ejabberd_hooks:run_fold(
+ roster_get_jid_info, LServer, {none, none, []},
+ [LUser, LServer, JID]),
Subscription.
-spec resource_conflict_action(binary(), binary(), binary()) ->
diff --git a/src/ejabberd_captcha.erl b/src/ejabberd_captcha.erl
index e1f070380..3fad361fd 100644
--- a/src/ejabberd_captcha.erl
+++ b/src/ejabberd_captcha.erl
@@ -41,7 +41,8 @@
-export([create_captcha/6, build_captcha_html/2,
check_captcha/2, process_reply/1, process/2,
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("ejabberd.hrl").
@@ -53,7 +54,8 @@
-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(),
pid :: pid() | undefined,
@@ -214,6 +216,25 @@ process_reply(#xcaptcha{xdata = #xdata{} = X}) ->
process_reply(_) ->
{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,
#request{method = 'GET', lang = Lang,
path = [_, Id]}) ->
@@ -261,12 +282,30 @@ process(_Handlers,
process(_Handlers, _Request) ->
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([]) ->
mnesia:delete_table(captcha),
- ets:new(captcha,
- [named_table, public, {keypos, #captcha.id}]),
- check_captcha_setup(),
- {ok, #state{}}.
+ ets:new(captcha, [named_table, public, {keypos, #captcha.id}]),
+ case check_captcha_setup() of
+ true ->
+ 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,
State) ->
@@ -285,6 +324,23 @@ handle_call({is_limited, Limiter, RateLimit}, _From,
Limits),
{reply, false, State#state{limits = NewLimits}}
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) ->
{reply, bad_request, State}.
@@ -293,17 +349,29 @@ handle_cast(_Msg, State) -> {noreply, State}.
handle_info({remove_id, Id}, State) ->
?DEBUG("captcha ~p timed out", [Id]),
case ets:lookup(captcha, Id) of
- [#captcha{args = Args, pid = Pid}] ->
- if is_pid(Pid) -> Pid ! {captcha_failed, Args};
- true -> ok
- end,
- ets:delete(captcha, Id);
- _ -> ok
+ [#captcha{args = Args, pid = Pid}] ->
+ callback(captcha_failed, Pid, Args),
+ ets:delete(captcha, Id);
+ _ -> ok
end,
{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}.
@@ -467,16 +535,18 @@ is_feature_available() ->
check_captcha_setup() ->
case is_feature_available() of
- true ->
- case create_image() of
- {ok, _, _, _} -> ok;
- _Err ->
- ?CRITICAL_MSG("Captcha is enabled in the option captcha_cmd, "
- "but it can't generate images.",
- []),
- throw({error, captcha_cmd_enabled_but_fails})
- end;
- false -> ok
+ true ->
+ case create_image() of
+ {ok, _, _, _} ->
+ true;
+ Err ->
+ ?CRITICAL_MSG("Captcha is enabled in the option captcha_cmd, "
+ "but it can't generate images.",
+ []),
+ Err
+ end;
+ false ->
+ false
end.
lookup_captcha(Id) ->
@@ -491,22 +561,18 @@ lookup_captcha(Id) ->
check_captcha(Id, ProvidedKey) ->
case ets:lookup(captcha, Id) of
- [#captcha{pid = Pid, args = Args, key = ValidKey,
- tref = Tref}] ->
- ets:delete(captcha, Id),
- erlang:cancel_timer(Tref),
- if ValidKey == ProvidedKey ->
- if is_pid(Pid) -> Pid ! {captcha_succeed, Args};
- true -> ok
- end,
- captcha_valid;
- true ->
- if is_pid(Pid) -> Pid ! {captcha_failed, Args};
- true -> ok
- end,
- captcha_non_valid
- end;
- _ -> captcha_not_found
+ [#captcha{pid = Pid, args = Args, key = ValidKey, tref = Tref}] ->
+ ets:delete(captcha, Id),
+ erlang:cancel_timer(Tref),
+ if ValidKey == ProvidedKey ->
+ callback(captcha_succeed, Pid, Args),
+ captcha_valid;
+ true ->
+ callback(captcha_failed, Pid, Args),
+ captcha_non_valid
+ end;
+ _ ->
+ captcha_not_found
end.
clean_treap(Treap, CleanPriority) ->
@@ -520,6 +586,14 @@ clean_treap(Treap, CleanPriority) ->
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() ->
-p1_time_compat:system_time(micro_seconds).
diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl
index 64c9e5f77..54f248f5e 100644
--- a/src/ejabberd_sm.erl
+++ b/src/ejabberd_sm.erl
@@ -43,7 +43,7 @@
open_session/5,
open_session/6,
close_session/4,
- check_in_subscription/6,
+ check_in_subscription/2,
bounce_offline_message/1,
disconnect_removed_user/2,
get_user_resources/2,
@@ -183,10 +183,9 @@ close_session(SID, User, Server, Resource) ->
ejabberd_hooks:run(sm_remove_connection_hook,
JID#jid.lserver, [SID, JID, Info]).
--spec check_in_subscription(boolean(), binary(), binary(), jid(),
- subscribe | subscribed | unsubscribe | unsubscribed,
- binary()) -> boolean() | {stop, false}.
-check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
+-spec check_in_subscription(boolean(), presence()) -> boolean() | {stop, false}.
+check_in_subscription(Acc, #presence{to = To}) ->
+ #jid{user = User, server = Server} = To,
case ejabberd_auth:user_exists(User, Server) of
true -> Acc;
false -> {stop, false}
@@ -627,19 +626,14 @@ do_route(To, Term) ->
end.
-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 ->
?DEBUG("processing subscription:~n~s", [xmpp:pp(Packet)]),
- #jid{user = User, server = Server,
- luser = LUser, lserver = LServer} = To,
- Reason = if T == subscribe -> xmpp:get_text(Status);
- true -> <<"">>
- end,
+ #jid{luser = LUser, lserver = LServer} = To,
case is_privacy_allow(Packet) andalso
ejabberd_hooks:run_fold(
roster_in_subscription,
- LServer, false,
- [User, Server, From, T, Reason]) of
+ LServer, false, [Packet]) of
true ->
Mod = get_sm_backend(LServer),
lists:foreach(
diff --git a/src/ejabberd_sup.erl b/src/ejabberd_sup.erl
index cea0c279b..5980937fe 100644
--- a/src/ejabberd_sup.erl
+++ b/src/ejabberd_sup.erl
@@ -166,7 +166,6 @@ init([]) ->
ACME,
Listener,
S2S,
- Captcha,
S2SInSupervisor,
S2SOutSupervisor,
ServiceSupervisor,
@@ -182,6 +181,7 @@ init([]) ->
RouterMulticast,
Local,
SM,
+ Captcha,
ExtMod,
GenModSupervisor,
Auth,
diff --git a/src/mod_block_strangers.erl b/src/mod_block_strangers.erl
index e865754e2..bd933c7a1 100644
--- a/src/mod_block_strangers.erl
+++ b/src/mod_block_strangers.erl
@@ -32,7 +32,7 @@
-export([start/2, stop/1, reload/3,
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("ejabberd.hrl").
@@ -40,15 +40,22 @@
-define(SETS, gb_sets).
+%%%===================================================================
+%%% Callbacks and hooks
+%%%===================================================================
start(Host, _Opts) ->
ejabberd_hooks:add(user_receive_packet, Host,
?MODULE, filter_packet, 25),
+ ejabberd_hooks:add(roster_in_subscription, Host,
+ ?MODULE, filter_subscription, 25),
ejabberd_hooks:add(offline_message_hook, Host,
?MODULE, filter_offline_msg, 25).
stop(Host) ->
ejabberd_hooks:delete(user_receive_packet, Host,
?MODULE, filter_packet, 25),
+ ejabberd_hooks:delete(roster_in_subscription, Host,
+ ?MODULE, filter_subscription, 25),
ejabberd_hooks:delete(offline_message_hook, Host,
?MODULE, filter_offline_msg, 25).
@@ -79,17 +86,72 @@ filter_offline_msg({_Action, #message{} = Msg} = Acc) ->
deny -> {stop, {drop, Msg}}
end.
-check_message(#message{from = From, to = To, lang = Lang} = Msg) ->
+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,
- AllowLocalUsers =
- gen_mod:get_module_opt(LServer, ?MODULE, allow_local_users),
- case (Msg#message.body == [] andalso
- Msg#message.subject == [])
- orelse ((AllowLocalUsers orelse From#jid.luser == <<"">>) andalso
- ejabberd_router:is_my_host(From#jid.lserver)) of
+ 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) ->
+ LServer = To#jid.lserver,
+ case need_check(Msg) of
+ true ->
case check_subscription(From, To) of
- none ->
+ false ->
Drop = gen_mod:get_module_opt(LServer, ?MODULE, drop),
Log = gen_mod:get_module_opt(LServer, ?MODULE, log),
if
@@ -112,10 +174,10 @@ check_message(#message{from = From, to = To, lang = Lang} = Msg) ->
true ->
allow
end;
- some ->
+ true ->
allow
end;
- true ->
+ false ->
allow
end.
@@ -125,34 +187,46 @@ maybe_adjust_from(#message{type = groupchat, from = From} = Msg) ->
maybe_adjust_from(#message{} = 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) ->
{LocalUser, LocalServer, _} = jid:tolower(To),
{RemoteUser, RemoteServer, _} = jid:tolower(From),
- case ejabberd_hooks:run_fold(
- roster_get_jid_info, LocalServer,
- {none, []}, [LocalUser, LocalServer, From]) of
- {none, _} when RemoteUser == <<"">> ->
- none;
- {none, _} ->
- case gen_mod:get_module_opt(LocalServer, ?MODULE,
- allow_transports) of
- true ->
- %% Check if the contact's server is in the roster
- 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
+ case has_subscription(LocalUser, LocalServer, From) of
+ false when RemoteUser == <<"">> ->
+ false;
+ false ->
+ %% Check if the contact's server is in the roster
+ gen_mod:get_module_opt(LocalServer, ?MODULE, allow_transports)
+ andalso has_subscription(LocalUser, LocalServer,
+ jid:make(RemoteServer));
+ true ->
+ true
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) ->
case ?SETS:next(sets_iterator_from(LBJID, Set)) of
{{U, S, _}, _} -> true;
@@ -189,10 +263,13 @@ mod_opt_type(log) ->
mod_opt_type(allow_local_users) ->
fun (B) when is_boolean(B) -> B end;
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.
mod_options(_) ->
[{drop, true},
{log, false},
+ {captcha, false},
{allow_local_users, true},
{allow_transports, true}].
diff --git a/src/mod_caps.erl b/src/mod_caps.erl
index 6b23b193c..7d26178fa 100644
--- a/src/mod_caps.erl
+++ b/src/mod_caps.erl
@@ -203,9 +203,10 @@ disco_info(Acc, _, _, _Node, _Lang) ->
-spec c2s_presence_in(ejabberd_c2s:state(), presence()) -> ejabberd_c2s:state().
c2s_presence_in(C2SState,
#presence{from = From, to = To, type = Type} = Presence) ->
- {Subscription, _} = ejabberd_hooks:run_fold(
- roster_get_jid_info, To#jid.lserver,
- {none, []}, [To#jid.luser, To#jid.lserver, From]),
+ {Subscription, _, _} = ejabberd_hooks:run_fold(
+ roster_get_jid_info, To#jid.lserver,
+ {none, none, []},
+ [To#jid.luser, To#jid.lserver, From]),
ToSelf = (From#jid.luser == To#jid.luser)
and (From#jid.lserver == To#jid.lserver),
Insert = (Type == available)
diff --git a/src/mod_last.erl b/src/mod_last.erl
index f9edb91f4..d69a5acac 100644
--- a/src/mod_last.erl
+++ b/src/mod_last.erl
@@ -142,9 +142,9 @@ process_sm_iq(#iq{type = set, lang = Lang} = IQ) ->
process_sm_iq(#iq{from = From, to = To, lang = Lang} = IQ) ->
User = To#jid.luser,
Server = To#jid.lserver,
- {Subscription, _Groups} =
+ {Subscription, _Ask, _Groups} =
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
(From#jid.luser == To#jid.luser) and
(From#jid.lserver == To#jid.lserver) ->
diff --git a/src/mod_mam.erl b/src/mod_mam.erl
index b6f80be67..8e8f57171 100644
--- a/src/mod_mam.erl
+++ b/src/mod_mam.erl
@@ -672,10 +672,10 @@ should_archive_peer(LUser, LServer,
always -> true;
never -> false;
roster ->
- {Sub, _} = ejabberd_hooks:run_fold(
- roster_get_jid_info,
- LServer, {none, []},
- [LUser, LServer, Peer]),
+ {Sub, _, _} = ejabberd_hooks:run_fold(
+ roster_get_jid_info,
+ LServer, {none, none, []},
+ [LUser, LServer, Peer]),
Sub == both orelse Sub == from orelse Sub == to
end
end
diff --git a/src/mod_privacy.erl b/src/mod_privacy.erl
index 888a231ed..5dcd69720 100644
--- a/src/mod_privacy.erl
+++ b/src/mod_privacy.erl
@@ -589,9 +589,10 @@ do_check_packet(#jid{luser = LUser, lserver = LServer}, List, Packet, Dir) ->
in -> jid:tolower(From);
out -> jid:tolower(To)
end,
- {Subscription, Groups} = ejabberd_hooks:run_fold(
- roster_get_jid_info, LServer,
- {none, []}, [LUser, LServer, LJID]),
+ {Subscription, _Ask, Groups} = ejabberd_hooks:run_fold(
+ roster_get_jid_info, LServer,
+ {none, none, []},
+ [LUser, LServer, LJID]),
check_packet_aux(List, PType2, LJID, Subscription, Groups)
end.
diff --git a/src/mod_pubsub.erl b/src/mod_pubsub.erl
index 465d15a26..436ed1f3e 100644
--- a/src/mod_pubsub.erl
+++ b/src/mod_pubsub.erl
@@ -52,7 +52,7 @@
%% exports for hooks
-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,
disco_local_identity/5, disco_local_features/5,
disco_local_items/5, disco_sm_identity/5,
@@ -605,22 +605,17 @@ on_user_offline(C2SState, _Reason) ->
%% subscription hooks handling functions
%%
--spec out_subscription(
- binary(), binary(), jid(),
- subscribed | unsubscribed | subscribe | unsubscribe) -> boolean().
-out_subscription(User, Server, To, subscribed) ->
- send_last_pep(jid:make(User, Server), To),
- true;
-out_subscription(_, _, _, _) ->
- true.
+-spec out_subscription(presence()) -> any().
+out_subscription(#presence{type = subscribed, from = From, to = To}) ->
+ send_last_pep(jid:remove_resource(From), To);
+out_subscription(_) ->
+ ok.
--spec in_subscription(boolean(), binary(), binary(), jid(),
- subscribe | subscribed | unsubscribe | unsubscribed,
- binary()) -> true.
-in_subscription(_, User, Server, Owner, unsubscribed, _) ->
- unsubscribe_user(jid:make(User, Server), Owner),
+-spec in_subscription(boolean(), presence()) -> true.
+in_subscription(_, #presence{to = To, from = Owner, type = unsubscribed}) ->
+ unsubscribe_user(jid:remove_resource(To), Owner),
true;
-in_subscription(_, _, _, _, _, _) ->
+in_subscription(_, _) ->
true.
unsubscribe_user(Entity, Owner) ->
@@ -2513,8 +2508,8 @@ get_roster_info(_, _, {<<>>, <<>>, _}, _) ->
{false, false};
get_roster_info(OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, _}, AllowedGroups) ->
LJID = {SubscriberUser, SubscriberServer, <<>>},
- {Subscription, Groups} = ejabberd_hooks:run_fold(roster_get_jid_info,
- OwnerServer, {none, []},
+ {Subscription, _Ask, Groups} = ejabberd_hooks:run_fold(roster_get_jid_info,
+ OwnerServer, {none, none, []},
[OwnerUser, OwnerServer, LJID]),
PresenceSubscription = Subscription == both orelse
Subscription == from orelse
diff --git a/src/mod_roster.erl b/src/mod_roster.erl
index f9779c47f..68d987e0e 100644
--- a/src/mod_roster.erl
+++ b/src/mod_roster.erl
@@ -44,8 +44,8 @@
import_info/0, process_local_iq/1, get_user_roster/2,
import/5, get_roster/2,
import_start/2, import_stop/2,
- c2s_self_presence/1, in_subscription/6,
- out_subscription/4, set_items/3, remove_user/2,
+ c2s_self_presence/1, in_subscription/2,
+ out_subscription/1, set_items/3, remove_user/2,
get_jid_info/4, encode_item/1, webadmin_page/3,
webadmin_user/4, get_versioning_feature/2,
roster_versioning_enabled/1, roster_version/2,
@@ -76,7 +76,7 @@
-callback get_roster(binary(), binary()) -> {ok, [#roster{}]} | error.
-callback get_roster_item(binary(), binary(), ljid()) -> {ok, #roster{}} | error.
-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 transaction(binary(), function()) -> {atomic, any()} | {aborted, any()}.
-callback remove_user(binary(), binary()) -> any().
@@ -383,20 +383,28 @@ get_subscription_and_groups(LUser, LServer, LJID) ->
fun() ->
Items = get_roster(LUser, LServer),
case lists:keyfind(LBJID, #roster.jid, Items) of
- #roster{subscription = Sub, groups = Groups} ->
- {ok, {Sub, Groups}};
+ #roster{subscription = Sub,
+ ask = Ask,
+ groups = Groups} ->
+ {ok, {Sub, Ask, Groups}};
false ->
error
end
end);
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,
case Res of
{ok, SubAndGroups} ->
SubAndGroups;
error ->
- {none, []}
+ {none, none, []}
end.
set_roster(#roster{us = {LUser, LServer}, jid = LJID} = Item) ->
@@ -555,17 +563,19 @@ transaction(LUser, LServer, LJIDs, F) ->
Err
end.
--spec in_subscription(boolean(), binary(), binary(), jid(),
- subscribe | subscribed | unsubscribe | unsubscribed,
- binary()) -> boolean().
-in_subscription(_, User, Server, JID, Type, Reason) ->
+-spec in_subscription(boolean(), presence()) -> boolean().
+in_subscription(_, #presence{from = JID, to = To,
+ type = Type, status = Status}) ->
+ #jid{user = User, server = Server} = To,
+ Reason = if Type == subscribe -> xmpp:get_text(Status);
+ true -> <<"">>
+ end,
process_subscription(in, User, Server, JID, Type,
Reason).
--spec out_subscription(
- binary(), binary(), jid(),
- subscribed | unsubscribed | subscribe | unsubscribe) -> boolean().
-out_subscription(User, Server, JID, Type) ->
+-spec out_subscription(presence()) -> boolean().
+out_subscription(#presence{from = From, to = JID, type = Type}) ->
+ #jid{user = User, server = Server} = From,
process_subscription(out, User, Server, JID, Type, <<"">>).
process_subscription(Direction, User, Server, JID1,
@@ -878,8 +888,8 @@ get_priority_from_presence(#presence{priority = Prio}) ->
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--spec get_jid_info({subscription(), [binary()]}, binary(), binary(), jid())
- -> {subscription(), [binary()]}.
+-spec get_jid_info({subscription(), ask(), [binary()]}, binary(), binary(), jid())
+ -> {subscription(), ask(), [binary()]}.
get_jid_info(_, User, Server, JID) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
@@ -1029,9 +1039,10 @@ user_roster_parse_query(User, Server, Items, Query) ->
end.
user_roster_subscribe_jid(User, Server, JID) ->
- out_subscription(User, Server, JID, subscribe),
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,
Query) ->
@@ -1043,12 +1054,11 @@ user_roster_item_parse_query(User, Server, Items,
of
{value, _} ->
JID1 = jid:make(JID),
- out_subscription(User, Server, JID1,
- subscribed),
UJID = jid:make(User, Server),
- ejabberd_router:route(
- #presence{from = UJID, to = JID1,
- type = subscribed}),
+ Pres = #presence{from = UJID, to = JID1,
+ type = subscribed},
+ out_subscription(Pres),
+ ejabberd_router:route(Pres),
throw(submitted);
false ->
case lists:keysearch(<<"remove",
diff --git a/src/mod_roster_mnesia.erl b/src/mod_roster_mnesia.erl
index 67d302f8a..01e671b43 100644
--- a/src/mod_roster_mnesia.erl
+++ b/src/mod_roster_mnesia.erl
@@ -102,8 +102,8 @@ del_roster(LUser, LServer, LJID) ->
read_subscription_and_groups(LUser, LServer, LJID) ->
case mnesia:dirty_read(roster, {LUser, LServer, LJID}) of
- [#roster{subscription = Subscription, groups = Groups}] ->
- {ok, {Subscription, Groups}};
+ [#roster{subscription = Subscription, ask = Ask, groups = Groups}] ->
+ {ok, {Subscription, Ask, Groups}};
_ ->
error
end.
diff --git a/src/mod_roster_riak.erl b/src/mod_roster_riak.erl
index 86fc02cf7..1f65d384c 100644
--- a/src/mod_roster_riak.erl
+++ b/src/mod_roster_riak.erl
@@ -88,8 +88,9 @@ del_roster(LUser, LServer, LJID) ->
read_subscription_and_groups(LUser, LServer, LJID) ->
case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
{ok, #roster{subscription = Subscription,
+ ask = Ask,
groups = Groups}} ->
- {ok, {Subscription, Groups}};
+ {ok, {Subscription, Ask, Groups}};
_ ->
error
end.
diff --git a/src/mod_roster_sql.erl b/src/mod_roster_sql.erl
index c943fc446..85019e21d 100644
--- a/src/mod_roster_sql.erl
+++ b/src/mod_roster_sql.erl
@@ -171,25 +171,15 @@ del_roster(LUser, LServer, LJID) ->
read_subscription_and_groups(LUser, LServer, LJID) ->
SJID = jid:encode(LJID),
case get_subscription(LServer, LUser, SJID) of
- {selected, [{SSubscription}]} ->
- Subscription = case SSubscription of
- <<"B">> -> both;
- <<"T">> -> to;
- <<"F">> -> from;
- <<"N">> -> none;
- <<"">> -> none;
- _ ->
- ?ERROR_MSG("~s", [format_row_error(
- LUser, LServer,
- {subscription, SSubscription})]),
- none
- end,
+ {selected, [{SSubscription, SAsk}]} ->
+ Subscription = decode_subscription(LUser, LServer, SSubscription),
+ Ask = decode_ask(LUser, LServer, SAsk),
Groups = case get_rostergroup_by_jid(LServer, LUser, SJID) of
{selected, JGrps} when is_list(JGrps) ->
[JGrp || {JGrp} <- JGrps];
_ -> []
end,
- {ok, {Subscription, Groups}};
+ {ok, {Subscription, Ask, Groups}};
_ ->
error
end.
@@ -272,7 +262,7 @@ get_rostergroup_by_jid(LServer, LUser, SJID) ->
get_subscription(LServer, LUser, SJID) ->
ejabberd_sql:sql_query(
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")).
update_roster_sql({LUser, LServer, SJID, Name, SSubscription, SAsk, AskMessage},
@@ -320,30 +310,8 @@ raw_to_record(LServer,
try jid:decode(SJID) of
JID ->
LJID = jid:tolower(JID),
- Subscription = case SSubscription of
- <<"B">> -> both;
- <<"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,
+ Subscription = decode_subscription(User, LServer, SSubscription),
+ Ask = decode_ask(User, LServer, SAsk),
#roster{usj = {User, LServer, LJID},
us = {User, LServer}, jid = LJID, name = Nick,
subscription = Subscription, ask = Ask,
@@ -374,6 +342,32 @@ record_to_row(
end,
{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) ->
[case Why of
{jid, JID} -> ["Malformed 'jid' field with value '", JID, "'"];
diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl
index fffd68d60..a1fda9438 100644
--- a/src/mod_shared_roster.erl
+++ b/src/mod_shared_roster.erl
@@ -33,7 +33,7 @@
import_info/0, webadmin_menu/3, webadmin_page/3,
get_user_roster/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,
list_groups/1, create_group/2, create_group/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
%% existing roster groups.
[] ->
- mod_roster:out_subscription(UserTo, ServerTo,
- jid:make(UserFrom, ServerFrom),
- unsubscribe),
- mod_roster:in_subscription(false, UserFrom, ServerFrom,
- jid:make(UserTo, ServerTo),
- unsubscribe, <<"">>),
+ Pres = #presence{from = jid:make(UserTo, ServerTo),
+ to = jid:make(UserFrom, ServerFrom),
+ type = unsubscribe},
+ mod_roster:out_subscription(Pres),
+ mod_roster:in_subscription(false, Pres),
RosterItem#roster{subscription = both, ask = none};
%% If so, it means the user wants to add that contact
%% to his personal roster
@@ -268,22 +267,22 @@ set_new_rosteritems(UserFrom, ServerFrom, UserTo,
RITo = build_roster_record(UserTo, ServerTo, UserFrom,
ServerFrom, UserFrom, []),
set_item(UserTo, ServerTo, <<"">>, RITo),
- mod_roster:out_subscription(UserFrom, ServerFrom, JIDTo,
- subscribe),
- mod_roster:in_subscription(false, UserTo, ServerTo,
- JIDFrom, subscribe, <<"">>),
- mod_roster:out_subscription(UserTo, ServerTo, JIDFrom,
- subscribed),
- mod_roster:in_subscription(false, UserFrom, ServerFrom,
- JIDTo, subscribed, <<"">>),
- mod_roster:out_subscription(UserTo, ServerTo, JIDFrom,
- subscribe),
- mod_roster:in_subscription(false, UserFrom, ServerFrom,
- JIDTo, subscribe, <<"">>),
- mod_roster:out_subscription(UserFrom, ServerFrom, JIDTo,
- subscribed),
- mod_roster:in_subscription(false, UserTo, ServerTo,
- JIDFrom, subscribed, <<"">>),
+ mod_roster:out_subscription(
+ #presence{from = JIDFrom, to = JIDTo, type = subscribe}),
+ mod_roster:in_subscription(
+ false, #presence{to = JIDTo, from = JIDFrom, type = subscribe}),
+ mod_roster:out_subscription(
+ #presence{from = JIDTo, to = JIDFrom, type = subscribed}),
+ mod_roster:in_subscription(
+ false, #presence{to = JIDFrom, from = JIDTo, type = subscribed}),
+ mod_roster:out_subscription(
+ #presence{from = JIDTo, to = JIDFrom, type = subscribe}),
+ mod_roster:in_subscription(
+ false, #presence{to = JIDFrom, from = JIDTo, type = subscribe}),
+ mod_roster:out_subscription(
+ #presence{from = JIDFrom, to = JIDTo, type = subscribed}),
+ mod_roster:in_subscription(
+ false, #presence{to = JIDTo, from = JIDFrom, type = subscribed}),
RIFrom.
set_item(User, Server, Resource, Item) ->
@@ -294,9 +293,9 @@ set_item(User, Server, Resource, Item) ->
items = [mod_roster:encode_item(Item)]}]},
ejabberd_router:route(ResIQ).
--spec get_jid_info({subscription(), [binary()]}, binary(), binary(), jid())
- -> {subscription(), [binary()]}.
-get_jid_info({Subscription, Groups}, User, Server,
+-spec get_jid_info({subscription(), ask(), [binary()]}, binary(), binary(), jid())
+ -> {subscription(), ask(), [binary()]}.
+get_jid_info({Subscription, Ask, Groups}, User, Server,
JID) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
@@ -320,33 +319,26 @@ get_jid_info({Subscription, Groups}, User, Server,
NewGroups = if Groups == [] -> GroupNames;
true -> Groups
end,
- {both, NewGroups};
- error -> {Subscription, Groups}
+ {both, none, NewGroups};
+ error -> {Subscription, Ask, Groups}
end.
--spec in_subscription(boolean(), binary(), binary(), jid(),
- subscribe | subscribed | unsubscribe | unsubscribed,
- binary()) -> boolean().
-in_subscription(Acc, User, Server, JID, Type,
- _Reason) ->
+-spec in_subscription(boolean(), presence()) -> boolean().
+in_subscription(Acc, #presence{to = To, from = JID, type = Type}) ->
+ #jid{user = User, server = Server} = To,
process_subscription(in, User, Server, JID, Type, Acc).
--spec out_subscription(
- binary(), binary(), jid(),
- subscribed | unsubscribed | subscribe | unsubscribe) -> boolean().
-out_subscription(UserFrom, ServerFrom, JIDTo,
- unsubscribed) ->
- #jid{luser = UserTo, lserver = ServerTo} = JIDTo,
- JIDFrom = jid:make(UserFrom, ServerFrom),
- mod_roster:out_subscription(UserTo, ServerTo, JIDFrom,
- unsubscribe),
- mod_roster:in_subscription(false, UserFrom, ServerFrom,
- JIDTo, unsubscribe, <<"">>),
- process_subscription(out, UserFrom, ServerFrom, JIDTo,
- unsubscribed, false);
-out_subscription(User, Server, JID, Type) ->
- process_subscription(out, User, Server, JID, Type,
- false).
+-spec out_subscription(presence()) -> boolean().
+out_subscription(#presence{from = From, to = To, type = unsubscribed} = Pres) ->
+ #jid{user = User, server = Server} = From,
+ mod_roster:out_subscription(Pres#presence{type = unsubscribe}),
+ mod_roster:in_subscription(false, xmpp:set_from_to(
+ Pres#presence{type = unsubscribe},
+ To, From)),
+ process_subscription(out, User, Server, To, unsubscribed, false);
+out_subscription(#presence{from = From, to = To, type = Type}) ->
+ #jid{user = User, server = Server} = From,
+ process_subscription(out, User, Server, To, Type, false).
process_subscription(Direction, User, Server, JID,
_Type, Acc) ->
diff --git a/src/mod_shared_roster_ldap.erl b/src/mod_shared_roster_ldap.erl
index 82db781e6..745430cad 100644
--- a/src/mod_shared_roster_ldap.erl
+++ b/src/mod_shared_roster_ldap.erl
@@ -40,8 +40,8 @@
handle_info/2, terminate/2, code_change/3]).
-export([get_user_roster/2,
- get_jid_info/4, process_item/2, in_subscription/6,
- out_subscription/4, mod_opt_type/1, mod_options/1,
+ get_jid_info/4, process_item/2, in_subscription/2,
+ out_subscription/1, mod_opt_type/1, mod_options/1,
opt_type/1, depends/2, transform_module_options/1]).
-include("ejabberd.hrl").
@@ -159,9 +159,9 @@ process_item(RosterItem, _Host) ->
_ -> RosterItem#roster{subscription = both, ask = none}
end.
--spec get_jid_info({subscription(), [binary()]}, binary(), binary(), jid())
- -> {subscription(), [binary()]}.
-get_jid_info({Subscription, Groups}, User, Server,
+-spec get_jid_info({subscription(), ask(), [binary()]}, binary(), binary(), jid())
+ -> {subscription(), ask(), [binary()]}.
+get_jid_info({Subscription, Ask, Groups}, User, Server,
JID) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
@@ -174,23 +174,19 @@ get_jid_info({Subscription, Groups}, User, Server,
NewGroups = if Groups == [] -> GroupNames;
true -> Groups
end,
- {both, NewGroups};
- error -> {Subscription, Groups}
+ {both, none, NewGroups};
+ error -> {Subscription, Ask, Groups}
end.
--spec in_subscription(boolean(), binary(), binary(), jid(),
- subscribe | subscribed | unsubscribe | unsubscribed,
- binary()) -> boolean().
-in_subscription(Acc, User, Server, JID, Type,
- _Reason) ->
+-spec in_subscription(boolean(), presence()) -> boolean().
+in_subscription(Acc, #presence{to = To, from = JID, type = Type}) ->
+ #jid{user = User, server = Server} = To,
process_subscription(in, User, Server, JID, Type, Acc).
--spec out_subscription(
- binary(), binary(), jid(),
- subscribed | unsubscribed | subscribe | unsubscribe) -> boolean().
-out_subscription(User, Server, JID, Type) ->
- process_subscription(out, User, Server, JID, Type,
- false).
+-spec out_subscription(presence()) -> boolean().
+out_subscription(#presence{from = From, to = JID, type = Type}) ->
+ #jid{user = User, server = Server} = From,
+ process_subscription(out, User, Server, JID, Type, false).
process_subscription(Direction, User, Server, JID,
_Type, Acc) ->