aboutsummaryrefslogtreecommitdiff
path: root/src/mod_muc.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_muc.erl')
-rw-r--r--src/mod_muc.erl1837
1 files changed, 1056 insertions, 781 deletions
diff --git a/src/mod_muc.erl b/src/mod_muc.erl
index 571f85926..afbefa28a 100644
--- a/src/mod_muc.erl
+++ b/src/mod_muc.erl
@@ -5,7 +5,7 @@
%%% Created : 19 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
-%%% ejabberd, Copyright (C) 2002-2016 ProcessOne
+%%% ejabberd, Copyright (C) 2002-2019 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@@ -22,126 +22,285 @@
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-
-module(mod_muc).
-
-author('alexey@process-one.net').
-
-protocol({xep, 45, '1.25'}).
-
--behaviour(gen_server).
-
+-ifndef(GEN_SERVER).
+-define(GEN_SERVER, gen_server).
+-endif.
+-behaviour(?GEN_SERVER).
-behaviour(gen_mod).
%% API
--export([start_link/2,
- start/2,
+-export([start/2,
stop/1,
+ start_link/2,
+ reload/3,
room_destroyed/4,
store_room/4,
+ store_room/5,
restore_room/3,
forget_room/3,
create_room/5,
shutdown_rooms/1,
- process_iq_disco_items/4,
- broadcast_service_message/2,
+ process_disco_info/1,
+ process_disco_items/1,
+ process_vcard/1,
+ process_register/1,
+ process_muc_unique/1,
+ process_mucsub/1,
+ broadcast_service_message/3,
export/1,
- import/1,
- import/3,
+ import_info/0,
+ import/5,
+ import_start/2,
opts_to_binary/1,
- can_use_nick/4]).
+ find_online_room/2,
+ register_online_room/3,
+ get_online_rooms/1,
+ count_online_rooms/1,
+ register_online_user/4,
+ unregister_online_user/4,
+ iq_set_register_info/5,
+ count_online_rooms_by_user/3,
+ get_online_rooms_by_user/3,
+ can_use_nick/4,
+ get_subscribed_rooms/2,
+ procname/2,
+ route/1]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3,
- mod_opt_type/1, depends/2]).
+ mod_opt_type/1, mod_options/1, depends/2]).
--include("ejabberd.hrl").
-include("logger.hrl").
-
--include("jlib.hrl").
+-include("xmpp.hrl").
-include("mod_muc.hrl").
-
--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).
-
--define(MAX_ROOMS_DISCOITEMS, 100).
-
+-include("mod_muc_room.hrl").
+-include("translate.hrl").
+-include("ejabberd_stacktrace.hrl").
+
+-type state() :: #{hosts := [binary()],
+ server_host := binary(),
+ worker := pos_integer()}.
+-type access() :: {acl:acl(), acl:acl(), acl:acl(), acl:acl(), acl:acl()}.
-type muc_room_opts() :: [{atom(), any()}].
+-export_type([access/0]).
-callback init(binary(), gen_mod:opts()) -> any().
--callback import(binary(), #muc_room{} | #muc_registered{}) -> ok | pass.
--callback store_room(binary(), binary(), binary(), list()) -> {atomic, any()}.
+-callback import(binary(), binary(), [binary()]) -> ok.
+-callback store_room(binary(), binary(), binary(), list(), list()|undefined) -> {atomic, any()}.
-callback restore_room(binary(), binary(), binary()) -> muc_room_opts() | error.
-callback forget_room(binary(), binary(), binary()) -> {atomic, any()}.
-callback can_use_nick(binary(), binary(), jid(), binary()) -> boolean().
-callback get_rooms(binary(), binary()) -> [#muc_room{}].
-callback get_nick(binary(), binary(), jid()) -> binary() | error.
-callback set_nick(binary(), binary(), jid(), binary()) -> {atomic, ok | false}.
+-callback register_online_room(binary(), binary(), binary(), pid()) -> any().
+-callback unregister_online_room(binary(), binary(), binary(), pid()) -> any().
+-callback find_online_room(binary(), binary(), binary()) -> {ok, pid()} | error.
+-callback get_online_rooms(binary(), binary(), undefined | rsm_set()) -> [{binary(), binary(), pid()}].
+-callback count_online_rooms(binary(), binary()) -> non_neg_integer().
+-callback rsm_supported() -> boolean().
+-callback register_online_user(binary(), ljid(), binary(), binary()) -> any().
+-callback unregister_online_user(binary(), ljid(), binary(), binary()) -> any().
+-callback count_online_rooms_by_user(binary(), binary(), binary()) -> non_neg_integer().
+-callback get_online_rooms_by_user(binary(), binary(), binary()) -> [{binary(), binary()}].
+-callback get_subscribed_rooms(binary(), binary(), jid()) ->
+ {ok, [{jid(), [binary()]}]} | {error, db_failure}.
+
+-optional_callbacks([get_subscribed_rooms/3]).
%%====================================================================
%% API
%%====================================================================
-start_link(Host, Opts) ->
- Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- gen_server:start_link({local, Proc}, ?MODULE,
- [Host, Opts], []).
-
start(Host, Opts) ->
- Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]},
- transient, 1000, worker, [?MODULE]},
- supervisor:start_child(ejabberd_sup, ChildSpec).
+ case mod_muc_sup:start(Host) of
+ {ok, _} ->
+ MyHosts = gen_mod:get_opt_hosts(Opts),
+ Mod = gen_mod:db_mod(Opts, ?MODULE),
+ RMod = gen_mod:ram_db_mod(Opts, ?MODULE),
+ Mod:init(Host, gen_mod:set_opt(hosts, MyHosts, Opts)),
+ RMod:init(Host, gen_mod:set_opt(hosts, MyHosts, Opts)),
+ load_permanent_rooms(MyHosts, Host, Opts);
+ Err ->
+ Err
+ end.
stop(Host) ->
- Rooms = shutdown_rooms(Host),
- Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
- gen_server:call(Proc, stop),
- supervisor:delete_child(ejabberd_sup, Proc),
- {wait, Rooms}.
+ Proc = mod_muc_sup:procname(Host),
+ supervisor:terminate_child(ejabberd_gen_mod_sup, Proc),
+ supervisor:delete_child(ejabberd_gen_mod_sup, Proc).
+
+-spec reload(binary(), gen_mod:opts(), gen_mod:opts()) -> ok.
+reload(ServerHost, NewOpts, OldOpts) ->
+ NewMod = gen_mod:db_mod(NewOpts, ?MODULE),
+ NewRMod = gen_mod:ram_db_mod(NewOpts, ?MODULE),
+ OldMod = gen_mod:db_mod(OldOpts, ?MODULE),
+ OldRMod = gen_mod:ram_db_mod(OldOpts, ?MODULE),
+ NewHosts = gen_mod:get_opt_hosts(NewOpts),
+ OldHosts = gen_mod:get_opt_hosts(OldOpts),
+ AddHosts = NewHosts -- OldHosts,
+ DelHosts = OldHosts -- NewHosts,
+ if NewMod /= OldMod ->
+ NewMod:init(ServerHost, gen_mod:set_opt(hosts, NewHosts, NewOpts));
+ true ->
+ ok
+ end,
+ if NewRMod /= OldRMod ->
+ NewRMod:init(ServerHost, gen_mod:set_opt(hosts, NewHosts, NewOpts));
+ true ->
+ ok
+ end,
+ lists:foreach(
+ fun(I) ->
+ ?GEN_SERVER:cast(procname(ServerHost, I),
+ {reload, AddHosts, DelHosts, NewHosts})
+ end, lists:seq(1, erlang:system_info(logical_processors))),
+ load_permanent_rooms(AddHosts, ServerHost, NewOpts),
+ shutdown_rooms(ServerHost, DelHosts, OldRMod),
+ lists:foreach(
+ fun(Host) ->
+ lists:foreach(
+ fun({_, _, Pid}) when node(Pid) == node() ->
+ mod_muc_room:config_reloaded(Pid);
+ (_) ->
+ ok
+ end, get_online_rooms(ServerHost, Host))
+ end, misc:intersection(NewHosts, OldHosts)).
depends(_Host, _Opts) ->
[{mod_mam, soft}].
-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},
- {'==', {node, '$2'}, node()}],
- ['$2']}]),
- [Pid ! shutdown || Pid <- Rooms],
- Rooms.
+start_link(Host, I) ->
+ Proc = procname(Host, I),
+ ?GEN_SERVER:start_link({local, Proc}, ?MODULE, [Host, I],
+ ejabberd_config:fsm_limit_opts([])).
+
+-spec procname(binary(), pos_integer() | {binary(), binary()}) -> atom().
+procname(Host, I) when is_integer(I) ->
+ binary_to_atom(
+ <<(atom_to_binary(?MODULE, latin1))/binary, "_", Host/binary,
+ "_", (integer_to_binary(I))/binary>>, utf8);
+procname(Host, RoomHost) ->
+ Cores = erlang:system_info(logical_processors),
+ I = erlang:phash2(RoomHost, Cores) + 1,
+ procname(Host, I).
+
+-spec route(stanza()) -> ok.
+route(Pkt) ->
+ To = xmpp:get_to(Pkt),
+ ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
+ route(Pkt, ServerHost).
+
+-spec route(stanza(), binary()) -> ok.
+route(Pkt, ServerHost) ->
+ From = xmpp:get_from(Pkt),
+ To = xmpp:get_to(Pkt),
+ Host = To#jid.lserver,
+ Access = mod_muc_opt:access(ServerHost),
+ case acl:match_rule(ServerHost, Access, From) of
+ allow ->
+ route(Pkt, Host, ServerHost);
+ deny ->
+ Lang = xmpp:get_lang(Pkt),
+ ErrText = ?T("Access denied by service policy"),
+ Err = xmpp:err_forbidden(ErrText, Lang),
+ ejabberd_router:route_error(Pkt, Err)
+ end.
+
+-spec route(stanza(), binary(), binary()) -> ok.
+route(#iq{to = #jid{luser = <<"">>, lresource = <<"">>}} = IQ, _, _) ->
+ ejabberd_router:process_iq(IQ);
+route(#message{lang = Lang, body = Body, type = Type, from = From,
+ to = #jid{luser = <<"">>, lresource = <<"">>}} = Pkt,
+ Host, ServerHost) ->
+ if Type == error ->
+ ok;
+ true ->
+ AccessAdmin = mod_muc_opt:access_admin(ServerHost),
+ case acl:match_rule(ServerHost, AccessAdmin, From) of
+ allow ->
+ Msg = xmpp:get_text(Body),
+ broadcast_service_message(ServerHost, Host, Msg);
+ deny ->
+ ErrText = ?T("Only service administrators are allowed "
+ "to send service messages"),
+ Err = xmpp:err_forbidden(ErrText, Lang),
+ ejabberd_router:route_error(Pkt, Err)
+ end
+ end;
+route(Pkt, Host, ServerHost) ->
+ {Room, _, _} = jid:tolower(xmpp:get_to(Pkt)),
+ case Room of
+ <<"">> ->
+ Txt = ?T("No module is handling this query"),
+ Err = xmpp:err_service_unavailable(Txt, xmpp:get_lang(Pkt)),
+ ejabberd_router:route_error(Pkt, Err);
+ _ ->
+ RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
+ case RMod:find_online_room(ServerHost, Room, Host) of
+ error ->
+ Proc = procname(ServerHost, {Room, Host}),
+ case whereis(Proc) of
+ Pid when Pid == self() ->
+ route_to_room(Pkt, ServerHost);
+ Pid when is_pid(Pid) ->
+ ?DEBUG("Routing to MUC worker ~p:~n~ts", [Proc, xmpp:pp(Pkt)]),
+ ?GEN_SERVER:cast(Pid, {route_to_room, Pkt});
+ undefined ->
+ ?DEBUG("MUC worker ~p is dead", [Proc]),
+ Err = xmpp:err_internal_server_error(),
+ ejabberd_router:route_error(Pkt, Err)
+ end;
+ {ok, Pid} ->
+ mod_muc_room:route(Pid, Pkt)
+ end
+ end.
+
+-spec shutdown_rooms(binary()) -> [pid()].
+shutdown_rooms(ServerHost) ->
+ RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
+ Hosts = gen_mod:get_module_opt_hosts(ServerHost, mod_muc),
+ shutdown_rooms(ServerHost, Hosts, RMod).
+
+-spec shutdown_rooms(binary(), [binary()], module()) -> [pid()].
+shutdown_rooms(ServerHost, Hosts, RMod) ->
+ Rooms = [RMod:get_online_rooms(ServerHost, Host, undefined)
+ || Host <- Hosts],
+ lists:flatmap(
+ fun({_, _, Pid}) when node(Pid) == node() ->
+ mod_muc_room:shutdown(Pid),
+ [Pid];
+ (_) ->
+ []
+ end, lists:flatten(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
+%% So the message sending must be caught
+-spec room_destroyed(binary(), binary(), pid(), binary()) -> ok.
room_destroyed(Host, Room, Pid, ServerHost) ->
- catch gen_mod:get_module_proc(ServerHost, ?PROCNAME) !
- {room_destroyed, {Room, Host}, Pid},
- ok.
+ Proc = procname(ServerHost, {Room, Host}),
+ ?GEN_SERVER:cast(Proc, {room_destroyed, {Room, Host}, Pid}).
%% @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}).
+ ServerHost = ejabberd_router:host_of_route(Host),
+ Proc = procname(ServerHost, {Name, Host}),
+ ?GEN_SERVER:call(Proc, {create, Name, Host, From, Nick, Opts}).
store_room(ServerHost, Host, Name, Opts) ->
+ store_room(ServerHost, Host, Name, Opts, undefined).
+
+store_room(ServerHost, Host, Name, Opts, ChangesHints) ->
LServer = jid:nameprep(ServerHost),
Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:store_room(LServer, Host, Name, Opts).
+ Mod:store_room(LServer, Host, Name, Opts, ChangesHints).
restore_room(ServerHost, Host, Name) ->
LServer = jid:nameprep(ServerHost),
@@ -154,597 +313,678 @@ forget_room(ServerHost, Host, Name) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:forget_room(LServer, Host, Name).
-process_iq_disco_items(Host, From, To,
- #iq{lang = Lang} = IQ) ->
- Rsm = jlib:rsm_decode(IQ),
- DiscoNode = fxml:get_tag_attr_s(<<"node">>, IQ#iq.sub_el),
- Res = IQ#iq{type = result,
- sub_el =
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}],
- children = iq_disco_items(Host, From, Lang, DiscoNode, 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 = jid:nameprep(ServerHost),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:can_use_nick(LServer, Host, JID, Nick).
+-spec find_online_room(binary(), binary()) -> {ok, pid()} | error.
+find_online_room(Room, Host) ->
+ ServerHost = ejabberd_router:host_of_route(Host),
+ RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
+ RMod:find_online_room(ServerHost, Room, Host).
+
+-spec register_online_room(binary(), binary(), pid()) -> any().
+register_online_room(Room, Host, Pid) ->
+ ServerHost = ejabberd_router:host_of_route(Host),
+ RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
+ RMod:register_online_room(ServerHost, Room, Host, Pid).
+
+-spec get_online_rooms(binary()) -> [{binary(), binary(), pid()}].
+get_online_rooms(Host) ->
+ ServerHost = ejabberd_router:host_of_route(Host),
+ get_online_rooms(ServerHost, Host).
+
+-spec count_online_rooms(binary()) -> non_neg_integer().
+count_online_rooms(Host) ->
+ ServerHost = ejabberd_router:host_of_route(Host),
+ count_online_rooms(ServerHost, Host).
+
+-spec register_online_user(binary(), ljid(), binary(), binary()) -> any().
+register_online_user(ServerHost, LJID, Name, Host) ->
+ RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
+ RMod:register_online_user(ServerHost, LJID, Name, Host).
+
+-spec unregister_online_user(binary(), ljid(), binary(), binary()) -> any().
+unregister_online_user(ServerHost, LJID, Name, Host) ->
+ RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
+ RMod:unregister_online_user(ServerHost, LJID, Name, Host).
+
+-spec count_online_rooms_by_user(binary(), binary(), binary()) -> non_neg_integer().
+count_online_rooms_by_user(ServerHost, LUser, LServer) ->
+ RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
+ RMod:count_online_rooms_by_user(ServerHost, LUser, LServer).
+
+-spec get_online_rooms_by_user(binary(), binary(), binary()) -> [{binary(), binary()}].
+get_online_rooms_by_user(ServerHost, LUser, LServer) ->
+ RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
+ RMod:get_online_rooms_by_user(ServerHost, LUser, LServer).
+
%%====================================================================
%% gen_server callbacks
%%====================================================================
-
-init([Host, Opts]) ->
- MyHost = gen_mod:get_opt_host(Host, Opts,
- <<"conference.@HOST@">>),
- Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
- Mod:init(Host, [{host, MyHost}|Opts]),
- 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 acl:access_rules_validator/1, all),
- AccessCreate = gen_mod:get_opt(access_create, Opts,
- fun acl:access_rules_validator/1, all),
- AccessAdmin = gen_mod:get_opt(access_admin, Opts,
- fun acl:access_rules_validator/1,
- none),
- AccessPersistent = gen_mod:get_opt(access_persistent, Opts,
- fun acl:access_rules_validator/1,
- all),
- HistorySize = gen_mod:get_opt(history_size, Opts,
- fun(I) when is_integer(I), I>=0 -> I end,
- 20),
- DefRoomOpts1 = gen_mod:get_opt(default_room_options, Opts,
- fun(L) when is_list(L) -> L end,
- []),
- DefRoomOpts =
- lists:flatmap(
- fun({Opt, Val}) ->
- Bool = fun(B) when is_boolean(B) -> B end,
- VFun = case Opt of
- allow_change_subj -> Bool;
- allow_private_messages -> Bool;
- allow_query_users -> Bool;
- allow_user_invites -> Bool;
- allow_visitor_nickchange -> Bool;
- allow_visitor_status -> Bool;
- anonymous -> Bool;
- captcha_protected -> Bool;
- logging -> Bool;
- members_by_default -> Bool;
- members_only -> Bool;
- moderated -> Bool;
- password_protected -> Bool;
- persistent -> Bool;
- public -> Bool;
- public_list -> Bool;
- mam -> Bool;
- allow_subscription -> Bool;
- password -> fun iolist_to_binary/1;
- title -> fun iolist_to_binary/1;
- allow_private_messages_from_visitors ->
- fun(anyone) -> anyone;
- (moderators) -> moderators;
- (nobody) -> nobody
- end;
- max_users ->
- fun(I) when is_integer(I), I > 0 -> I end;
- presence_broadcast ->
- fun(L) ->
- lists:map(
- fun(moderator) -> moderator;
- (participant) -> participant;
- (visitor) -> visitor
- end, L)
- end;
- _ ->
- ?ERROR_MSG("unknown option ~p with value ~p",
- [Opt, Val]),
- fun(_) -> undefined end
- end,
- case gen_mod:get_opt(Opt, [{Opt, Val}], VFun) of
- undefined -> [];
- NewVal -> [{Opt, NewVal}]
- end
- end, DefRoomOpts1),
- RoomShaper = gen_mod:get_opt(room_shaper, Opts,
- fun(A) when is_atom(A) -> A end,
- none),
- ejabberd_router:register_route(MyHost, Host),
- 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}}.
-
+-spec init(list()) -> {ok, state()}.
+init([Host, Worker]) ->
+ process_flag(trap_exit, true),
+ Opts = gen_mod:get_module_opts(Host, ?MODULE),
+ MyHosts = gen_mod:get_opt_hosts(Opts),
+ register_routes(Host, MyHosts, Worker),
+ register_iq_handlers(MyHosts, Worker),
+ {ok, #{server_host => Host, hosts => MyHosts, worker => Worker}}.
+
+-spec handle_call(term(), {pid(), term()}, state()) ->
+ {reply, ok | {ok, pid()} | {error, any()}, state()} |
+ {stop, normal, ok, state()}.
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]),
+handle_call({create, Room, Host, From, Nick, Opts}, _From,
+ #{server_host := ServerHost} = State) ->
+ ?DEBUG("MUC: create new room '~ts'~n", [Room]),
NewOpts = case Opts of
- default -> DefOpts;
- _ -> Opts
+ default -> mod_muc_opt:default_room_options(ServerHost);
+ _ -> 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}.
-
-handle_cast(_Msg, State) -> {noreply, State}.
-
-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
+ RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
+ case start_room(RMod, Host, ServerHost, Room, NewOpts, From, Nick) of
+ {ok, _} ->
+ ejabberd_hooks:run(create_room, ServerHost, [ServerHost, Room, Host]),
+ {reply, ok, State};
+ Err ->
+ {reply, Err, State}
+ end.
+
+-spec handle_cast(term(), state()) -> {noreply, state()}.
+handle_cast({route_to_room, Packet}, #{server_host := ServerHost} = State) ->
+ try route_to_room(Packet, ServerHost)
+ catch ?EX_RULE(Class, Reason, St) ->
+ StackTrace = ?EX_STACK(St),
+ ?ERROR_MSG("Failed to route packet:~n~ts~n** ~ts",
+ [xmpp:pp(Packet),
+ misc:format_exception(2, Class, Reason, StackTrace)])
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),
+handle_cast({room_destroyed, {Room, Host}, Pid},
+ #{server_host := ServerHost} = State) ->
+ RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
+ RMod:unregister_online_room(ServerHost, Room, Host, Pid),
{noreply, State};
-handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
- clean_table_from_bad_node(Node),
+handle_cast({reload, AddHosts, DelHosts, NewHosts},
+ #{server_host := ServerHost, worker := Worker} = State) ->
+ register_routes(ServerHost, AddHosts, Worker),
+ register_iq_handlers(AddHosts, Worker),
+ unregister_routes(DelHosts, Worker),
+ unregister_iq_handlers(DelHosts, Worker),
+ {noreply, State#{hosts => NewHosts}};
+handle_cast(Msg, State) ->
+ ?WARNING_MSG("Unexpected cast: ~p", [Msg]),
+ {noreply, State}.
+
+-spec handle_info(term(), state()) -> {noreply, state()}.
+handle_info({route, Packet}, #{server_host := ServerHost} = State) ->
+ %% We can only receive the packet here from other nodes
+ %% where mod_muc is not loaded. Such configuration
+ %% is *highly* discouraged
+ try route(Packet, ServerHost)
+ catch ?EX_RULE(Class, Reason, St) ->
+ StackTrace = ?EX_STACK(St),
+ ?ERROR_MSG("Failed to route packet:~n~ts~n** ~ts",
+ [xmpp:pp(Packet),
+ misc:format_exception(2, Class, Reason, StackTrace)])
+ end,
{noreply, State};
-handle_info(_Info, State) -> {noreply, State}.
-
-terminate(_Reason, State) ->
- ejabberd_router:unregister_route(State#state.host),
- ok.
-
+handle_info({room_destroyed, {Room, Host}, Pid}, State) ->
+ %% For backward compat
+ handle_cast({room_destroyed, {Room, Host}, Pid}, State);
+handle_info(Info, State) ->
+ ?ERROR_MSG("Unexpected info: ~p", [Info]),
+ {noreply, State}.
+
+-spec terminate(term(), state()) -> any().
+terminate(_Reason, #{hosts := Hosts, worker := Worker}) ->
+ unregister_routes(Hosts, Worker),
+ unregister_iq_handlers(Hosts, Worker).
+
+-spec code_change(term(), state(), term()) -> {ok, state()}.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+-spec register_iq_handlers([binary()], pos_integer()) -> ok.
+register_iq_handlers(Hosts, 1) ->
+ %% Only register handlers on first worker
+ lists:foreach(
+ fun(Host) ->
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_REGISTER,
+ ?MODULE, process_register),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_VCARD,
+ ?MODULE, process_vcard),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_MUCSUB,
+ ?MODULE, process_mucsub),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_MUC_UNIQUE,
+ ?MODULE, process_muc_unique),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO,
+ ?MODULE, process_disco_info),
+ gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS,
+ ?MODULE, process_disco_items)
+ end, Hosts);
+register_iq_handlers(_, _) ->
+ ok.
-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 = fxml: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.
+-spec unregister_iq_handlers([binary()], pos_integer()) -> ok.
+unregister_iq_handlers(Hosts, 1) ->
+ %% Only unregister handlers on first worker
+ lists:foreach(
+ fun(Host) ->
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_REGISTER),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_VCARD),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MUCSUB),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_MUC_UNIQUE),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_INFO),
+ gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_DISCO_ITEMS)
+ end, Hosts);
+unregister_iq_handlers(_, _) ->
+ ok.
+
+-spec register_routes(binary(), [binary()], pos_integer()) -> ok.
+register_routes(ServerHost, Hosts, 1) ->
+ %% Only register routes on first worker
+ lists:foreach(
+ fun(Host) ->
+ ejabberd_router:register_route(
+ Host, ServerHost, {apply, ?MODULE, route})
+ end, Hosts);
+register_routes(_, _, _) ->
+ ok.
+-spec unregister_routes([binary()], pos_integer()) -> ok.
+unregister_routes(Hosts, 1) ->
+ %% Only unregister routes on first worker
+ lists:foreach(
+ fun(Host) ->
+ ejabberd_router:unregister_route(Host)
+ end, Hosts);
+unregister_routes(_, _) ->
+ ok.
-do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
- From, To, Packet, DefRoomOpts) ->
- {_AccessRoute, AccessCreate, AccessAdmin, _AccessPersistent} = Access,
- {Room, _, Nick} = 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(
- ServerHost, 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_MUCSUB,
- sub_el = #xmlel{name = <<"subscriptions">>} = SubEl} = IQ ->
- RoomJIDs = get_subscribed_rooms(ServerHost, Host, From),
- Subs = lists:map(
- fun(J) ->
- #xmlel{name = <<"subscription">>,
- attrs = [{<<"jid">>,
- jid:to_string(J)}]}
- end, RoomJIDs),
- Res = IQ#iq{type = result,
- sub_el = [SubEl#xmlel{children = Subs}]},
- 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 fxml:get_attr_s(<<"type">>, Attrs) of
- <<"error">> -> ok;
- _ ->
- case acl:match_rule(ServerHost, AccessAdmin, From)
- of
- allow ->
- Msg = fxml:get_path_s(Packet,
- [{elem, <<"body">>},
- cdata]),
- broadcast_service_message(Host, Msg);
- _ ->
- Lang = fxml: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 fxml: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 = fxml:get_attr_s(<<"type">>, Attrs),
- case {Name, Type} of
- {<<"presence">>, <<"">>} ->
- case check_user_can_create_room(ServerHost,
- AccessCreate, From, Room) and
- check_create_roomid(ServerHost, Room) of
+%% Function copied from mod_muc_room.erl
+-spec extract_password(presence() | iq()) -> binary() | false.
+extract_password(#presence{} = Pres) ->
+ case xmpp:get_subtag(Pres, #muc{}) of
+ #muc{password = Password} when is_binary(Password) ->
+ Password;
+ _ ->
+ false
+ end;
+extract_password(#iq{} = IQ) ->
+ case xmpp:get_subtag(IQ, #muc_subscribe{}) of
+ #muc_subscribe{password = Password} when Password /= <<"">> ->
+ Password;
+ _ ->
+ false
+ end.
+
+-spec route_to_room(stanza(), binary()) -> ok.
+route_to_room(Packet, ServerHost) ->
+ From = xmpp:get_from(Packet),
+ To = xmpp:get_to(Packet),
+ {Room, Host, Nick} = jid:tolower(To),
+ RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
+ case RMod:find_online_room(ServerHost, Room, Host) of
+ error ->
+ case should_start_room(Packet) of
+ false ->
+ Lang = xmpp:get_lang(Packet),
+ ErrText = ?T("Conference room does not exist"),
+ Err = xmpp:err_item_not_found(ErrText, Lang),
+ ejabberd_router:route_error(Packet, Err);
+ StartType ->
+ case load_room(RMod, Host, ServerHost, Room) of
+ {error, notfound} when StartType == start ->
+ case check_create_room(ServerHost, Host, Room, From) 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;
+ Pass = extract_password(Packet),
+ case start_new_room(RMod, Host, ServerHost, Room, Pass, From, Nick) of
+ {ok, Pid} ->
+ mod_muc_room:route(Pid, Packet);
+ _Err ->
+ Err = xmpp:err_internal_server_error(),
+ ejabberd_router:route_error(Packet, Err)
+ end;
false ->
- Lang = fxml: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)
+ Lang = xmpp:get_lang(Packet),
+ ErrText = ?T("Room creation is denied by service policy"),
+ Err = xmpp:err_forbidden(ErrText, Lang),
+ ejabberd_router:route_error(Packet, Err)
end;
- _ ->
- Lang = fxml: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
+ {error, notfound} ->
+ Lang = xmpp:get_lang(Packet),
+ ErrText = ?T("Conference room does not exist"),
+ Err = xmpp:err_item_not_found(ErrText, Lang),
+ ejabberd_router:route_error(Packet, Err);
+ {error, _} ->
+ Err = xmpp:err_internal_server_error(),
+ ejabberd_router:route_error(Packet, Err);
+ {ok, Pid2} ->
+ mod_muc_room:route(Pid2, Packet)
+ end
+ end;
+ {ok, Pid} ->
+ mod_muc_room:route(Pid, Packet)
+ end.
+
+-spec process_vcard(iq()) -> iq().
+process_vcard(#iq{type = get, to = To, lang = Lang, sub_els = [#vcard_temp{}]} = IQ) ->
+ ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
+ VCard = case mod_muc_opt:vcard(ServerHost) of
+ undefined ->
+ #vcard_temp{fn = <<"ejabberd/mod_muc">>,
+ url = ejabberd_config:get_uri(),
+ desc = misc:get_descr(Lang, ?T("ejabberd MUC module"))};
+ V ->
+ V
+ end,
+ xmpp:make_iq_result(IQ, VCard);
+process_vcard(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_vcard(#iq{lang = Lang} = IQ) ->
+ Txt = ?T("No module is handling this query"),
+ xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
+
+-spec process_register(iq()) -> iq().
+process_register(#iq{type = Type, from = From, to = To, lang = Lang,
+ sub_els = [El = #register{}]} = IQ) ->
+ Host = To#jid.lserver,
+ ServerHost = ejabberd_router:host_of_route(Host),
+ AccessRegister = mod_muc_opt:access_register(ServerHost),
+ case acl:match_rule(ServerHost, AccessRegister, From) of
+ allow ->
+ case Type of
+ get ->
+ xmpp:make_iq_result(
+ IQ, iq_get_register_info(ServerHost, Host, From, Lang));
+ set ->
+ case process_iq_register_set(ServerHost, Host, From, El, Lang) of
+ {result, Result} ->
+ xmpp:make_iq_result(IQ, Result);
+ {error, Err} ->
+ xmpp:make_error(IQ, Err)
+ end
+ end;
+ deny ->
+ ErrText = ?T("Access denied by service policy"),
+ Err = xmpp:err_forbidden(ErrText, Lang),
+ xmpp:make_error(IQ, Err)
end.
-check_user_can_create_room(ServerHost, AccessCreate,
- From, _RoomID) ->
+-spec process_disco_info(iq()) -> iq().
+process_disco_info(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_disco_info(#iq{type = get, from = From, to = To, lang = Lang,
+ sub_els = [#disco_info{node = <<"">>}]} = IQ) ->
+ ServerHost = ejabberd_router:host_of_route(To#jid.lserver),
+ RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
+ AccessRegister = mod_muc_opt:access_register(ServerHost),
+ X = ejabberd_hooks:run_fold(disco_info, ServerHost, [],
+ [ServerHost, ?MODULE, <<"">>, Lang]),
+ MAMFeatures = case gen_mod:is_loaded(ServerHost, mod_mam) of
+ true -> [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1, ?NS_MAM_2];
+ false -> []
+ end,
+ RSMFeatures = case RMod:rsm_supported() of
+ true -> [?NS_RSM];
+ false -> []
+ end,
+ RegisterFeatures = case acl:match_rule(ServerHost, AccessRegister, From) of
+ allow -> [?NS_REGISTER];
+ deny -> []
+ end,
+ Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
+ ?NS_MUC, ?NS_VCARD, ?NS_MUCSUB, ?NS_MUC_UNIQUE
+ | RegisterFeatures ++ RSMFeatures ++ MAMFeatures],
+ Name = mod_muc_opt:name(ServerHost),
+ Identity = #identity{category = <<"conference">>,
+ type = <<"text">>,
+ name = translate:translate(Lang, Name)},
+ xmpp:make_iq_result(
+ IQ, #disco_info{features = Features,
+ identities = [Identity],
+ xdata = X});
+process_disco_info(#iq{type = get, lang = Lang,
+ sub_els = [#disco_info{}]} = IQ) ->
+ xmpp:make_error(IQ, xmpp:err_item_not_found(?T("Node not found"), Lang));
+process_disco_info(#iq{lang = Lang} = IQ) ->
+ Txt = ?T("No module is handling this query"),
+ xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
+
+-spec process_disco_items(iq()) -> iq().
+process_disco_items(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_disco_items(#iq{type = get, from = From, to = To, lang = Lang,
+ sub_els = [#disco_items{node = Node, rsm = RSM}]} = IQ) ->
+ Host = To#jid.lserver,
+ ServerHost = ejabberd_router:host_of_route(Host),
+ MaxRoomsDiscoItems = mod_muc_opt:max_rooms_discoitems(ServerHost),
+ case iq_disco_items(ServerHost, Host, From, Lang,
+ MaxRoomsDiscoItems, Node, RSM) of
+ {error, Err} ->
+ xmpp:make_error(IQ, Err);
+ {result, Result} ->
+ xmpp:make_iq_result(IQ, Result)
+ end;
+process_disco_items(#iq{lang = Lang} = IQ) ->
+ Txt = ?T("No module is handling this query"),
+ xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
+
+-spec process_muc_unique(iq()) -> iq().
+process_muc_unique(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_muc_unique(#iq{from = From, type = get,
+ sub_els = [#muc_unique{}]} = IQ) ->
+ Name = str:sha(term_to_binary([From, erlang:timestamp(),
+ p1_rand:get_string()])),
+ xmpp:make_iq_result(IQ, #muc_unique{name = Name}).
+
+-spec process_mucsub(iq()) -> iq().
+process_mucsub(#iq{type = set, lang = Lang} = IQ) ->
+ Txt = ?T("Value 'set' of 'type' attribute is not allowed"),
+ xmpp:make_error(IQ, xmpp:err_not_allowed(Txt, Lang));
+process_mucsub(#iq{type = get, from = From, to = To, lang = Lang,
+ sub_els = [#muc_subscriptions{}]} = IQ) ->
+ Host = To#jid.lserver,
+ ServerHost = ejabberd_router:host_of_route(Host),
+ case get_subscribed_rooms(ServerHost, Host, From) of
+ {ok, Subs} ->
+ List = [#muc_subscription{jid = JID, events = Nodes}
+ || {JID, Nodes} <- Subs],
+ xmpp:make_iq_result(IQ, #muc_subscriptions{list = List});
+ {error, _} ->
+ Txt = ?T("Database failure"),
+ xmpp:make_error(IQ, xmpp:err_internal_server_error(Txt, Lang))
+ end;
+process_mucsub(#iq{lang = Lang} = IQ) ->
+ Txt = ?T("No module is handling this query"),
+ xmpp:make_error(IQ, xmpp:err_service_unavailable(Txt, Lang)).
+
+-spec should_start_room(stanza()) -> start | load | false.
+should_start_room(#presence{type = available}) ->
+ start;
+should_start_room(#iq{type = T} = IQ) when T == get; T == set ->
+ case xmpp:has_subtag(IQ, #muc_subscribe{}) orelse
+ xmpp:has_subtag(IQ, #muc_owner{}) of
+ true ->
+ start;
+ _ ->
+ load
+ end;
+should_start_room(#message{type = T, to = #jid{lresource = <<>>}})
+ when T == groupchat; T == normal->
+ load;
+should_start_room(#message{type = T, to = #jid{lresource = Res}})
+ when Res /= <<>> andalso T /= groupchat andalso T /= error ->
+ load;
+should_start_room(_) ->
+ false.
+
+-spec check_create_room(binary(), binary(), binary(), jid()) -> boolean().
+check_create_room(ServerHost, Host, Room, From) ->
+ AccessCreate = mod_muc_opt:access_create(ServerHost),
case acl:match_rule(ServerHost, AccessCreate, From) of
- allow -> true;
- _ -> false
+ allow ->
+ case mod_muc_opt:max_room_id(ServerHost) of
+ Max when byte_size(Room) =< Max ->
+ Regexp = mod_muc_opt:regexp_room_id(ServerHost),
+ case re:run(Room, Regexp, [{capture, none}]) of
+ match ->
+ AccessAdmin = mod_muc_opt:access_admin(ServerHost),
+ case acl:match_rule(ServerHost, AccessAdmin, From) of
+ allow ->
+ true;
+ _ ->
+ ejabberd_hooks:run_fold(
+ check_create_room, ServerHost, true,
+ [ServerHost, Room, Host])
+ end;
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end;
+ _ ->
+ false
end.
-check_create_roomid(ServerHost, RoomID) ->
- Max = gen_mod:get_module_opt(ServerHost, ?MODULE, max_room_id,
- fun(infinity) -> infinity;
- (I) when is_integer(I), I>0 -> I
- end, infinity),
- Regexp = gen_mod:get_module_opt(ServerHost, ?MODULE, regexp_room_id,
- fun iolist_to_binary/1, ""),
- (byte_size(RoomID) =< Max) and
- (re:run(RoomID, Regexp, [unicode, {capture, none}]) == match).
+-spec get_access(binary() | gen_mod:opts()) -> access().
+get_access(ServerHost) ->
+ Access = mod_muc_opt:access(ServerHost),
+ AccessCreate = mod_muc_opt:access_create(ServerHost),
+ AccessAdmin = mod_muc_opt:access_admin(ServerHost),
+ AccessPersistent = mod_muc_opt:access_persistent(ServerHost),
+ AccessMam = mod_muc_opt:access_mam(ServerHost),
+ {Access, AccessCreate, AccessAdmin, AccessPersistent, AccessMam}.
+-spec get_rooms(binary(), binary()) -> [#muc_room{}].
get_rooms(ServerHost, Host) ->
- LServer = jid:nameprep(ServerHost),
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:get_rooms(LServer, Host).
+ Mod = gen_mod:db_mod(ServerHost, ?MODULE),
+ Mod:get_rooms(ServerHost, Host).
-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) ->
+-spec load_permanent_rooms([binary()], binary(), gen_mod:opts()) -> ok.
+load_permanent_rooms(Hosts, ServerHost, Opts) ->
+ case mod_muc_opt:preload_rooms(Opts) of
+ true ->
+ Access = get_access(Opts),
+ HistorySize = mod_muc_opt:history_size(Opts),
+ QueueType = mod_muc_opt:queue_type(Opts),
+ RoomShaper = mod_muc_opt:room_shaper(Opts),
+ RMod = gen_mod:ram_db_mod(Opts, ?MODULE),
+ lists:foreach(
+ fun(Host) ->
+ ?DEBUG("Loading rooms at ~ts", [Host]),
+ lists:foreach(
+ fun(R) ->
+ {Room, _} = R#muc_room.name_host,
+ case proplists:get_bool(persistent, R#muc_room.opts) of
+ true ->
+ case RMod:find_online_room(ServerHost, Room, Host) of
+ error ->
+ start_room(RMod, Host, ServerHost, Access,
+ Room, HistorySize, RoomShaper,
+ R#muc_room.opts, QueueType);
+ {ok, _} ->
+ ok
+ end;
+ _ ->
+ forget_room(ServerHost, Host, Room)
+ end
+ end, get_rooms(ServerHost, Host))
+ end, Hosts);
+ false ->
+ ok
+ end.
+
+-spec load_room(module(), binary(), binary(), binary()) -> {ok, pid()} |
+ {error, notfound | term()}.
+load_room(RMod, Host, ServerHost, Room) ->
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)
+ {error, notfound};
+ Opts0 ->
+ case proplists:get_bool(persistent, Opts0) of
+ true ->
+ ?DEBUG("Restore room: ~ts", [Room]),
+ start_room(RMod, Host, ServerHost, Room, Opts0);
+ _ ->
+ ?DEBUG("Restore hibernated non-persistent room: ~ts", [Room]),
+ Res = start_room(RMod, Host, ServerHost, Room, Opts0),
+ Mod = gen_mod:db_mod(ServerHost, mod_muc),
+ case erlang:function_exported(Mod, get_subscribed_rooms, 3) of
+ true ->
+ ok;
+ _ ->
+ forget_room(ServerHost, Host, Room)
+ end,
+ Res
+ end
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(ServerHost, 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_MUCSUB}], children = []},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_VCARD}], children = []}] ++
- case gen_mod:is_loaded(ServerHost, mod_mam) of
- true ->
- [#xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_MAM_TMP}]},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_MAM_0}]},
- #xmlel{name = <<"feature">>,
- attrs = [{<<"var">>, ?NS_MAM_1}]}];
- false ->
- []
- end.
-
-iq_disco_items(Host, From, Lang, <<>>, none) ->
- Rooms = get_vh_rooms(Host),
- case erlang:length(Rooms) < ?MAX_ROOMS_DISCOITEMS of
- true ->
- iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang});
- false ->
- iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, none)
- end;
-iq_disco_items(Host, From, Lang, <<"nonemptyrooms">>, none) ->
- XmlEmpty = #xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>, <<"conference.localhost">>},
- {<<"node">>, <<"emptyrooms">>},
- {<<"name">>, translate:translate(Lang, <<"Empty Rooms">>)}],
- children = []},
- Query = {get_disco_item, only_non_empty, From, Lang},
- [XmlEmpty | iq_disco_items_list(Host, get_vh_rooms(Host), Query)];
-iq_disco_items(Host, From, Lang, <<"emptyrooms">>, none) ->
- iq_disco_items_list(Host, get_vh_rooms(Host), {get_disco_item, 0, From, Lang});
-iq_disco_items(Host, From, Lang, _DiscoNode, Rsm) ->
- {Rooms, RsmO} = get_vh_rooms(Host, Rsm),
- RsmOut = jlib:rsm_encode(RsmO),
- iq_disco_items_list(Host, Rooms, {get_disco_item, all, From, Lang}) ++ RsmOut.
-
-iq_disco_items_list(Host, Rooms, Query) ->
- lists:zf(fun (#muc_online_room{name_host =
- {Name, _Host},
- pid = Pid}) ->
- case catch gen_fsm:sync_send_all_state_event(Pid,
- Query,
- 100)
- of
- {item, Desc} ->
- flush(),
- {true,
- #xmlel{name = <<"item">>,
- attrs =
- [{<<"jid">>,
- jid:to_string({Name, Host,
- <<"">>})},
- {<<"name">>, Desc}],
- children = []}};
- _ -> false
- end
- end, Rooms).
-
-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}]
+start_new_room(RMod, Host, ServerHost, Room, Pass, From, Nick) ->
+ ?DEBUG("Open new room: ~ts", [Room]),
+ DefRoomOpts = mod_muc_opt:default_room_options(ServerHost),
+ DefRoomOpts2 = add_password_options(Pass, DefRoomOpts),
+ start_room(RMod, Host, ServerHost, Room, DefRoomOpts2, From, Nick).
+
+add_password_options(false, DefRoomOpts) ->
+ DefRoomOpts;
+add_password_options(<<>>, DefRoomOpts) ->
+ DefRoomOpts;
+add_password_options(Pass, DefRoomOpts) when is_binary(Pass) ->
+ O2 = lists:keystore(password, 1, DefRoomOpts, {password, Pass}),
+ lists:keystore(password_protected, 1, O2, {password_protected, true}).
+
+start_room(Mod, Host, ServerHost, Room, DefOpts) ->
+ Access = get_access(ServerHost),
+ HistorySize = mod_muc_opt:history_size(ServerHost),
+ QueueType = mod_muc_opt:queue_type(ServerHost),
+ RoomShaper = mod_muc_opt:room_shaper(ServerHost),
+ start_room(Mod, Host, ServerHost, Access, Room, HistorySize,
+ RoomShaper, DefOpts, QueueType).
+
+start_room(Mod, Host, ServerHost, Room, DefOpts, Creator, Nick) ->
+ Access = get_access(ServerHost),
+ HistorySize = mod_muc_opt:history_size(ServerHost),
+ QueueType = mod_muc_opt:queue_type(ServerHost),
+ RoomShaper = mod_muc_opt:room_shaper(ServerHost),
+ start_room(Mod, Host, ServerHost, Access, Room,
+ HistorySize, RoomShaper,
+ Creator, Nick, DefOpts, QueueType).
+
+start_room(Mod, Host, ServerHost, Access, Room,
+ HistorySize, RoomShaper, DefOpts, QueueType) ->
+ case mod_muc_room:start(Host, ServerHost, Access, Room,
+ HistorySize, RoomShaper, DefOpts, QueueType) of
+ {ok, Pid} ->
+ Mod:register_online_room(ServerHost, Room, Host, Pid),
+ {ok, Pid};
+ Err ->
+ Err
+ end.
+
+start_room(Mod, Host, ServerHost, Access, Room, HistorySize,
+ RoomShaper, Creator, Nick, DefOpts, QueueType) ->
+ case mod_muc_room:start(Host, ServerHost, Access, Room,
+ HistorySize, RoomShaper,
+ Creator, Nick, DefOpts, QueueType) of
+ {ok, Pid} ->
+ Mod:register_online_room(ServerHost, Room, Host, Pid),
+ {ok, Pid};
+ Err ->
+ Err
+ end.
+
+-spec iq_disco_items(binary(), binary(), jid(), binary(), integer(), binary(),
+ rsm_set() | undefined) ->
+ {result, disco_items()} | {error, stanza_error()}.
+iq_disco_items(ServerHost, Host, From, Lang, MaxRoomsDiscoItems, Node, RSM)
+ when Node == <<"">>; Node == <<"nonemptyrooms">>; Node == <<"emptyrooms">> ->
+ Count = count_online_rooms(ServerHost, Host),
+ Query = if Node == <<"">>, RSM == undefined, Count > MaxRoomsDiscoItems ->
+ {only_non_empty, From, Lang};
+ Node == <<"nonemptyrooms">> ->
+ {only_non_empty, From, Lang};
+ Node == <<"emptyrooms">> ->
+ {0, From, Lang};
+ true ->
+ {all, From, Lang}
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}}
+ MaxItems = case RSM of
+ undefined ->
+ MaxRoomsDiscoItems;
+ #rsm_set{max = undefined} ->
+ MaxRoomsDiscoItems;
+ #rsm_set{max = Max} when Max > MaxRoomsDiscoItems ->
+ MaxRoomsDiscoItems;
+ #rsm_set{max = Max} ->
+ Max
+ end,
+ {Items, HitMax} = lists:foldr(
+ fun(_, {Acc, _}) when length(Acc) >= MaxItems ->
+ {Acc, true};
+ (R, {Acc, _}) ->
+ case get_room_disco_item(R, Query) of
+ {ok, Item} -> {[Item | Acc], false};
+ {error, _} -> {Acc, false}
+ end
+ end, {[], false}, get_online_rooms(ServerHost, Host, RSM)),
+ ResRSM = case Items of
+ [_|_] when RSM /= undefined; HitMax ->
+ #disco_item{jid = #jid{luser = First}} = hd(Items),
+ #disco_item{jid = #jid{luser = Last}} = lists:last(Items),
+ #rsm_set{first = #rsm_first{data = First},
+ last = Last,
+ count = Count};
+ [] when RSM /= undefined ->
+ #rsm_set{count = Count};
+ _ ->
+ undefined
+ end,
+ {result, #disco_items{node = Node, items = Items, rsm = ResRSM}};
+iq_disco_items(_ServerHost, _Host, _From, Lang, _MaxRoomsDiscoItems, _Node, _RSM) ->
+ {error, xmpp:err_item_not_found(?T("Node not found"), Lang)}.
+
+-spec get_room_disco_item({binary(), binary(), pid()},
+ {mod_muc_room:disco_item_filter(),
+ jid(), binary()}) -> {ok, disco_item()} |
+ {error, timeout | notfound}.
+get_room_disco_item({Name, Host, Pid}, {Filter, JID, Lang}) ->
+ case mod_muc_room:get_disco_item(Pid, Filter, JID, Lang) of
+ {ok, Desc} ->
+ RoomJID = jid:make(Name, Host),
+ {ok, #disco_item{jid = RoomJID, name = Desc}};
+ {error, _} = Err ->
+ Err
end.
+-spec get_subscribed_rooms(binary(), jid()) -> {ok, [{jid(), [binary()]}]} | {error, any()}.
+get_subscribed_rooms(Host, User) ->
+ ServerHost = ejabberd_router:host_of_route(Host),
+ get_subscribed_rooms(ServerHost, Host, User).
+
+-spec get_subscribed_rooms(binary(), binary(), jid()) ->
+ {ok, [{jid(), [binary()]}]} | {error, any()}.
get_subscribed_rooms(ServerHost, Host, From) ->
- Rooms = get_rooms(ServerHost, Host),
- lists:flatmap(
- fun(#muc_room{name_host = {Name, _}, opts = Opts}) ->
- Subscribers = proplists:get_value(subscribers, Opts, []),
- case lists:keymember(From, 1, Subscribers) of
- true -> [jid:make(Name, Host, <<>>)];
- false -> []
- end;
- (_) ->
- []
- end, Rooms).
-
-%% @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),
- #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, p1_time_compat:timestamp(),
- randoms:get_string()]))}.
+ LServer = jid:nameprep(ServerHost),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ BareFrom = jid:remove_resource(From),
+ case erlang:function_exported(Mod, get_subscribed_rooms, 3) of
+ false ->
+ Rooms = get_online_rooms(ServerHost, Host),
+ {ok, lists:flatmap(
+ fun({Name, _, Pid}) when Pid == self() ->
+ USR = jid:split(BareFrom),
+ case erlang:get(muc_subscribers) of
+ #{USR := #subscriber{nodes = Nodes}} ->
+ [{jid:make(Name, Host), Nodes}];
+ _ ->
+ []
+ end;
+ ({Name, _, Pid}) ->
+ case mod_muc_room:is_subscribed(Pid, BareFrom) of
+ {true, Nodes} ->
+ [{jid:make(Name, Host), Nodes}];
+ false -> []
+ end;
+ (_) ->
+ []
+ end, Rooms)};
+ true ->
+ Mod:get_subscribed_rooms(LServer, Host, BareFrom)
+ end.
get_nick(ServerHost, Host, From) ->
LServer = jid:nameprep(ServerHost),
@@ -752,39 +992,23 @@ get_nick(ServerHost, Host, From) ->
Mod:get_nick(LServer, Host, From).
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 = []}]}
+ {Nick, Registered} = case get_nick(ServerHost, Host, From) of
+ error -> {<<"">>, false};
+ N -> {N, true}
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)]}].
+ Title = <<(translate:translate(
+ Lang, ?T("Nickname Registration at ")))/binary, Host/binary>>,
+ Inst = translate:translate(Lang, ?T("Enter nickname you want to register")),
+ Fields = muc_register:encode([{roomnick, Nick}], Lang),
+ X = #xdata{type = form, title = Title,
+ instructions = [Inst], fields = Fields},
+ #register{nick = Nick,
+ registered = Registered,
+ instructions =
+ translate:translate(
+ Lang, ?T("You need a client that supports x:data "
+ "to register the nickname")),
+ xdata = X}.
set_nick(ServerHost, Host, From, Nick) ->
LServer = jid:nameprep(ServerHost),
@@ -794,108 +1018,64 @@ set_nick(ServerHost, Host, From, Nick) ->
iq_set_register_info(ServerHost, Host, From, Nick,
Lang) ->
case set_nick(ServerHost, Host, From, Nick) of
- {atomic, ok} -> {result, []};
+ {atomic, ok} -> {result, undefined};
{atomic, false} ->
- ErrText = <<"That nickname is registered by another "
- "person">>,
- {error, ?ERRT_CONFLICT(Lang, ErrText)};
+ ErrText = ?T("That nickname is registered by another person"),
+ {error, xmpp:err_conflict(ErrText, Lang)};
_ ->
- Txt = <<"Database failure">>,
- {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)}
+ Txt = ?T("Database failure"),
+ {error, xmpp:err_internal_server_error(Txt, Lang)}
end.
-process_iq_register_set(ServerHost, Host, From, SubEl,
- Lang) ->
- #xmlel{children = Els} = SubEl,
- case fxml:get_subtag(SubEl, <<"remove">>) of
- false ->
- case fxml:remove_cdata(Els) of
- [#xmlel{name = <<"x">>} = XEl] ->
- case {fxml:get_tag_attr_s(<<"xmlns">>, XEl),
- fxml:get_tag_attr_s(<<"type">>, XEl)}
- of
- {?NS_XDATA, <<"cancel">>} -> {result, []};
- {?NS_XDATA, <<"submit">>} ->
- XData = jlib:parse_xdata_submit(XEl),
- case XData of
- invalid ->
- Txt = <<"Incorrect data form">>,
- {error, ?ERRT_BAD_REQUEST(Lang, Txt)};
- _ ->
- 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)
+process_iq_register_set(ServerHost, Host, From,
+ #register{remove = true}, Lang) ->
+ iq_set_register_info(ServerHost, Host, From, <<"">>, Lang);
+process_iq_register_set(_ServerHost, _Host, _From,
+ #register{xdata = #xdata{type = cancel}}, _Lang) ->
+ {result, undefined};
+process_iq_register_set(ServerHost, Host, From,
+ #register{nick = Nick, xdata = XData}, Lang) ->
+ case XData of
+ #xdata{type = submit, fields = Fs} ->
+ try
+ Options = muc_register:decode(Fs),
+ N = proplists:get_value(roomnick, Options),
+ iq_set_register_info(ServerHost, Host, From, N, Lang)
+ catch _:{muc_register, Why} ->
+ ErrText = muc_register:format_error(Why),
+ {error, xmpp:err_bad_request(ErrText, Lang)}
+ end;
+ #xdata{} ->
+ Txt = ?T("Incorrect data form"),
+ {error, xmpp:err_bad_request(Txt, Lang)};
+ _ when is_binary(Nick), Nick /= <<"">> ->
+ iq_set_register_info(ServerHost, Host, From, Nick, Lang);
+ _ ->
+ ErrText = ?T("You must fill in field \"Nickname\" in the form"),
+ {error, xmpp:err_not_acceptable(ErrText, 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-2016 ProcessOne">>}]}].
-
-broadcast_service_message(Host, Msg) ->
+-spec broadcast_service_message(binary(), binary(), binary()) -> ok.
+broadcast_service_message(ServerHost, 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).
+ fun({_, _, Pid}) ->
+ mod_muc_room:service_message(Pid, Msg)
+ end, get_online_rooms(ServerHost, Host)).
+
+-spec get_online_rooms(binary(), binary()) -> [{binary(), binary(), pid()}].
+get_online_rooms(ServerHost, Host) ->
+ get_online_rooms(ServerHost, Host, undefined).
+
+-spec get_online_rooms(binary(), binary(), undefined | rsm_set()) ->
+ [{binary(), binary(), pid()}].
+get_online_rooms(ServerHost, Host, RSM) ->
+ RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
+ RMod:get_online_rooms(ServerHost, Host, RSM).
+
+-spec count_online_rooms(binary(), binary()) -> non_neg_integer().
+count_online_rooms(ServerHost, Host) ->
+ RMod = gen_mod:ram_db_mod(ServerHost, ?MODULE),
+ RMod:count_online_rooms(ServerHost, Host).
opts_to_binary(Opts) ->
lists:map(
@@ -905,7 +1085,7 @@ opts_to_binary(Opts) ->
{description, iolist_to_binary(Desc)};
({password, Pass}) ->
{password, iolist_to_binary(Pass)};
- ({subject, Subj}) ->
+ ({subject, [C|_] = Subj}) when is_integer(C), C >= 0, C =< 255 ->
{subject, iolist_to_binary(Subj)};
({subject_author, Author}) ->
{subject_author, iolist_to_binary(Author)};
@@ -939,65 +1119,160 @@ export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
-import(LServer) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:import(LServer).
+import_info() ->
+ [{<<"muc_room">>, 4}, {<<"muc_registered">>, 4}].
+
+import_start(LServer, DBType) ->
+ Mod = gen_mod:db_mod(DBType, ?MODULE),
+ Mod:init(LServer, []).
-import(LServer, DBType, Data) ->
+import(LServer, {sql, _}, DBType, Tab, L) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
- Mod:import(LServer, Data).
+ Mod:import(LServer, Tab, L).
mod_opt_type(access) ->
- fun acl:access_rules_validator/1;
+ econf:acl();
mod_opt_type(access_admin) ->
- fun acl:access_rules_validator/1;
+ econf:acl();
mod_opt_type(access_create) ->
- fun acl:access_rules_validator/1;
+ econf:acl();
mod_opt_type(access_persistent) ->
- fun acl:access_rules_validator/1;
-mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
-mod_opt_type(default_room_options) ->
- fun (L) when is_list(L) -> L end;
+ econf:acl();
+mod_opt_type(access_mam) ->
+ econf:acl();
+mod_opt_type(access_register) ->
+ econf:acl();
mod_opt_type(history_size) ->
- fun (I) when is_integer(I), I >= 0 -> I end;
-mod_opt_type(host) -> fun iolist_to_binary/1;
+ econf:non_neg_int();
+mod_opt_type(name) ->
+ econf:binary();
mod_opt_type(max_room_desc) ->
- fun (infinity) -> infinity;
- (I) when is_integer(I), I > 0 -> I
- end;
+ econf:pos_int(infinity);
mod_opt_type(max_room_id) ->
- fun (infinity) -> infinity;
- (I) when is_integer(I), I > 0 -> I
- end;
+ econf:pos_int(infinity);
+mod_opt_type(max_rooms_discoitems) ->
+ econf:non_neg_int();
mod_opt_type(regexp_room_id) ->
- fun iolist_to_binary/1;
+ econf:re([unicode]);
mod_opt_type(max_room_name) ->
- fun (infinity) -> infinity;
- (I) when is_integer(I), I > 0 -> I
- end;
+ econf:pos_int(infinity);
mod_opt_type(max_user_conferences) ->
- fun (I) when is_integer(I), I > 0 -> I end;
+ econf:pos_int();
mod_opt_type(max_users) ->
- fun (I) when is_integer(I), I > 0 -> I end;
+ econf:pos_int();
mod_opt_type(max_users_admin_threshold) ->
- fun (I) when is_integer(I), I > 0 -> I end;
+ econf:pos_int();
mod_opt_type(max_users_presence) ->
- fun (MUP) when is_integer(MUP) -> MUP end;
+ econf:int();
mod_opt_type(min_message_interval) ->
- fun (MMI) when is_number(MMI) -> MMI end;
+ econf:number(0);
mod_opt_type(min_presence_interval) ->
- fun (I) when is_number(I), I >= 0 -> I end;
+ econf:number(0);
+mod_opt_type(preload_rooms) ->
+ econf:bool();
mod_opt_type(room_shaper) ->
- fun (A) when is_atom(A) -> A end;
+ econf:atom();
mod_opt_type(user_message_shaper) ->
- fun (A) when is_atom(A) -> A end;
+ econf:atom();
mod_opt_type(user_presence_shaper) ->
- fun (A) when is_atom(A) -> A end;
-mod_opt_type(_) ->
- [access, access_admin, access_create, access_persistent,
- db_type, default_room_options, history_size, host,
- max_room_desc, max_room_id, max_room_name, regexp_room_id,
- max_user_conferences, max_users,
- max_users_admin_threshold, max_users_presence,
- min_message_interval, min_presence_interval,
- room_shaper, user_message_shaper, user_presence_shaper].
+ econf:atom();
+mod_opt_type(default_room_options) ->
+ econf:options(
+ #{allow_change_subj => econf:bool(),
+ allow_private_messages => econf:bool(),
+ allow_private_messages_from_visitors =>
+ econf:enum([anyone, moderators, nobody]),
+ allow_query_users => econf:bool(),
+ allow_subscription => econf:bool(),
+ allow_user_invites => econf:bool(),
+ allow_visitor_nickchange => econf:bool(),
+ allow_visitor_status => econf:bool(),
+ anonymous => econf:bool(),
+ captcha_protected => econf:bool(),
+ lang => econf:lang(),
+ logging => econf:bool(),
+ mam => econf:bool(),
+ max_users => econf:pos_int(),
+ members_by_default => econf:bool(),
+ members_only => econf:bool(),
+ moderated => econf:bool(),
+ password => econf:binary(),
+ password_protected => econf:bool(),
+ persistent => econf:bool(),
+ presence_broadcast =>
+ econf:list(
+ econf:enum([moderator, participant, visitor])),
+ public => econf:bool(),
+ public_list => econf:bool(),
+ title => econf:binary()});
+mod_opt_type(db_type) ->
+ econf:db_type(?MODULE);
+mod_opt_type(ram_db_type) ->
+ econf:db_type(?MODULE);
+mod_opt_type(host) ->
+ econf:host();
+mod_opt_type(hosts) ->
+ econf:hosts();
+mod_opt_type(queue_type) ->
+ econf:queue_type();
+mod_opt_type(hibernation_timeout) ->
+ econf:timeout(second, infinity);
+mod_opt_type(vcard) ->
+ econf:vcard_temp().
+
+mod_options(Host) ->
+ [{access, all},
+ {access_admin, none},
+ {access_create, all},
+ {access_persistent, all},
+ {access_mam, all},
+ {access_register, all},
+ {db_type, ejabberd_config:default_db(Host, ?MODULE)},
+ {ram_db_type, ejabberd_config:default_ram_db(Host, ?MODULE)},
+ {history_size, 20},
+ {host, <<"conference.", Host/binary>>},
+ {hosts, []},
+ {name, ?T("Chatrooms")},
+ {max_room_desc, infinity},
+ {max_room_id, infinity},
+ {max_room_name, infinity},
+ {max_rooms_discoitems, 100},
+ {max_user_conferences, 100},
+ {max_users, 200},
+ {max_users_admin_threshold, 5},
+ {max_users_presence, 1000},
+ {min_message_interval, 0},
+ {min_presence_interval, 0},
+ {queue_type, ejabberd_option:queue_type(Host)},
+ {regexp_room_id, <<"">>},
+ {room_shaper, none},
+ {user_message_shaper, none},
+ {user_presence_shaper, none},
+ {preload_rooms, true},
+ {hibernation_timeout, infinity},
+ {vcard, undefined},
+ {default_room_options,
+ [{allow_change_subj,true},
+ {allow_private_messages,true},
+ {allow_query_users,true},
+ {allow_user_invites,false},
+ {allow_visitor_nickchange,true},
+ {allow_visitor_status,true},
+ {anonymous,true},
+ {captcha_protected,false},
+ {lang,<<>>},
+ {logging,false},
+ {members_by_default,true},
+ {members_only,false},
+ {moderated,true},
+ {password_protected,false},
+ {persistent,false},
+ {public,true},
+ {public_list,true},
+ {mam,false},
+ {allow_subscription,false},
+ {password,<<>>},
+ {title,<<>>},
+ {allow_private_messages_from_visitors,anyone},
+ {max_users,200},
+ {presence_broadcast,[moderator,participant,visitor]}]}].