24
1
mirror of https://github.com/processone/ejabberd.git synced 2024-06-16 22:05:29 +02:00
xmpp.chapril.org-ejabberd/src/ejabberd_sm.erl
Holger Weiss 45321fa2e2 Process messages of unknown type consistently
If an incoming message sent to an unavailable resource has an unknown
type, handle it like messages of type "normal" (as mandated by RFC 6121,
section 5.2.2).  The same is already done for messages of unknown type
sent to the bare JID of an offline user.
2016-04-25 01:13:41 +02:00

823 lines
25 KiB
Erlang

%%%----------------------------------------------------------------------
%%% File : ejabberd_sm.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Session manager
%%% Created : 24 Nov 2002 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
%%%
%%% 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 the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(ejabberd_sm).
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-behaviour(gen_server).
%% API
-export([start/0,
start_link/0,
route/3,
process_iq/3,
open_session/5,
open_session/6,
close_session/4,
check_in_subscription/6,
bounce_offline_message/3,
disconnect_removed_user/2,
get_user_resources/2,
get_user_present_resources/2,
set_presence/7,
unset_presence/6,
close_session_unset_presence/5,
dirty_get_sessions_list/0,
dirty_get_my_sessions_list/0,
get_vh_session_list/1,
get_vh_session_number/1,
get_vh_by_backend/1,
register_iq_handler/4,
register_iq_handler/5,
unregister_iq_handler/2,
force_update_presence/1,
connected_users/0,
connected_users_number/0,
user_resources/2,
kick_user/2,
get_session_pid/3,
get_user_info/3,
get_user_ip/3,
get_max_user_sessions/2,
get_all_pids/0,
is_existing_resource/3,
get_commands_spec/0,
make_sid/0
]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-include("ejabberd_commands.hrl").
-include("mod_privacy.hrl").
-include("ejabberd_sm.hrl").
-callback init() -> ok | {error, any()}.
-callback set_session(#session{}) -> ok.
-callback delete_session(binary(), binary(), binary(), sid()) ->
{ok, #session{}} | {error, notfound}.
-callback get_sessions() -> [#session{}].
-callback get_sessions(binary()) -> [#session{}].
-callback get_sessions(binary(), binary()) -> [#session{}].
-callback get_sessions(binary(), binary(), binary()) -> [#session{}].
-record(state, {}).
%% default value for the maximum number of user connections
-define(MAX_USER_SESSIONS, infinity).
%%====================================================================
%% API
%%====================================================================
-export_type([sid/0]).
start() ->
ChildSpec = {?MODULE, {?MODULE, start_link, []},
transient, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec route(jid(), jid(), xmlel() | broadcast()) -> ok.
route(From, To, Packet) ->
case catch do_route(From, To, Packet) of
{'EXIT', Reason} ->
?ERROR_MSG("~p~nwhen processing: ~p",
[Reason, {From, To, Packet}]);
_ -> ok
end.
-spec open_session(sid(), binary(), binary(), binary(), prio(), info()) -> ok.
open_session(SID, User, Server, Resource, Priority, Info) ->
set_session(SID, User, Server, Resource, Priority, Info),
check_for_sessions_to_replace(User, Server, Resource),
JID = jid:make(User, Server, Resource),
ejabberd_hooks:run(sm_register_connection_hook,
JID#jid.lserver, [SID, JID, Info]).
-spec open_session(sid(), binary(), binary(), binary(), info()) -> ok.
open_session(SID, User, Server, Resource, Info) ->
open_session(SID, User, Server, Resource, undefined, Info).
-spec close_session(sid(), binary(), binary(), binary()) -> ok.
close_session(SID, User, Server, Resource) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
Mod = get_sm_backend(LServer),
Info = case Mod:delete_session(LUser, LServer, LResource, SID) of
{ok, #session{info = I}} -> I;
{error, notfound} -> []
end,
JID = jid:make(User, Server, Resource),
ejabberd_hooks:run(sm_remove_connection_hook,
JID#jid.lserver, [SID, JID, Info]).
-spec check_in_subscription(any(), binary(), binary(),
any(), any(), any()) -> any().
check_in_subscription(Acc, User, Server, _JID, _Type, _Reason) ->
case ejabberd_auth:is_user_exists(User, Server) of
true -> Acc;
false -> {stop, false}
end.
-spec bounce_offline_message(jid(), jid(), xmlel()) -> stop.
bounce_offline_message(From, To, Packet) ->
Lang = fxml:get_tag_attr_s(<<"xml:lang">>, Packet),
Txt = <<"User session not found">>,
Err = jlib:make_error_reply(
Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err),
stop.
-spec disconnect_removed_user(binary(), binary()) -> ok.
disconnect_removed_user(User, Server) ->
ejabberd_sm:route(jid:make(<<"">>, <<"">>, <<"">>),
jid:make(User, Server, <<"">>),
{broadcast, {exit, <<"User removed">>}}).
get_user_resources(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = get_sm_backend(LServer),
Ss = Mod:get_sessions(LUser, LServer),
[element(3, S#session.usr) || S <- clean_session_list(Ss)].
-spec get_user_present_resources(binary(), binary()) -> [tuple()].
get_user_present_resources(LUser, LServer) ->
Mod = get_sm_backend(LServer),
Ss = Mod:get_sessions(LUser, LServer),
[{S#session.priority, element(3, S#session.usr)}
|| S <- clean_session_list(Ss), is_integer(S#session.priority)].
-spec get_user_ip(binary(), binary(), binary()) -> ip().
get_user_ip(User, Server, Resource) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer, LResource) of
[] ->
undefined;
Ss ->
Session = lists:max(Ss),
proplists:get_value(ip, Session#session.info)
end.
-spec get_user_info(binary(), binary(), binary()) -> info() | offline.
get_user_info(User, Server, Resource) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer, LResource) of
[] ->
offline;
Ss ->
Session = lists:max(Ss),
Node = node(element(2, Session#session.sid)),
Conn = proplists:get_value(conn, Session#session.info),
IP = proplists:get_value(ip, Session#session.info),
[{node, Node}, {conn, Conn}, {ip, IP}]
end.
-spec set_presence(sid(), binary(), binary(), binary(),
prio(), xmlel(), info()) -> ok.
set_presence(SID, User, Server, Resource, Priority,
Presence, Info) ->
set_session(SID, User, Server, Resource, Priority,
Info),
ejabberd_hooks:run(set_presence_hook,
jid:nameprep(Server),
[User, Server, Resource, Presence]).
-spec unset_presence(sid(), binary(), binary(),
binary(), binary(), info()) -> ok.
unset_presence(SID, User, Server, Resource, Status,
Info) ->
set_session(SID, User, Server, Resource, undefined,
Info),
ejabberd_hooks:run(unset_presence_hook,
jid:nameprep(Server),
[User, Server, Resource, Status]).
-spec close_session_unset_presence(sid(), binary(), binary(),
binary(), binary()) -> ok.
close_session_unset_presence(SID, User, Server,
Resource, Status) ->
close_session(SID, User, Server, Resource),
ejabberd_hooks:run(unset_presence_hook,
jid:nameprep(Server),
[User, Server, Resource, Status]).
-spec get_session_pid(binary(), binary(), binary()) -> none | pid().
get_session_pid(User, Server, Resource) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer, LResource) of
[#session{sid = {_, Pid}}] -> Pid;
_ -> none
end.
-spec dirty_get_sessions_list() -> [ljid()].
dirty_get_sessions_list() ->
lists:flatmap(
fun(Mod) ->
[S#session.usr || S <- Mod:get_sessions()]
end, get_sm_backends()).
-spec dirty_get_my_sessions_list() -> [#session{}].
dirty_get_my_sessions_list() ->
lists:flatmap(
fun(Mod) ->
[S || S <- Mod:get_sessions(),
node(element(2, S#session.sid)) == node()]
end, get_sm_backends()).
-spec get_vh_session_list(binary()) -> [ljid()].
get_vh_session_list(Server) ->
LServer = jid:nameprep(Server),
Mod = get_sm_backend(LServer),
[S#session.usr || S <- Mod:get_sessions(LServer)].
-spec get_all_pids() -> [pid()].
get_all_pids() ->
lists:flatmap(
fun(Mod) ->
[element(2, S#session.sid) || S <- Mod:get_sessions()]
end, get_sm_backends()).
-spec get_vh_session_number(binary()) -> non_neg_integer().
get_vh_session_number(Server) ->
LServer = jid:nameprep(Server),
Mod = get_sm_backend(LServer),
length(Mod:get_sessions(LServer)).
register_iq_handler(Host, XMLNS, Module, Fun) ->
ejabberd_sm ! {register_iq_handler, Host, XMLNS, Module, Fun}.
-spec register_iq_handler(binary(), binary(), atom(), atom(), list()) -> any().
register_iq_handler(Host, XMLNS, Module, Fun, Opts) ->
ejabberd_sm ! {register_iq_handler, Host, XMLNS, Module, Fun, Opts}.
-spec unregister_iq_handler(binary(), binary()) -> any().
unregister_iq_handler(Host, XMLNS) ->
ejabberd_sm ! {unregister_iq_handler, Host, XMLNS}.
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([]) ->
lists:foreach(fun(Mod) -> Mod:init() end, get_sm_backends()),
ets:new(sm_iqtable, [named_table]),
lists:foreach(
fun(Host) ->
ejabberd_hooks:add(roster_in_subscription, Host,
ejabberd_sm, check_in_subscription, 20),
ejabberd_hooks:add(offline_message_hook, Host,
ejabberd_sm, bounce_offline_message, 100),
ejabberd_hooks:add(remove_user, Host,
ejabberd_sm, disconnect_removed_user, 100)
end, ?MYHOSTS),
ejabberd_commands:register_commands(get_commands_spec()),
{ok, #state{}}.
handle_call(_Request, _From, State) ->
Reply = ok, {reply, Reply, State}.
handle_cast(_Msg, State) -> {noreply, State}.
handle_info({route, From, To, Packet}, State) ->
case catch do_route(From, To, Packet) of
{'EXIT', Reason} ->
?ERROR_MSG("~p~nwhen processing: ~p",
[Reason, {From, To, Packet}]);
_ ->
ok
end,
{noreply, State};
handle_info({register_iq_handler, Host, XMLNS, Module, Function}, State) ->
ets:insert(sm_iqtable, {{XMLNS, Host}, Module, Function}),
{noreply, State};
handle_info({register_iq_handler, Host, XMLNS, Module,
Function, Opts},
State) ->
ets:insert(sm_iqtable,
{{XMLNS, Host}, Module, Function, Opts}),
{noreply, State};
handle_info({unregister_iq_handler, Host, XMLNS},
State) ->
case ets:lookup(sm_iqtable, {XMLNS, Host}) of
[{_, Module, Function, Opts}] ->
gen_iq_handler:stop_iq_handler(Module, Function, Opts);
_ -> ok
end,
ets:delete(sm_iqtable, {XMLNS, Host}),
{noreply, State};
handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, _State) ->
ejabberd_commands:unregister_commands(get_commands_spec()),
ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-spec set_session(sid(), binary(), binary(), binary(),
prio(), info()) -> ok.
set_session(SID, User, Server, Resource, Priority, Info) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
US = {LUser, LServer},
USR = {LUser, LServer, LResource},
Mod = get_sm_backend(LServer),
Mod:set_session(#session{sid = SID, usr = USR, us = US,
priority = Priority, info = Info}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
do_route(From, To, {broadcast, _} = Packet) ->
case To#jid.lresource of
<<"">> ->
lists:foreach(fun(R) ->
do_route(From,
jid:replace_resource(To, R),
Packet)
end,
get_user_resources(To#jid.user, To#jid.server));
_ ->
{U, S, R} = jid:tolower(To),
Mod = get_sm_backend(S),
case Mod:get_sessions(U, S, R) of
[] ->
?DEBUG("packet dropped~n", []);
Ss ->
Session = lists:max(Ss),
Pid = element(2, Session#session.sid),
?DEBUG("sending to process ~p~n", [Pid]),
Pid ! {route, From, To, Packet}
end
end;
do_route(From, To, #xmlel{} = Packet) ->
?DEBUG("session manager~n\tfrom ~p~n\tto ~p~n\tpacket "
"~P~n",
[From, To, Packet, 8]),
#jid{user = User, server = Server,
luser = LUser, lserver = LServer, lresource = LResource} = To,
#xmlel{name = Name, attrs = Attrs} = Packet,
Lang = fxml:get_attr_s(<<"xml:lang">>, Attrs),
case LResource of
<<"">> ->
case Name of
<<"presence">> ->
{Pass, _Subsc} = case fxml:get_attr_s(<<"type">>, Attrs)
of
<<"subscribe">> ->
Reason = fxml:get_path_s(Packet,
[{elem,
<<"status">>},
cdata]),
{is_privacy_allow(From, To, Packet)
andalso
ejabberd_hooks:run_fold(roster_in_subscription,
LServer,
false,
[User, Server,
From,
subscribe,
Reason]),
true};
<<"subscribed">> ->
{is_privacy_allow(From, To, Packet)
andalso
ejabberd_hooks:run_fold(roster_in_subscription,
LServer,
false,
[User, Server,
From,
subscribed,
<<"">>]),
true};
<<"unsubscribe">> ->
{is_privacy_allow(From, To, Packet)
andalso
ejabberd_hooks:run_fold(roster_in_subscription,
LServer,
false,
[User, Server,
From,
unsubscribe,
<<"">>]),
true};
<<"unsubscribed">> ->
{is_privacy_allow(From, To, Packet)
andalso
ejabberd_hooks:run_fold(roster_in_subscription,
LServer,
false,
[User, Server,
From,
unsubscribed,
<<"">>]),
true};
_ -> {true, false}
end,
if Pass ->
PResources = get_user_present_resources(LUser, LServer),
lists:foreach(fun ({_, R}) ->
do_route(From,
jid:replace_resource(To,
R),
Packet)
end,
PResources);
true -> ok
end;
<<"message">> ->
case fxml:get_attr_s(<<"type">>, Attrs) of
<<"chat">> -> route_message(From, To, Packet, chat);
<<"headline">> -> route_message(From, To, Packet, headline);
<<"error">> -> ok;
<<"groupchat">> ->
ErrTxt = <<"User session not found">>,
Err = jlib:make_error_reply(
Packet, ?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
ejabberd_router:route(To, From, Err);
_ ->
route_message(From, To, Packet, normal)
end;
<<"iq">> -> process_iq(From, To, Packet);
_ -> ok
end;
_ ->
Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer, LResource) of
[] ->
case Name of
<<"message">> ->
case fxml:get_attr_s(<<"type">>, Attrs) of
<<"chat">> -> route_message(From, To, Packet, chat);
<<"headline">> -> ok;
<<"error">> -> ok;
<<"groupchat">> ->
ErrTxt = <<"User session not found">>,
Err = jlib:make_error_reply(
Packet,
?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
ejabberd_router:route(To, From, Err);
_ ->
route_message(From, To, Packet, normal)
end;
<<"iq">> ->
case fxml:get_attr_s(<<"type">>, Attrs) of
<<"error">> -> ok;
<<"result">> -> ok;
_ ->
ErrTxt = <<"User session not found">>,
Err = jlib:make_error_reply(
Packet,
?ERRT_SERVICE_UNAVAILABLE(Lang, ErrTxt)),
ejabberd_router:route(To, From, Err)
end;
_ -> ?DEBUG("packet dropped~n", [])
end;
Ss ->
Session = lists:max(Ss),
Pid = element(2, Session#session.sid),
?DEBUG("sending to process ~p~n", [Pid]),
Pid ! {route, From, To, Packet}
end
end.
%% The default list applies to the user as a whole,
%% and is processed if there is no active list set
%% for the target session/resource to which a stanza is addressed,
%% or if there are no current sessions for the user.
is_privacy_allow(From, To, Packet) ->
User = To#jid.user,
Server = To#jid.server,
PrivacyList =
ejabberd_hooks:run_fold(privacy_get_user_list, Server,
#userlist{}, [User, Server]),
is_privacy_allow(From, To, Packet, PrivacyList).
%% Check if privacy rules allow this delivery
%% Function copied from ejabberd_c2s.erl
is_privacy_allow(From, To, Packet, PrivacyList) ->
User = To#jid.user,
Server = To#jid.server,
allow ==
ejabberd_hooks:run_fold(privacy_check_packet, Server,
allow,
[User, Server, PrivacyList, {From, To, Packet},
in]).
route_message(From, To, Packet, Type) ->
LUser = To#jid.luser,
LServer = To#jid.lserver,
PrioRes = get_user_present_resources(LUser, LServer),
case catch lists:max(PrioRes) of
{Priority, _R}
when is_integer(Priority), Priority >= 0 ->
lists:foreach(fun ({P, R}) when P == Priority;
(P >= 0) and (Type == headline) ->
LResource = jid:resourceprep(R),
Mod = get_sm_backend(LServer),
case Mod:get_sessions(LUser, LServer,
LResource) of
[] ->
ok; % Race condition
Ss ->
Session = lists:max(Ss),
Pid = element(2, Session#session.sid),
?DEBUG("sending to process ~p~n", [Pid]),
Pid ! {route, From, To, Packet}
end;
%% Ignore other priority:
({_Prio, _Res}) -> ok
end,
PrioRes);
_ ->
case Type of
headline -> ok;
_ ->
case ejabberd_auth:is_user_exists(LUser, LServer) of
true ->
case is_privacy_allow(From, To, Packet) of
true ->
ejabberd_hooks:run(offline_message_hook, LServer,
[From, To, Packet]);
false -> ok
end;
_ ->
Err = jlib:make_error_reply(Packet,
?ERR_SERVICE_UNAVAILABLE),
ejabberd_router:route(To, From, Err)
end
end
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
clean_session_list(Ss) ->
clean_session_list(lists:keysort(#session.usr, Ss), []).
clean_session_list([], Res) -> Res;
clean_session_list([S], Res) -> [S | Res];
clean_session_list([S1, S2 | Rest], Res) ->
if S1#session.usr == S2#session.usr ->
if S1#session.sid > S2#session.sid ->
clean_session_list([S1 | Rest], Res);
true -> clean_session_list([S2 | Rest], Res)
end;
true -> clean_session_list([S2 | Rest], [S1 | Res])
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% On new session, check if some existing connections need to be replace
check_for_sessions_to_replace(User, Server, Resource) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
check_existing_resources(LUser, LServer, LResource),
check_max_sessions(LUser, LServer).
check_existing_resources(LUser, LServer, LResource) ->
SIDs = get_resource_sessions(LUser, LServer, LResource),
if SIDs == [] -> ok;
true ->
MaxSID = lists:max(SIDs),
lists:foreach(fun ({_, Pid} = S) when S /= MaxSID ->
Pid ! replaced;
(_) -> ok
end,
SIDs)
end.
-spec is_existing_resource(binary(), binary(), binary()) -> boolean().
is_existing_resource(LUser, LServer, LResource) ->
[] /= get_resource_sessions(LUser, LServer, LResource).
get_resource_sessions(User, Server, Resource) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
LResource = jid:resourceprep(Resource),
Mod = get_sm_backend(LServer),
[S#session.sid || S <- Mod:get_sessions(LUser, LServer, LResource)].
check_max_sessions(LUser, LServer) ->
Mod = get_sm_backend(LServer),
SIDs = [S#session.sid || S <- Mod:get_sessions(LUser, LServer)],
MaxSessions = get_max_user_sessions(LUser, LServer),
if length(SIDs) =< MaxSessions -> ok;
true -> {_, Pid} = lists:min(SIDs), Pid ! replaced
end.
%% Get the user_max_session setting
%% This option defines the max number of time a given users are allowed to
%% log in
%% Defaults to infinity
get_max_user_sessions(LUser, Host) ->
case acl:match_rule(Host, max_user_sessions,
jid:make(LUser, Host, <<"">>))
of
Max when is_integer(Max) -> Max;
infinity -> infinity;
_ -> ?MAX_USER_SESSIONS
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
process_iq(From, To, Packet) ->
IQ = jlib:iq_query_info(Packet),
case IQ of
#iq{xmlns = XMLNS, lang = Lang} ->
Host = To#jid.lserver,
case ets:lookup(sm_iqtable, {XMLNS, Host}) of
[{_, Module, Function}] ->
ResIQ = Module:Function(From, To, IQ),
if ResIQ /= ignore ->
ejabberd_router:route(To, From, jlib:iq_to_xml(ResIQ));
true -> ok
end;
[{_, Module, Function, Opts}] ->
gen_iq_handler:handle(Host, Module, Function, Opts,
From, To, IQ);
[] ->
Txt = <<"No module is handling this query">>,
Err = jlib:make_error_reply(
Packet,
?ERRT_SERVICE_UNAVAILABLE(Lang, Txt)),
ejabberd_router:route(To, From, Err)
end;
reply -> ok;
_ ->
Err = jlib:make_error_reply(Packet, ?ERR_BAD_REQUEST),
ejabberd_router:route(To, From, Err),
ok
end.
-spec force_update_presence({binary(), binary()}) -> any().
force_update_presence({LUser, LServer}) ->
Mod = get_sm_backend(LServer),
Ss = Mod:get_sessions(LUser, LServer),
lists:foreach(fun (#session{sid = {_, Pid}}) ->
Pid ! {force_update_presence, LUser, LServer}
end,
Ss).
-spec get_sm_backend(binary()) -> module().
get_sm_backend(Host) ->
DBType = ejabberd_config:get_option({sm_db_type, Host},
fun(mnesia) -> mnesia;
(internal) -> mnesia;
(odbc) -> sql;
(sql) -> sql;
(redis) -> redis
end, mnesia),
list_to_atom("ejabberd_sm_" ++ atom_to_list(DBType)).
-spec get_sm_backends() -> [module()].
get_sm_backends() ->
lists:usort([get_sm_backend(Host) || Host <- ?MYHOSTS]).
-spec get_vh_by_backend(module()) -> [binary()].
get_vh_by_backend(Mod) ->
lists:filter(
fun(Host) ->
get_sm_backend(Host) == Mod
end, ?MYHOSTS).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% ejabberd commands
get_commands_spec() ->
[#ejabberd_commands{name = connected_users,
tags = [session],
desc = "List all established sessions",
policy = admin,
module = ?MODULE, function = connected_users, args = [],
result = {connected_users, {list, {sessions, string}}}},
#ejabberd_commands{name = connected_users_number,
tags = [session, stats],
desc = "Get the number of established sessions",
policy = admin,
module = ?MODULE, function = connected_users_number,
args = [], result = {num_sessions, integer}},
#ejabberd_commands{name = user_resources,
tags = [session],
desc = "List user's connected resources",
policy = user,
module = ?MODULE, function = user_resources,
args = [],
result = {resources, {list, {resource, string}}}},
#ejabberd_commands{name = kick_user,
tags = [session],
desc = "Disconnect user's active sessions",
module = ?MODULE, function = kick_user,
args = [{user, binary}, {host, binary}],
result = {num_resources, integer}}].
-spec connected_users() -> [binary()].
connected_users() ->
USRs = dirty_get_sessions_list(),
SUSRs = lists:sort(USRs),
lists:map(fun ({U, S, R}) -> <<U/binary, $@, S/binary, $/, R/binary>> end,
SUSRs).
connected_users_number() ->
length(dirty_get_sessions_list()).
user_resources(User, Server) ->
Resources = get_user_resources(User, Server),
lists:sort(Resources).
kick_user(User, Server) ->
Resources = get_user_resources(User, Server),
lists:foreach(
fun(Resource) ->
PID = get_session_pid(User, Server, Resource),
PID ! kick
end, Resources),
length(Resources).
make_sid() ->
{p1_time_compat:unique_timestamp(), self()}.
opt_type(sm_db_type) ->
fun (mnesia) -> mnesia;
(internal) -> mnesia;
(sql) -> sql;
(odbc) -> sql;
(redis) -> redis
end;
opt_type(_) -> [sm_db_type].