mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-26 16:26:24 +01:00
1292 lines
44 KiB
Erlang
1292 lines
44 KiB
Erlang
%%%----------------------------------------------------------------------
|
|
%%% File : mod_muc.erl
|
|
%%% Author : Alexey Shchepin <alexey@process-one.net>
|
|
%%% Purpose : MUC support (XEP-0045)
|
|
%%% Created : 19 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
|
|
%%%
|
|
%%%
|
|
%%% ejabberd, Copyright (C) 2002-2015 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(mod_muc).
|
|
|
|
-author('alexey@process-one.net').
|
|
|
|
-behaviour(gen_server).
|
|
|
|
-behaviour(gen_mod).
|
|
|
|
%% API
|
|
-export([start_link/2,
|
|
start/2,
|
|
stop/1,
|
|
room_destroyed/4,
|
|
store_room/4,
|
|
restore_room/3,
|
|
forget_room/3,
|
|
create_room/5,
|
|
shutdown_rooms/1,
|
|
process_iq_disco_items/4,
|
|
broadcast_service_message/2,
|
|
export/1,
|
|
import/1,
|
|
import/3,
|
|
can_use_nick/4]).
|
|
|
|
%% gen_server callbacks
|
|
-export([init/1, handle_call/3, handle_cast/2,
|
|
handle_info/2, terminate/2, code_change/3]).
|
|
|
|
-include("ejabberd.hrl").
|
|
-include("logger.hrl").
|
|
|
|
-include("jlib.hrl").
|
|
|
|
-record(muc_room, {name_host = {<<"">>, <<"">>} :: {binary(), binary()} |
|
|
{'_', binary()},
|
|
opts = [] :: list() | '_'}).
|
|
|
|
-record(muc_online_room,
|
|
{name_host = {<<"">>, <<"">>} :: {binary(), binary()} | '$1' |
|
|
{'_', binary()} | '_',
|
|
pid = self() :: pid() | '$2' | '_' | '$1'}).
|
|
|
|
-record(muc_registered,
|
|
{us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()}, binary()} | '$1',
|
|
nick = <<"">> :: binary()}).
|
|
|
|
-record(state,
|
|
{host = <<"">> :: binary(),
|
|
server_host = <<"">> :: binary(),
|
|
access = {none, none, none, none} :: {atom(), atom(), atom(), atom()},
|
|
history_size = 20 :: non_neg_integer(),
|
|
default_room_opts = [] :: list(),
|
|
room_shaper = none :: shaper:shaper()}).
|
|
|
|
-define(PROCNAME, ejabberd_mod_muc).
|
|
|
|
%%====================================================================
|
|
%% API
|
|
%%====================================================================
|
|
%%--------------------------------------------------------------------
|
|
%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
|
|
%% Description: Starts the server
|
|
%%--------------------------------------------------------------------
|
|
start_link(Host, Opts) ->
|
|
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
|
gen_server:start_link({local, Proc}, ?MODULE,
|
|
[Host, Opts], []).
|
|
|
|
start(Host, Opts) ->
|
|
start_supervisor(Host),
|
|
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
|
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
|
|
temporary, 1000, worker, [?MODULE]},
|
|
supervisor:start_child(ejabberd_sup, ChildSpec).
|
|
|
|
stop(Host) ->
|
|
Rooms = shutdown_rooms(Host),
|
|
stop_supervisor(Host),
|
|
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
|
gen_server:call(Proc, stop),
|
|
supervisor:delete_child(ejabberd_sup, Proc),
|
|
{wait, Rooms}.
|
|
|
|
shutdown_rooms(Host) ->
|
|
MyHost = gen_mod:get_module_opt_host(Host, mod_muc,
|
|
<<"conference.@HOST@">>),
|
|
Rooms = mnesia:dirty_select(muc_online_room,
|
|
[{#muc_online_room{name_host = '$1',
|
|
pid = '$2'},
|
|
[{'==', {element, 2, '$1'}, MyHost}],
|
|
['$2']}]),
|
|
[Pid ! shutdown || Pid <- Rooms],
|
|
Rooms.
|
|
|
|
%% This function is called by a room in three situations:
|
|
%% A) The owner of the room destroyed it
|
|
%% B) The only participant of a temporary room leaves it
|
|
%% C) mod_muc:stop was called, and each room is being terminated
|
|
%% In this case, the mod_muc process died before the room processes
|
|
%% So the message sending must be catched
|
|
room_destroyed(Host, Room, Pid, ServerHost) ->
|
|
catch gen_mod:get_module_proc(ServerHost, ?PROCNAME) !
|
|
{room_destroyed, {Room, Host}, Pid},
|
|
ok.
|
|
|
|
%% @doc Create a room.
|
|
%% If Opts = default, the default room options are used.
|
|
%% Else use the passed options as defined in mod_muc_room.
|
|
create_room(Host, Name, From, Nick, Opts) ->
|
|
Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
|
|
gen_server:call(Proc, {create, Name, From, Nick, Opts}).
|
|
|
|
store_room(ServerHost, Host, Name, Opts) ->
|
|
LServer = jlib:nameprep(ServerHost),
|
|
store_room(LServer, Host, Name, Opts,
|
|
gen_mod:db_type(LServer, ?MODULE)).
|
|
|
|
store_room(_LServer, Host, Name, Opts, mnesia) ->
|
|
F = fun () ->
|
|
mnesia:write(#muc_room{name_host = {Name, Host},
|
|
opts = Opts})
|
|
end,
|
|
mnesia:transaction(F);
|
|
store_room(_LServer, Host, Name, Opts, riak) ->
|
|
{atomic, ejabberd_riak:put(#muc_room{name_host = {Name, Host},
|
|
opts = Opts},
|
|
muc_room_schema())};
|
|
store_room(LServer, Host, Name, Opts, odbc) ->
|
|
SName = ejabberd_odbc:escape(Name),
|
|
SHost = ejabberd_odbc:escape(Host),
|
|
SOpts = ejabberd_odbc:encode_term(Opts),
|
|
F = fun () ->
|
|
odbc_queries:update_t(<<"muc_room">>,
|
|
[<<"name">>, <<"host">>, <<"opts">>],
|
|
[SName, SHost, SOpts],
|
|
[<<"name='">>, SName, <<"' and host='">>,
|
|
SHost, <<"'">>])
|
|
end,
|
|
ejabberd_odbc:sql_transaction(LServer, F).
|
|
|
|
restore_room(ServerHost, Host, Name) ->
|
|
LServer = jlib:nameprep(ServerHost),
|
|
restore_room(LServer, Host, Name,
|
|
gen_mod:db_type(LServer, ?MODULE)).
|
|
|
|
restore_room(_LServer, Host, Name, mnesia) ->
|
|
case catch mnesia:dirty_read(muc_room, {Name, Host}) of
|
|
[#muc_room{opts = Opts}] -> Opts;
|
|
_ -> error
|
|
end;
|
|
restore_room(_LServer, Host, Name, riak) ->
|
|
case ejabberd_riak:get(muc_room, muc_room_schema(), {Name, Host}) of
|
|
{ok, #muc_room{opts = Opts}} -> Opts;
|
|
_ -> error
|
|
end;
|
|
restore_room(LServer, Host, Name, odbc) ->
|
|
SName = ejabberd_odbc:escape(Name),
|
|
SHost = ejabberd_odbc:escape(Host),
|
|
case catch ejabberd_odbc:sql_query(LServer,
|
|
[<<"select opts from muc_room where name='">>,
|
|
SName, <<"' and host='">>, SHost,
|
|
<<"';">>])
|
|
of
|
|
{selected, [<<"opts">>], [[Opts]]} ->
|
|
opts_to_binary(ejabberd_odbc:decode_term(Opts));
|
|
_ -> error
|
|
end.
|
|
|
|
forget_room(ServerHost, Host, Name) ->
|
|
LServer = jlib:nameprep(ServerHost),
|
|
forget_room(LServer, Host, Name,
|
|
gen_mod:db_type(LServer, ?MODULE)).
|
|
|
|
forget_room(_LServer, Host, Name, mnesia) ->
|
|
F = fun () -> mnesia:delete({muc_room, {Name, Host}})
|
|
end,
|
|
mnesia:transaction(F);
|
|
forget_room(_LServer, Host, Name, riak) ->
|
|
{atomic, ejabberd_riak:delete(muc_room, {Name, Host})};
|
|
forget_room(LServer, Host, Name, odbc) ->
|
|
SName = ejabberd_odbc:escape(Name),
|
|
SHost = ejabberd_odbc:escape(Host),
|
|
F = fun () ->
|
|
ejabberd_odbc:sql_query_t([<<"delete from muc_room where name='">>,
|
|
SName, <<"' and host='">>, SHost,
|
|
<<"';">>])
|
|
end,
|
|
ejabberd_odbc:sql_transaction(LServer, F).
|
|
|
|
process_iq_disco_items(Host, From, To,
|
|
#iq{lang = Lang} = IQ) ->
|
|
Rsm = jlib:rsm_decode(IQ),
|
|
Res = IQ#iq{type = result,
|
|
sub_el =
|
|
[#xmlel{name = <<"query">>,
|
|
attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}],
|
|
children = iq_disco_items(Host, From, Lang, Rsm)}]},
|
|
ejabberd_router:route(To, From, jlib:iq_to_xml(Res)).
|
|
|
|
can_use_nick(_ServerHost, _Host, _JID, <<"">>) -> false;
|
|
can_use_nick(ServerHost, Host, JID, Nick) ->
|
|
LServer = jlib:nameprep(ServerHost),
|
|
can_use_nick(LServer, Host, JID, Nick,
|
|
gen_mod:db_type(LServer, ?MODULE)).
|
|
|
|
can_use_nick(_LServer, Host, JID, Nick, mnesia) ->
|
|
{LUser, LServer, _} = jlib:jid_tolower(JID),
|
|
LUS = {LUser, LServer},
|
|
case catch mnesia:dirty_select(muc_registered,
|
|
[{#muc_registered{us_host = '$1',
|
|
nick = Nick, _ = '_'},
|
|
[{'==', {element, 2, '$1'}, Host}],
|
|
['$_']}])
|
|
of
|
|
{'EXIT', _Reason} -> true;
|
|
[] -> true;
|
|
[#muc_registered{us_host = {U, _Host}}] -> U == LUS
|
|
end;
|
|
can_use_nick(LServer, Host, JID, Nick, riak) ->
|
|
{LUser, LServer, _} = jlib:jid_tolower(JID),
|
|
LUS = {LUser, LServer},
|
|
case ejabberd_riak:get_by_index(muc_registered,
|
|
muc_registered_schema(),
|
|
<<"nick_host">>, {Nick, Host}) of
|
|
{ok, []} ->
|
|
true;
|
|
{ok, [#muc_registered{us_host = {U, _Host}}]} ->
|
|
U == LUS;
|
|
{error, _} ->
|
|
true
|
|
end;
|
|
can_use_nick(LServer, Host, JID, Nick, odbc) ->
|
|
SJID =
|
|
jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(JID))),
|
|
SNick = ejabberd_odbc:escape(Nick),
|
|
SHost = ejabberd_odbc:escape(Host),
|
|
case catch ejabberd_odbc:sql_query(LServer,
|
|
[<<"select jid from muc_registered ">>,
|
|
<<"where nick='">>, SNick,
|
|
<<"' and host='">>, SHost, <<"';">>])
|
|
of
|
|
{selected, [<<"jid">>], [[SJID1]]} -> SJID == SJID1;
|
|
_ -> true
|
|
end.
|
|
|
|
%%====================================================================
|
|
%% gen_server callbacks
|
|
%%====================================================================
|
|
|
|
%%--------------------------------------------------------------------
|
|
%% Function: init(Args) -> {ok, State} |
|
|
%% {ok, State, Timeout} |
|
|
%% ignore |
|
|
%% {stop, Reason}
|
|
%% Description: Initiates the server
|
|
%%--------------------------------------------------------------------
|
|
init([Host, Opts]) ->
|
|
MyHost = gen_mod:get_opt_host(Host, Opts,
|
|
<<"conference.@HOST@">>),
|
|
case gen_mod:db_type(Opts) of
|
|
mnesia ->
|
|
mnesia:create_table(muc_room,
|
|
[{disc_copies, [node()]},
|
|
{attributes,
|
|
record_info(fields, muc_room)}]),
|
|
mnesia:create_table(muc_registered,
|
|
[{disc_copies, [node()]},
|
|
{attributes,
|
|
record_info(fields, muc_registered)}]),
|
|
update_tables(MyHost),
|
|
mnesia:add_table_index(muc_registered, nick);
|
|
_ ->
|
|
ok
|
|
end,
|
|
mnesia:create_table(muc_online_room,
|
|
[{ram_copies, [node()]},
|
|
{attributes, record_info(fields, muc_online_room)}]),
|
|
mnesia:add_table_copy(muc_online_room, node(), ram_copies),
|
|
catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]),
|
|
clean_table_from_bad_node(node(), MyHost),
|
|
mnesia:subscribe(system),
|
|
Access = gen_mod:get_opt(access, Opts, fun(A) -> A end, all),
|
|
AccessCreate = gen_mod:get_opt(access_create, Opts, fun(A) -> A end, all),
|
|
AccessAdmin = gen_mod:get_opt(access_admin, Opts, fun(A) -> A end, none),
|
|
AccessPersistent = gen_mod:get_opt(access_persistent, Opts, fun(A) -> A end, all),
|
|
HistorySize = gen_mod:get_opt(history_size, Opts, fun(A) -> A end, 20),
|
|
DefRoomOpts = gen_mod:get_opt(default_room_options, Opts, fun(A) -> A end, []),
|
|
RoomShaper = gen_mod:get_opt(room_shaper, Opts, fun(A) -> A end, none),
|
|
ejabberd_router:register_route(MyHost),
|
|
load_permanent_rooms(MyHost, Host,
|
|
{Access, AccessCreate, AccessAdmin, AccessPersistent},
|
|
HistorySize,
|
|
RoomShaper),
|
|
{ok, #state{host = MyHost,
|
|
server_host = Host,
|
|
access = {Access, AccessCreate, AccessAdmin, AccessPersistent},
|
|
default_room_opts = DefRoomOpts,
|
|
history_size = HistorySize,
|
|
room_shaper = RoomShaper}}.
|
|
|
|
%%--------------------------------------------------------------------
|
|
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
|
%% {reply, Reply, State, Timeout} |
|
|
%% {noreply, State} |
|
|
%% {noreply, State, Timeout} |
|
|
%% {stop, Reason, Reply, State} |
|
|
%% {stop, Reason, State}
|
|
%% Description: Handling call messages
|
|
%%--------------------------------------------------------------------
|
|
handle_call(stop, _From, State) ->
|
|
{stop, normal, ok, State};
|
|
handle_call({create, Room, From, Nick, Opts}, _From,
|
|
#state{host = Host, server_host = ServerHost,
|
|
access = Access, default_room_opts = DefOpts,
|
|
history_size = HistorySize,
|
|
room_shaper = RoomShaper} = State) ->
|
|
?DEBUG("MUC: create new room '~s'~n", [Room]),
|
|
NewOpts = case Opts of
|
|
default -> DefOpts;
|
|
_ -> Opts
|
|
end,
|
|
{ok, Pid} = mod_muc_room:start(
|
|
Host, ServerHost, Access,
|
|
Room, HistorySize,
|
|
RoomShaper, From,
|
|
Nick, NewOpts),
|
|
register_room(Host, Room, Pid),
|
|
{reply, ok, State}.
|
|
|
|
%%--------------------------------------------------------------------
|
|
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
|
%% {noreply, State, Timeout} |
|
|
%% {stop, Reason, State}
|
|
%% Description: Handling cast messages
|
|
%%--------------------------------------------------------------------
|
|
handle_cast(_Msg, State) -> {noreply, State}.
|
|
|
|
%%--------------------------------------------------------------------
|
|
%% Function: handle_info(Info, State) -> {noreply, State} |
|
|
%% {noreply, State, Timeout} |
|
|
%% {stop, Reason, State}
|
|
%% Description: Handling all non call/cast messages
|
|
%%--------------------------------------------------------------------
|
|
handle_info({route, From, To, Packet},
|
|
#state{host = Host, server_host = ServerHost,
|
|
access = Access, default_room_opts = DefRoomOpts,
|
|
history_size = HistorySize,
|
|
room_shaper = RoomShaper} = State) ->
|
|
case catch do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
|
|
From, To, Packet, DefRoomOpts) of
|
|
{'EXIT', Reason} ->
|
|
?ERROR_MSG("~p", [Reason]);
|
|
_ ->
|
|
ok
|
|
end,
|
|
{noreply, State};
|
|
handle_info({room_destroyed, RoomHost, Pid}, State) ->
|
|
F = fun () ->
|
|
mnesia:delete_object(#muc_online_room{name_host =
|
|
RoomHost,
|
|
pid = Pid})
|
|
end,
|
|
mnesia:transaction(F),
|
|
{noreply, State};
|
|
handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
|
|
clean_table_from_bad_node(Node),
|
|
{noreply, State};
|
|
handle_info(_Info, State) -> {noreply, State}.
|
|
|
|
%%--------------------------------------------------------------------
|
|
%% Function: terminate(Reason, State) -> void()
|
|
%% Description: This function is called by a gen_server when it is about to
|
|
%% terminate. It should be the opposite of Module:init/1 and do any necessary
|
|
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
|
%% The return value is ignored.
|
|
%%--------------------------------------------------------------------
|
|
terminate(_Reason, State) ->
|
|
ejabberd_router:unregister_route(State#state.host),
|
|
ok.
|
|
|
|
%%--------------------------------------------------------------------
|
|
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
|
%% Description: Convert process state when code is changed
|
|
%%--------------------------------------------------------------------
|
|
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
|
|
|
%%--------------------------------------------------------------------
|
|
%%% Internal functions
|
|
%%--------------------------------------------------------------------
|
|
start_supervisor(Host) ->
|
|
Proc = gen_mod:get_module_proc(Host,
|
|
ejabberd_mod_muc_sup),
|
|
ChildSpec = {Proc,
|
|
{ejabberd_tmp_sup, start_link, [Proc, mod_muc_room]},
|
|
permanent, infinity, supervisor, [ejabberd_tmp_sup]},
|
|
supervisor:start_child(ejabberd_sup, ChildSpec).
|
|
|
|
stop_supervisor(Host) ->
|
|
Proc = gen_mod:get_module_proc(Host,
|
|
ejabberd_mod_muc_sup),
|
|
supervisor:terminate_child(ejabberd_sup, Proc),
|
|
supervisor:delete_child(ejabberd_sup, Proc).
|
|
|
|
do_route(Host, ServerHost, Access, HistorySize, RoomShaper,
|
|
From, To, Packet, DefRoomOpts) ->
|
|
{AccessRoute, _AccessCreate, _AccessAdmin, _AccessPersistent} = Access,
|
|
case acl:match_rule(ServerHost, AccessRoute, From) of
|
|
allow ->
|
|
do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
|
|
From, To, Packet, DefRoomOpts);
|
|
_ ->
|
|
#xmlel{attrs = Attrs} = Packet,
|
|
Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
|
|
ErrText = <<"Access denied by service policy">>,
|
|
Err = jlib:make_error_reply(Packet,
|
|
?ERRT_FORBIDDEN(Lang, ErrText)),
|
|
ejabberd_router:route_error(To, From, Err, Packet)
|
|
end.
|
|
|
|
|
|
do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
|
|
From, To, Packet, DefRoomOpts) ->
|
|
{_AccessRoute, AccessCreate, AccessAdmin, _AccessPersistent} = Access,
|
|
{Room, _, Nick} = jlib:jid_tolower(To),
|
|
#xmlel{name = Name, attrs = Attrs} = Packet,
|
|
case Room of
|
|
<<"">> ->
|
|
case Nick of
|
|
<<"">> ->
|
|
case Name of
|
|
<<"iq">> ->
|
|
case jlib:iq_query_info(Packet) of
|
|
#iq{type = get, xmlns = (?NS_DISCO_INFO) = XMLNS,
|
|
sub_el = _SubEl, lang = Lang} =
|
|
IQ ->
|
|
Info = ejabberd_hooks:run_fold(disco_info,
|
|
ServerHost, [],
|
|
[ServerHost, ?MODULE,
|
|
<<"">>, <<"">>]),
|
|
Res = IQ#iq{type = result,
|
|
sub_el =
|
|
[#xmlel{name = <<"query">>,
|
|
attrs =
|
|
[{<<"xmlns">>, XMLNS}],
|
|
children =
|
|
iq_disco_info(Lang) ++
|
|
Info}]},
|
|
ejabberd_router:route(To, From,
|
|
jlib:iq_to_xml(Res));
|
|
#iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ ->
|
|
spawn(?MODULE, process_iq_disco_items,
|
|
[Host, From, To, IQ]);
|
|
#iq{type = get, xmlns = (?NS_REGISTER) = XMLNS,
|
|
lang = Lang, sub_el = _SubEl} =
|
|
IQ ->
|
|
Res = IQ#iq{type = result,
|
|
sub_el =
|
|
[#xmlel{name = <<"query">>,
|
|
attrs =
|
|
[{<<"xmlns">>, XMLNS}],
|
|
children =
|
|
iq_get_register_info(ServerHost,
|
|
Host,
|
|
From,
|
|
Lang)}]},
|
|
ejabberd_router:route(To, From,
|
|
jlib:iq_to_xml(Res));
|
|
#iq{type = set, xmlns = (?NS_REGISTER) = XMLNS,
|
|
lang = Lang, sub_el = SubEl} =
|
|
IQ ->
|
|
case process_iq_register_set(ServerHost, Host, From,
|
|
SubEl, Lang)
|
|
of
|
|
{result, IQRes} ->
|
|
Res = IQ#iq{type = result,
|
|
sub_el =
|
|
[#xmlel{name = <<"query">>,
|
|
attrs =
|
|
[{<<"xmlns">>,
|
|
XMLNS}],
|
|
children = IQRes}]},
|
|
ejabberd_router:route(To, From,
|
|
jlib:iq_to_xml(Res));
|
|
{error, Error} ->
|
|
Err = jlib:make_error_reply(Packet, Error),
|
|
ejabberd_router:route(To, From, Err)
|
|
end;
|
|
#iq{type = get, xmlns = (?NS_VCARD) = XMLNS,
|
|
lang = Lang, sub_el = _SubEl} =
|
|
IQ ->
|
|
Res = IQ#iq{type = result,
|
|
sub_el =
|
|
[#xmlel{name = <<"vCard">>,
|
|
attrs =
|
|
[{<<"xmlns">>, XMLNS}],
|
|
children =
|
|
iq_get_vcard(Lang)}]},
|
|
ejabberd_router:route(To, From,
|
|
jlib:iq_to_xml(Res));
|
|
#iq{type = get, xmlns = ?NS_MUC_UNIQUE} = IQ ->
|
|
Res = IQ#iq{type = result,
|
|
sub_el =
|
|
[#xmlel{name = <<"unique">>,
|
|
attrs =
|
|
[{<<"xmlns">>,
|
|
?NS_MUC_UNIQUE}],
|
|
children =
|
|
[iq_get_unique(From)]}]},
|
|
ejabberd_router:route(To, From,
|
|
jlib:iq_to_xml(Res));
|
|
#iq{} ->
|
|
Err = jlib:make_error_reply(Packet,
|
|
?ERR_FEATURE_NOT_IMPLEMENTED),
|
|
ejabberd_router:route(To, From, Err);
|
|
_ -> ok
|
|
end;
|
|
<<"message">> ->
|
|
case xml:get_attr_s(<<"type">>, Attrs) of
|
|
<<"error">> -> ok;
|
|
_ ->
|
|
case acl:match_rule(ServerHost, AccessAdmin, From)
|
|
of
|
|
allow ->
|
|
Msg = xml:get_path_s(Packet,
|
|
[{elem, <<"body">>},
|
|
cdata]),
|
|
broadcast_service_message(Host, Msg);
|
|
_ ->
|
|
Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
|
|
ErrText =
|
|
<<"Only service administrators are allowed "
|
|
"to send service messages">>,
|
|
Err = jlib:make_error_reply(Packet,
|
|
?ERRT_FORBIDDEN(Lang,
|
|
ErrText)),
|
|
ejabberd_router:route(To, From, Err)
|
|
end
|
|
end;
|
|
<<"presence">> -> ok
|
|
end;
|
|
_ ->
|
|
case xml:get_attr_s(<<"type">>, Attrs) of
|
|
<<"error">> -> ok;
|
|
<<"result">> -> ok;
|
|
_ ->
|
|
Err = jlib:make_error_reply(
|
|
Packet, ?ERR_ITEM_NOT_FOUND),
|
|
ejabberd_router:route(To, From, Err)
|
|
end
|
|
end;
|
|
_ ->
|
|
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
|
|
[] ->
|
|
Type = xml:get_attr_s(<<"type">>, Attrs),
|
|
case {Name, Type} of
|
|
{<<"presence">>, <<"">>} ->
|
|
case check_user_can_create_room(ServerHost,
|
|
AccessCreate, From,
|
|
Room) of
|
|
true ->
|
|
{ok, Pid} = start_new_room(
|
|
Host, ServerHost, Access,
|
|
Room, HistorySize,
|
|
RoomShaper, From,
|
|
Nick, DefRoomOpts),
|
|
register_room(Host, Room, Pid),
|
|
mod_muc_room:route(Pid, From, Nick, Packet),
|
|
ok;
|
|
false ->
|
|
Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
|
|
ErrText = <<"Room creation is denied by service policy">>,
|
|
Err = jlib:make_error_reply(
|
|
Packet, ?ERRT_FORBIDDEN(Lang, ErrText)),
|
|
ejabberd_router:route(To, From, Err)
|
|
end;
|
|
_ ->
|
|
Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
|
|
ErrText = <<"Conference room does not exist">>,
|
|
Err = jlib:make_error_reply(Packet,
|
|
?ERRT_ITEM_NOT_FOUND(Lang,
|
|
ErrText)),
|
|
ejabberd_router:route(To, From, Err)
|
|
end;
|
|
[R] ->
|
|
Pid = R#muc_online_room.pid,
|
|
?DEBUG("MUC: send to process ~p~n", [Pid]),
|
|
mod_muc_room:route(Pid, From, Nick, Packet),
|
|
ok
|
|
end
|
|
end.
|
|
|
|
check_user_can_create_room(ServerHost, AccessCreate,
|
|
From, RoomID) ->
|
|
case acl:match_rule(ServerHost, AccessCreate, From) of
|
|
allow ->
|
|
byte_size(RoomID) =<
|
|
gen_mod:get_module_opt(ServerHost, ?MODULE, max_room_id,
|
|
fun(infinity) -> infinity;
|
|
(I) when is_integer(I), I>0 -> I
|
|
end, infinity);
|
|
_ -> false
|
|
end.
|
|
|
|
get_rooms(ServerHost, Host) ->
|
|
LServer = jlib:nameprep(ServerHost),
|
|
get_rooms(LServer, Host,
|
|
gen_mod:db_type(LServer, ?MODULE)).
|
|
|
|
get_rooms(_LServer, Host, mnesia) ->
|
|
case catch mnesia:dirty_select(muc_room,
|
|
[{#muc_room{name_host = {'_', Host},
|
|
_ = '_'},
|
|
[], ['$_']}])
|
|
of
|
|
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), [];
|
|
Rs -> Rs
|
|
end;
|
|
get_rooms(_LServer, Host, riak) ->
|
|
case ejabberd_riak:get(muc_room, muc_room_schema()) of
|
|
{ok, Rs} ->
|
|
lists:filter(
|
|
fun(#muc_room{name_host = {_, H}}) ->
|
|
Host == H
|
|
end, Rs);
|
|
_Err ->
|
|
[]
|
|
end;
|
|
get_rooms(LServer, Host, odbc) ->
|
|
SHost = ejabberd_odbc:escape(Host),
|
|
case catch ejabberd_odbc:sql_query(LServer,
|
|
[<<"select name, opts from muc_room ">>,
|
|
<<"where host='">>, SHost, <<"';">>])
|
|
of
|
|
{selected, [<<"name">>, <<"opts">>], RoomOpts} ->
|
|
lists:map(fun ([Room, Opts]) ->
|
|
#muc_room{name_host = {Room, Host},
|
|
opts = opts_to_binary(
|
|
ejabberd_odbc:decode_term(Opts))}
|
|
end,
|
|
RoomOpts);
|
|
Err -> ?ERROR_MSG("failed to get rooms: ~p", [Err]), []
|
|
end.
|
|
|
|
load_permanent_rooms(Host, ServerHost, Access, HistorySize, RoomShaper) ->
|
|
lists:foreach(
|
|
fun(R) ->
|
|
{Room, Host} = R#muc_room.name_host,
|
|
case mnesia:dirty_read(muc_online_room, {Room, Host}) of
|
|
[] ->
|
|
{ok, Pid} = mod_muc_room:start(
|
|
Host,
|
|
ServerHost,
|
|
Access,
|
|
Room,
|
|
HistorySize,
|
|
RoomShaper,
|
|
R#muc_room.opts),
|
|
register_room(Host, Room, Pid);
|
|
_ ->
|
|
ok
|
|
end
|
|
end, get_rooms(ServerHost, Host)).
|
|
|
|
start_new_room(Host, ServerHost, Access, Room,
|
|
HistorySize, RoomShaper, From,
|
|
Nick, DefRoomOpts) ->
|
|
case restore_room(ServerHost, Host, Room) of
|
|
error ->
|
|
?DEBUG("MUC: open new room '~s'~n", [Room]),
|
|
mod_muc_room:start(Host, ServerHost, Access,
|
|
Room, HistorySize,
|
|
RoomShaper, From,
|
|
Nick, DefRoomOpts);
|
|
Opts ->
|
|
?DEBUG("MUC: restore room '~s'~n", [Room]),
|
|
mod_muc_room:start(Host, ServerHost, Access,
|
|
Room, HistorySize,
|
|
RoomShaper, Opts)
|
|
end.
|
|
|
|
register_room(Host, Room, Pid) ->
|
|
F = fun() ->
|
|
mnesia:write(#muc_online_room{name_host = {Room, Host},
|
|
pid = Pid})
|
|
end,
|
|
mnesia:transaction(F).
|
|
|
|
|
|
iq_disco_info(Lang) ->
|
|
[#xmlel{name = <<"identity">>,
|
|
attrs =
|
|
[{<<"category">>, <<"conference">>},
|
|
{<<"type">>, <<"text">>},
|
|
{<<"name">>,
|
|
translate:translate(Lang, <<"Chatrooms">>)}],
|
|
children = []},
|
|
#xmlel{name = <<"feature">>,
|
|
attrs = [{<<"var">>, ?NS_DISCO_INFO}], children = []},
|
|
#xmlel{name = <<"feature">>,
|
|
attrs = [{<<"var">>, ?NS_DISCO_ITEMS}], children = []},
|
|
#xmlel{name = <<"feature">>,
|
|
attrs = [{<<"var">>, ?NS_MUC}], children = []},
|
|
#xmlel{name = <<"feature">>,
|
|
attrs = [{<<"var">>, ?NS_MUC_UNIQUE}], children = []},
|
|
#xmlel{name = <<"feature">>,
|
|
attrs = [{<<"var">>, ?NS_REGISTER}], children = []},
|
|
#xmlel{name = <<"feature">>,
|
|
attrs = [{<<"var">>, ?NS_RSM}], children = []},
|
|
#xmlel{name = <<"feature">>,
|
|
attrs = [{<<"var">>, ?NS_VCARD}], children = []}].
|
|
|
|
iq_disco_items(Host, From, Lang, none) ->
|
|
lists:zf(fun (#muc_online_room{name_host =
|
|
{Name, _Host},
|
|
pid = Pid}) ->
|
|
case catch gen_fsm:sync_send_all_state_event(Pid,
|
|
{get_disco_item,
|
|
From, Lang},
|
|
100)
|
|
of
|
|
{item, Desc} ->
|
|
flush(),
|
|
{true,
|
|
#xmlel{name = <<"item">>,
|
|
attrs =
|
|
[{<<"jid">>,
|
|
jlib:jid_to_string({Name, Host,
|
|
<<"">>})},
|
|
{<<"name">>, Desc}],
|
|
children = []}};
|
|
_ -> false
|
|
end
|
|
end, get_vh_rooms(Host));
|
|
|
|
iq_disco_items(Host, From, Lang, Rsm) ->
|
|
{Rooms, RsmO} = get_vh_rooms(Host, Rsm),
|
|
RsmOut = jlib:rsm_encode(RsmO),
|
|
lists:zf(fun (#muc_online_room{name_host =
|
|
{Name, _Host},
|
|
pid = Pid}) ->
|
|
case catch gen_fsm:sync_send_all_state_event(Pid,
|
|
{get_disco_item,
|
|
From, Lang},
|
|
100)
|
|
of
|
|
{item, Desc} ->
|
|
flush(),
|
|
{true,
|
|
#xmlel{name = <<"item">>,
|
|
attrs =
|
|
[{<<"jid">>,
|
|
jlib:jid_to_string({Name, Host,
|
|
<<"">>})},
|
|
{<<"name">>, Desc}],
|
|
children = []}};
|
|
_ -> false
|
|
end
|
|
end,
|
|
Rooms)
|
|
++ RsmOut.
|
|
|
|
get_vh_rooms(Host, #rsm_in{max=M, direction=Direction, id=I, index=Index})->
|
|
AllRooms = lists:sort(get_vh_rooms(Host)),
|
|
Count = erlang:length(AllRooms),
|
|
Guard = case Direction of
|
|
_ when Index =/= undefined -> [{'==', {element, 2, '$1'}, Host}];
|
|
aft -> [{'==', {element, 2, '$1'}, Host}, {'>=',{element, 1, '$1'} ,I}];
|
|
before when I =/= []-> [{'==', {element, 2, '$1'}, Host}, {'=<',{element, 1, '$1'} ,I}];
|
|
_ -> [{'==', {element, 2, '$1'}, Host}]
|
|
end,
|
|
L = lists:sort(
|
|
mnesia:dirty_select(muc_online_room,
|
|
[{#muc_online_room{name_host = '$1', _ = '_'},
|
|
Guard,
|
|
['$_']}])),
|
|
L2 = if
|
|
Index == undefined andalso Direction == before ->
|
|
lists:reverse(lists:sublist(lists:reverse(L), 1, M));
|
|
Index == undefined ->
|
|
lists:sublist(L, 1, M);
|
|
Index > Count orelse Index < 0 ->
|
|
[];
|
|
true ->
|
|
lists:sublist(L, Index+1, M)
|
|
end,
|
|
if L2 == [] -> {L2, #rsm_out{count = Count}};
|
|
true ->
|
|
H = hd(L2),
|
|
NewIndex = get_room_pos(H, AllRooms),
|
|
T = lists:last(L2),
|
|
{F, _} = H#muc_online_room.name_host,
|
|
{Last, _} = T#muc_online_room.name_host,
|
|
{L2,
|
|
#rsm_out{first = F, last = Last, count = Count,
|
|
index = NewIndex}}
|
|
end.
|
|
|
|
%% @doc Return the position of desired room in the list of rooms.
|
|
%% The room must exist in the list. The count starts in 0.
|
|
%% @spec (Desired::muc_online_room(), Rooms::[muc_online_room()]) -> integer()
|
|
get_room_pos(Desired, Rooms) ->
|
|
get_room_pos(Desired, Rooms, 0).
|
|
|
|
get_room_pos(Desired, [HeadRoom | _], HeadPosition)
|
|
when Desired#muc_online_room.name_host ==
|
|
HeadRoom#muc_online_room.name_host ->
|
|
HeadPosition;
|
|
get_room_pos(Desired, [_ | Rooms], HeadPosition) ->
|
|
get_room_pos(Desired, Rooms, HeadPosition + 1).
|
|
|
|
flush() -> receive _ -> flush() after 0 -> ok end.
|
|
|
|
-define(XFIELD(Type, Label, Var, Val),
|
|
%% @doc Get a pseudo unique Room Name. The Room Name is generated as a hash of
|
|
%% the requester JID, the local time and a random salt.
|
|
%%
|
|
%% "pseudo" because we don't verify that there is not a room
|
|
%% with the returned Name already created, nor mark the generated Name
|
|
%% as "already used". But in practice, it is unique enough. See
|
|
%% http://xmpp.org/extensions/xep-0045.html#createroom-unique
|
|
#xmlel{name = <<"field">>,
|
|
attrs =
|
|
[{<<"type">>, Type},
|
|
{<<"label">>, translate:translate(Lang, Label)},
|
|
{<<"var">>, Var}],
|
|
children =
|
|
[#xmlel{name = <<"value">>, attrs = [],
|
|
children = [{xmlcdata, Val}]}]}).
|
|
|
|
iq_get_unique(From) ->
|
|
{xmlcdata,
|
|
p1_sha:sha(term_to_binary([From, now(),
|
|
randoms:get_string()]))}.
|
|
|
|
get_nick(ServerHost, Host, From) ->
|
|
LServer = jlib:nameprep(ServerHost),
|
|
get_nick(LServer, Host, From,
|
|
gen_mod:db_type(LServer, ?MODULE)).
|
|
|
|
get_nick(_LServer, Host, From, mnesia) ->
|
|
{LUser, LServer, _} = jlib:jid_tolower(From),
|
|
LUS = {LUser, LServer},
|
|
case catch mnesia:dirty_read(muc_registered,
|
|
{LUS, Host})
|
|
of
|
|
{'EXIT', _Reason} -> error;
|
|
[] -> error;
|
|
[#muc_registered{nick = Nick}] -> Nick
|
|
end;
|
|
get_nick(LServer, Host, From, riak) ->
|
|
{LUser, LServer, _} = jlib:jid_tolower(From),
|
|
US = {LUser, LServer},
|
|
case ejabberd_riak:get(muc_registered,
|
|
muc_registered_schema(),
|
|
{US, Host}) of
|
|
{ok, #muc_registered{nick = Nick}} -> Nick;
|
|
{error, _} -> error
|
|
end;
|
|
get_nick(LServer, Host, From, odbc) ->
|
|
SJID =
|
|
ejabberd_odbc:escape(jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From)))),
|
|
SHost = ejabberd_odbc:escape(Host),
|
|
case catch ejabberd_odbc:sql_query(LServer,
|
|
[<<"select nick from muc_registered where "
|
|
"jid='">>,
|
|
SJID, <<"' and host='">>, SHost,
|
|
<<"';">>])
|
|
of
|
|
{selected, [<<"nick">>], [[Nick]]} -> Nick;
|
|
_ -> error
|
|
end.
|
|
|
|
iq_get_register_info(ServerHost, Host, From, Lang) ->
|
|
{Nick, Registered} = case get_nick(ServerHost, Host,
|
|
From)
|
|
of
|
|
error -> {<<"">>, []};
|
|
N ->
|
|
{N,
|
|
[#xmlel{name = <<"registered">>, attrs = [],
|
|
children = []}]}
|
|
end,
|
|
Registered ++
|
|
[#xmlel{name = <<"instructions">>, attrs = [],
|
|
children =
|
|
[{xmlcdata,
|
|
translate:translate(Lang,
|
|
<<"You need a client that supports x:data "
|
|
"to register the nickname">>)}]},
|
|
#xmlel{name = <<"x">>,
|
|
attrs = [{<<"xmlns">>, ?NS_XDATA},
|
|
{<<"type">>, <<"form">>}],
|
|
children =
|
|
[#xmlel{name = <<"title">>, attrs = [],
|
|
children =
|
|
[{xmlcdata,
|
|
<<(translate:translate(Lang,
|
|
<<"Nickname Registration at ">>))/binary,
|
|
Host/binary>>}]},
|
|
#xmlel{name = <<"instructions">>, attrs = [],
|
|
children =
|
|
[{xmlcdata,
|
|
translate:translate(Lang,
|
|
<<"Enter nickname you want to register">>)}]},
|
|
?XFIELD(<<"text-single">>, <<"Nickname">>, <<"nick">>,
|
|
Nick)]}].
|
|
|
|
set_nick(ServerHost, Host, From, Nick) ->
|
|
LServer = jlib:nameprep(ServerHost),
|
|
set_nick(LServer, Host, From, Nick,
|
|
gen_mod:db_type(LServer, ?MODULE)).
|
|
|
|
set_nick(_LServer, Host, From, Nick, mnesia) ->
|
|
{LUser, LServer, _} = jlib:jid_tolower(From),
|
|
LUS = {LUser, LServer},
|
|
F = fun () ->
|
|
case Nick of
|
|
<<"">> ->
|
|
mnesia:delete({muc_registered, {LUS, Host}}), ok;
|
|
_ ->
|
|
Allow = case mnesia:select(muc_registered,
|
|
[{#muc_registered{us_host =
|
|
'$1',
|
|
nick = Nick,
|
|
_ = '_'},
|
|
[{'==', {element, 2, '$1'},
|
|
Host}],
|
|
['$_']}])
|
|
of
|
|
[] -> true;
|
|
[#muc_registered{us_host = {U, _Host}}] ->
|
|
U == LUS
|
|
end,
|
|
if Allow ->
|
|
mnesia:write(#muc_registered{us_host = {LUS, Host},
|
|
nick = Nick}),
|
|
ok;
|
|
true -> false
|
|
end
|
|
end
|
|
end,
|
|
mnesia:transaction(F);
|
|
set_nick(LServer, Host, From, Nick, riak) ->
|
|
{LUser, LServer, _} = jlib:jid_tolower(From),
|
|
LUS = {LUser, LServer},
|
|
{atomic,
|
|
case Nick of
|
|
<<"">> ->
|
|
ejabberd_riak:delete(muc_registered, {LUS, Host});
|
|
_ ->
|
|
Allow = case ejabberd_riak:get_by_index(
|
|
muc_registered,
|
|
muc_registered_schema(),
|
|
<<"nick_host">>, {Nick, Host}) of
|
|
{ok, []} ->
|
|
true;
|
|
{ok, [#muc_registered{us_host = {U, _Host}}]} ->
|
|
U == LUS;
|
|
{error, _} ->
|
|
false
|
|
end,
|
|
if Allow ->
|
|
ejabberd_riak:put(#muc_registered{us_host = {LUS, Host},
|
|
nick = Nick},
|
|
muc_registered_schema(),
|
|
[{'2i', [{<<"nick_host">>,
|
|
{Nick, Host}}]}]);
|
|
true ->
|
|
false
|
|
end
|
|
end};
|
|
set_nick(LServer, Host, From, Nick, odbc) ->
|
|
JID =
|
|
jlib:jid_to_string(jlib:jid_tolower(jlib:jid_remove_resource(From))),
|
|
SJID = ejabberd_odbc:escape(JID),
|
|
SNick = ejabberd_odbc:escape(Nick),
|
|
SHost = ejabberd_odbc:escape(Host),
|
|
F = fun () ->
|
|
case Nick of
|
|
<<"">> ->
|
|
ejabberd_odbc:sql_query_t([<<"delete from muc_registered where ">>,
|
|
<<"jid='">>, SJID,
|
|
<<"' and host='">>, Host,
|
|
<<"';">>]),
|
|
ok;
|
|
_ ->
|
|
Allow = case
|
|
ejabberd_odbc:sql_query_t([<<"select jid from muc_registered ">>,
|
|
<<"where nick='">>,
|
|
SNick,
|
|
<<"' and host='">>,
|
|
SHost, <<"';">>])
|
|
of
|
|
{selected, [<<"jid">>], [[J]]} -> J == JID;
|
|
_ -> true
|
|
end,
|
|
if Allow ->
|
|
odbc_queries:update_t(<<"muc_registered">>,
|
|
[<<"jid">>, <<"host">>,
|
|
<<"nick">>],
|
|
[SJID, SHost, SNick],
|
|
[<<"jid='">>, SJID,
|
|
<<"' and host='">>, SHost,
|
|
<<"'">>]),
|
|
ok;
|
|
true -> false
|
|
end
|
|
end
|
|
end,
|
|
ejabberd_odbc:sql_transaction(LServer, F).
|
|
|
|
iq_set_register_info(ServerHost, Host, From, Nick,
|
|
Lang) ->
|
|
case set_nick(ServerHost, Host, From, Nick) of
|
|
{atomic, ok} -> {result, []};
|
|
{atomic, false} ->
|
|
ErrText = <<"That nickname is registered by another "
|
|
"person">>,
|
|
{error, ?ERRT_CONFLICT(Lang, ErrText)};
|
|
_ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
|
|
end.
|
|
|
|
process_iq_register_set(ServerHost, Host, From, SubEl,
|
|
Lang) ->
|
|
#xmlel{children = Els} = SubEl,
|
|
case xml:get_subtag(SubEl, <<"remove">>) of
|
|
false ->
|
|
case xml:remove_cdata(Els) of
|
|
[#xmlel{name = <<"x">>} = XEl] ->
|
|
case {xml:get_tag_attr_s(<<"xmlns">>, XEl),
|
|
xml:get_tag_attr_s(<<"type">>, XEl)}
|
|
of
|
|
{?NS_XDATA, <<"cancel">>} -> {result, []};
|
|
{?NS_XDATA, <<"submit">>} ->
|
|
XData = jlib:parse_xdata_submit(XEl),
|
|
case XData of
|
|
invalid -> {error, ?ERR_BAD_REQUEST};
|
|
_ ->
|
|
case lists:keysearch(<<"nick">>, 1, XData) of
|
|
{value, {_, [Nick]}} when Nick /= <<"">> ->
|
|
iq_set_register_info(ServerHost, Host, From,
|
|
Nick, Lang);
|
|
_ ->
|
|
ErrText =
|
|
<<"You must fill in field \"Nickname\" "
|
|
"in the form">>,
|
|
{error, ?ERRT_NOT_ACCEPTABLE(Lang, ErrText)}
|
|
end
|
|
end;
|
|
_ -> {error, ?ERR_BAD_REQUEST}
|
|
end;
|
|
_ -> {error, ?ERR_BAD_REQUEST}
|
|
end;
|
|
_ ->
|
|
iq_set_register_info(ServerHost, Host, From, <<"">>,
|
|
Lang)
|
|
end.
|
|
|
|
iq_get_vcard(Lang) ->
|
|
[#xmlel{name = <<"FN">>, attrs = [],
|
|
children = [{xmlcdata, <<"ejabberd/mod_muc">>}]},
|
|
#xmlel{name = <<"URL">>, attrs = [],
|
|
children = [{xmlcdata, ?EJABBERD_URI}]},
|
|
#xmlel{name = <<"DESC">>, attrs = [],
|
|
children =
|
|
[{xmlcdata,
|
|
<<(translate:translate(Lang,
|
|
<<"ejabberd MUC module">>))/binary,
|
|
"\nCopyright (c) 2003-2015 ProcessOne">>}]}].
|
|
|
|
|
|
broadcast_service_message(Host, Msg) ->
|
|
lists:foreach(
|
|
fun(#muc_online_room{pid = Pid}) ->
|
|
gen_fsm:send_all_state_event(
|
|
Pid, {service_message, Msg})
|
|
end, get_vh_rooms(Host)).
|
|
|
|
|
|
get_vh_rooms(Host) ->
|
|
mnesia:dirty_select(muc_online_room,
|
|
[{#muc_online_room{name_host = '$1', _ = '_'},
|
|
[{'==', {element, 2, '$1'}, Host}],
|
|
['$_']}]).
|
|
|
|
|
|
clean_table_from_bad_node(Node) ->
|
|
F = fun() ->
|
|
Es = mnesia:select(
|
|
muc_online_room,
|
|
[{#muc_online_room{pid = '$1', _ = '_'},
|
|
[{'==', {node, '$1'}, Node}],
|
|
['$_']}]),
|
|
lists:foreach(fun(E) ->
|
|
mnesia:delete_object(E)
|
|
end, Es)
|
|
end,
|
|
mnesia:async_dirty(F).
|
|
|
|
clean_table_from_bad_node(Node, Host) ->
|
|
F = fun() ->
|
|
Es = mnesia:select(
|
|
muc_online_room,
|
|
[{#muc_online_room{pid = '$1',
|
|
name_host = {'_', Host},
|
|
_ = '_'},
|
|
[{'==', {node, '$1'}, Node}],
|
|
['$_']}]),
|
|
lists:foreach(fun(E) ->
|
|
mnesia:delete_object(E)
|
|
end, Es)
|
|
end,
|
|
mnesia:async_dirty(F).
|
|
|
|
opts_to_binary(Opts) ->
|
|
lists:map(
|
|
fun({title, Title}) ->
|
|
{title, iolist_to_binary(Title)};
|
|
({description, Desc}) ->
|
|
{description, iolist_to_binary(Desc)};
|
|
({password, Pass}) ->
|
|
{password, iolist_to_binary(Pass)};
|
|
({subject, Subj}) ->
|
|
{subject, iolist_to_binary(Subj)};
|
|
({subject_author, Author}) ->
|
|
{subject_author, iolist_to_binary(Author)};
|
|
({affiliations, Affs}) ->
|
|
{affiliations, lists:map(
|
|
fun({{U, S, R}, Aff}) ->
|
|
NewAff =
|
|
case Aff of
|
|
{A, Reason} ->
|
|
{A, iolist_to_binary(Reason)};
|
|
_ ->
|
|
Aff
|
|
end,
|
|
{{iolist_to_binary(U),
|
|
iolist_to_binary(S),
|
|
iolist_to_binary(R)},
|
|
NewAff}
|
|
end, Affs)};
|
|
({captcha_whitelist, CWList}) ->
|
|
{captcha_whitelist, lists:map(
|
|
fun({U, S, R}) ->
|
|
{iolist_to_binary(U),
|
|
iolist_to_binary(S),
|
|
iolist_to_binary(R)}
|
|
end, CWList)};
|
|
(Opt) ->
|
|
Opt
|
|
end, Opts).
|
|
|
|
update_tables(Host) ->
|
|
update_muc_room_table(Host),
|
|
update_muc_registered_table(Host).
|
|
|
|
muc_room_schema() ->
|
|
{record_info(fields, muc_room), #muc_room{}}.
|
|
|
|
muc_registered_schema() ->
|
|
{record_info(fields, muc_registered), #muc_registered{}}.
|
|
|
|
update_muc_room_table(_Host) ->
|
|
Fields = record_info(fields, muc_room),
|
|
case mnesia:table_info(muc_room, attributes) of
|
|
Fields ->
|
|
ejabberd_config:convert_table_to_binary(
|
|
muc_room, Fields, set,
|
|
fun(#muc_room{name_host = {N, _}}) -> N end,
|
|
fun(#muc_room{name_host = {N, H},
|
|
opts = Opts} = R) ->
|
|
R#muc_room{name_host = {iolist_to_binary(N),
|
|
iolist_to_binary(H)},
|
|
opts = opts_to_binary(Opts)}
|
|
end);
|
|
_ ->
|
|
?INFO_MSG("Recreating muc_room table", []),
|
|
mnesia:transform_table(muc_room, ignore, Fields)
|
|
end.
|
|
|
|
update_muc_registered_table(_Host) ->
|
|
Fields = record_info(fields, muc_registered),
|
|
case mnesia:table_info(muc_registered, attributes) of
|
|
Fields ->
|
|
ejabberd_config:convert_table_to_binary(
|
|
muc_registered, Fields, set,
|
|
fun(#muc_registered{us_host = {_, H}}) -> H end,
|
|
fun(#muc_registered{us_host = {{U, S}, H},
|
|
nick = Nick} = R) ->
|
|
R#muc_registered{us_host = {{iolist_to_binary(U),
|
|
iolist_to_binary(S)},
|
|
iolist_to_binary(H)},
|
|
nick = iolist_to_binary(Nick)}
|
|
end);
|
|
_ ->
|
|
?INFO_MSG("Recreating muc_registered table", []),
|
|
mnesia:transform_table(muc_registered, ignore, Fields)
|
|
end.
|
|
|
|
export(_Server) ->
|
|
[{muc_room,
|
|
fun(Host, #muc_room{name_host = {Name, RoomHost}, opts = Opts}) ->
|
|
case str:suffix(Host, RoomHost) of
|
|
true ->
|
|
SName = ejabberd_odbc:escape(Name),
|
|
SRoomHost = ejabberd_odbc:escape(RoomHost),
|
|
SOpts = ejabberd_odbc:encode_term(Opts),
|
|
[[<<"delete from muc_room where name='">>, SName,
|
|
<<"' and host='">>, SRoomHost, <<"';">>],
|
|
[<<"insert into muc_room(name, host, opts) ",
|
|
"values (">>,
|
|
<<"'">>, SName, <<"', '">>, SRoomHost,
|
|
<<"', '">>, SOpts, <<"');">>]];
|
|
false ->
|
|
[]
|
|
end
|
|
end},
|
|
{muc_registered,
|
|
fun(Host, #muc_registered{us_host = {{U, S}, RoomHost},
|
|
nick = Nick}) ->
|
|
case str:suffix(Host, RoomHost) of
|
|
true ->
|
|
SJID = ejabberd_odbc:escape(
|
|
jlib:jid_to_string(
|
|
jlib:make_jid(U, S, <<"">>))),
|
|
SNick = ejabberd_odbc:escape(Nick),
|
|
SRoomHost = ejabberd_odbc:escape(RoomHost),
|
|
[[<<"delete from muc_registered where jid='">>,
|
|
SJID, <<"' and host='">>, SRoomHost, <<"';">>],
|
|
[<<"insert into muc_registered(jid, host, "
|
|
"nick) values ('">>,
|
|
SJID, <<"', '">>, SRoomHost, <<"', '">>, SNick,
|
|
<<"');">>]];
|
|
false ->
|
|
[]
|
|
end
|
|
end}].
|
|
|
|
import(_LServer) ->
|
|
[{<<"select name, host, opts from muc_room;">>,
|
|
fun([Name, RoomHost, SOpts]) ->
|
|
Opts = opts_to_binary(ejabberd_odbc:decode_term(SOpts)),
|
|
#muc_room{name_host = {Name, RoomHost},
|
|
opts = Opts}
|
|
end},
|
|
{<<"select jid, host, nick from muc_registered;">>,
|
|
fun([J, RoomHost, Nick]) ->
|
|
#jid{user = U, server = S} =
|
|
jlib:string_to_jid(J),
|
|
#muc_registered{us_host = {{U, S}, RoomHost},
|
|
nick = Nick}
|
|
end}].
|
|
|
|
import(_LServer, mnesia, #muc_room{} = R) ->
|
|
mnesia:dirty_write(R);
|
|
import(_LServer, mnesia, #muc_registered{} = R) ->
|
|
mnesia:dirty_write(R);
|
|
import(_LServer, riak, #muc_room{} = R) ->
|
|
ejabberd_riak:put(R, muc_room_schema());
|
|
import(_LServer, riak,
|
|
#muc_registered{us_host = {_, Host}, nick = Nick} = R) ->
|
|
ejabberd_riak:put(R, muc_registered_schema(),
|
|
[{'2i', [{<<"nick_host">>, {Nick, Host}}]}]);
|
|
import(_, _, _) ->
|
|
pass.
|