./ct_report/coverage/mod_muc.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : mod_muc.erl
3 %%% Author : Alexey Shchepin <alexey@process-one.net>
4 %%% Purpose : MUC support (XEP-0045)
5 %%% Created : 19 Mar 2003 by Alexey Shchepin <alexey@process-one.net>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2011 ProcessOne
9 %%%
10 %%% This program is free software; you can redistribute it and/or
11 %%% modify it under the terms of the GNU General Public License as
12 %%% published by the Free Software Foundation; either version 2 of the
13 %%% License, or (at your option) any later version.
14 %%%
15 %%% This program is distributed in the hope that it will be useful,
16 %%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17 %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 %%% General Public License for more details.
19 %%%
20 %%% You should have received a copy of the GNU General Public License
21 %%% along with this program; if not, write to the Free Software
22 %%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 %%%
24 %%%----------------------------------------------------------------------
25
26 -module(mod_muc).
27 -author('alexey@process-one.net').
28 -xep([{xep, 45}, {version, "1.25"}]).
29 -behaviour(gen_server).
30 -behaviour(gen_mod).
31 -behaviour(mongoose_packet_handler).
32 -behaviour(mongoose_module_metrics).
33
34 %% API
35 -export([start_link/2,
36 start/2,
37 stop/1,
38 supported_features/0,
39 config_spec/0,
40 process_room_affiliation/1,
41 room_destroyed/4,
42 store_room/4,
43 restore_room/3,
44 forget_room/3,
45 create_instant_room/6,
46 broadcast_service_message/2,
47 can_use_nick/4,
48 room_jid_to_pid/1,
49 default_host/0]).
50 -export([server_host_to_muc_host/2]).
51
52 %% For testing purposes only
53 -export([register_room/4]).
54
55 %% gen_server callbacks
56 -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
57 terminate/2, code_change/3]).
58
59 %% packet handler callback
60 -export([process_packet/5]).
61
62 %% Hooks handlers
63 -export([is_muc_room_owner/4,
64 can_access_room/4,
65 remove_domain/3,
66 acc_room_affiliations/2,
67 can_access_identity/4,
68 disco_local_items/1]).
69
70 %% Stats
71 -export([online_rooms_number/0]).
72 -export([hibernated_rooms_number/0]).
73
74 -export([config_metrics/1]).
75
76 -ignore_xref([
77 can_access_identity/4, can_access_room/4, acc_room_affiliations/2, create_instant_room/6,
78 disco_local_items/1, hibernated_rooms_number/0, is_muc_room_owner/4, remove_domain/3,
79 online_rooms_number/0, register_room/4, restore_room/3, start_link/2]).
80
81 -include("mongoose.hrl").
82 -include("jlib.hrl").
83 -include("mongoose_rsm.hrl").
84 -include("mongoose_config_spec.hrl").
85 -include("mod_muc_room.hrl").
86
87 -export_type([access/0,
88 room/0,
89 nick/0,
90 packet/0,
91 role/0,
92 affiliation/0
93 ]).
94
95 -type role() :: moderator | participant | visitor | none.
96 -type affiliation() :: admin | owner | member | outcast | none.
97 -type room() :: binary().
98 -type nick() :: binary().
99 -type room_host() :: jid:simple_bare_jid().
100 -type packet() :: exml:element().
101 -type from_to_packet() ::
102 {From :: jid:jid(), To :: jid:jid(), Acc :: mongoose_acc:t(),
103 Packet :: packet()}.
104 -type access() :: {_AccessRoute, _AccessCreate, _AccessAdmin, _AccessPersistent}.
105 -type host_type() :: mongooseim:host_type().
106 -type muc_host() :: jid:lserver().
107
108 -include("mod_muc.hrl").
109
110 -type muc_room() :: #muc_room{
111 name_host :: room_host(),
112 opts :: list()
113 }.
114
115 -type muc_online_room() :: #muc_online_room{
116 name_host :: room_host(),
117 host_type :: host_type(),
118 pid :: pid()
119 }.
120
121 -type muc_registered() :: #muc_registered{
122 us_host :: jid:literal_jid(),
123 nick :: nick()
124 }.
125
126 -type room_event_data() :: #{
127 from_nick := nick(),
128 from_jid := jid:jid(),
129 room_jid := jid:jid(),
130 affiliation := affiliation(),
131 role := role(),
132 timestamp := integer()
133 }.
134 -export_type([room_event_data/0]).
135
136 -record(muc_state, {host_type :: host_type(),
137 subdomain_pattern :: mongoose_subdomain_utils:subdomain_pattern(),
138 access,
139 history_size :: integer(),
140 default_room_opts :: list(),
141 room_shaper :: shaper:shaper(),
142 http_auth_pool :: mongoose_http_client:pool(),
143 hibernated_room_check_interval :: timeout(),
144 hibernated_room_timeout :: timeout() }).
145
146 -type state() :: #muc_state{}.
147
148 -export_type([muc_room/0, muc_registered/0]).
149
150 -define(PROCNAME, ejabberd_mod_muc).
151
152
153 %%====================================================================
154 %% API
155 %%====================================================================
156 %%--------------------------------------------------------------------
157 %% Function: start_link() -> {ok, Pid} | ignore | {error, Error}
158 %% Description: Starts the server
159 %%--------------------------------------------------------------------
160 -spec start_link(host_type(), map())
161 -> ignore | {error, _} | {ok, pid()}.
162 start_link(HostType, Opts) ->
163 11 Proc = gen_mod:get_module_proc(HostType, ?PROCNAME),
164 11 gen_server:start_link({local, Proc}, ?MODULE, {HostType, Opts}, []).
165
166 -spec start(host_type(), _) -> ok.
167 start(HostType, Opts) when is_map(Opts) ->
168 11 ensure_metrics(HostType),
169 11 start_supervisor(HostType),
170 11 start_server(HostType, Opts),
171 11 assert_server_running(HostType),
172 11 ok.
173
174 -spec stop(host_type()) -> ok.
175 stop(HostType) ->
176 11 stop_supervisor(HostType),
177 11 stop_gen_server(HostType),
178 11 ok.
179
180 -spec supported_features() -> [atom()].
181 supported_features() ->
182
:-(
[dynamic_domains].
183
184 start_server(HostType, Opts) ->
185 11 Proc = gen_mod:get_module_proc(HostType, ?PROCNAME),
186 11 ChildSpec =
187 {Proc,
188 {?MODULE, start_link, [HostType, Opts]},
189 temporary,
190 1000,
191 worker,
192 [?MODULE]},
193 11 {ok, _} = ejabberd_sup:start_child(ChildSpec).
194
195 assert_server_running(HostType) ->
196 11 true = is_pid(whereis(gen_mod:get_module_proc(HostType, ?PROCNAME))).
197
198 -spec config_spec() -> mongoose_config_spec:config_section().
199 config_spec() ->
200 164 #section{
201 format_items = map,
202 items = #{<<"backend">> => #option{type = atom,
203 validate = {module, mod_muc}},
204 <<"host">> => #option{type = string,
205 validate = subdomain_template,
206 process = fun mongoose_subdomain_utils:make_subdomain_pattern/1},
207 <<"access">> => #option{type = atom,
208 validate = access_rule},
209 <<"access_create">> => #option{type = atom,
210 validate = access_rule},
211 <<"access_admin">> => #option{type = atom,
212 validate = access_rule},
213 <<"access_persistent">> => #option{type = atom,
214 validate = access_rule},
215 <<"history_size">> => #option{type = integer,
216 validate = non_negative},
217 <<"room_shaper">> => #option{type = atom,
218 validate = shaper},
219 <<"max_room_id">> => #option{type = int_or_infinity,
220 validate = non_negative},
221 <<"max_room_name">> => #option{type = int_or_infinity,
222 validate = non_negative},
223 <<"max_room_desc">> => #option{type = int_or_infinity,
224 validate = non_negative},
225 <<"min_message_interval">> => #option{type = integer,
226 validate = non_negative},
227 <<"min_presence_interval">> => #option{type = integer,
228 validate = non_negative},
229 <<"max_users">> => #option{type = integer,
230 validate = positive},
231 <<"max_users_admin_threshold">> => #option{type = integer,
232 validate = positive},
233 <<"user_message_shaper">> => #option{type = atom,
234 validate = shaper},
235 <<"user_presence_shaper">> => #option{type = atom,
236 validate = shaper},
237 <<"max_user_conferences">> => #option{type = integer,
238 validate = non_negative},
239 <<"http_auth_pool">> => #option{type = atom,
240 validate = pool_name},
241 <<"load_permanent_rooms_at_startup">> => #option{type = boolean},
242 <<"hibernate_timeout">> => #option{type = int_or_infinity,
243 validate = non_negative},
244 <<"hibernated_room_check_interval">> => #option{type = int_or_infinity,
245 validate = non_negative},
246 <<"hibernated_room_timeout">> => #option{type = int_or_infinity,
247 validate = non_negative},
248 <<"default_room">> => default_room_config_spec()
249 },
250 defaults = defaults()
251 }.
252
253 defaults() ->
254 164 #{<<"backend">> => mnesia,
255 <<"host">> => default_host(),
256 <<"access">> => all,
257 <<"access_create">> => all,
258 <<"access_admin">> => none,
259 <<"access_persistent">> => all,
260 <<"history_size">> => 20,
261 <<"room_shaper">> => none,
262 <<"max_room_id">> => infinity,
263 <<"max_room_name">> => infinity,
264 <<"max_room_desc">> => infinity,
265 <<"min_message_interval">> => 0,
266 <<"min_presence_interval">> => 0,
267 <<"max_users">> => ?MAX_USERS_DEFAULT,
268 <<"max_users_admin_threshold">> => 5,
269 <<"user_message_shaper">> => none,
270 <<"user_presence_shaper">> => none,
271 <<"max_user_conferences">> => 10,
272 <<"http_auth_pool">> => none,
273 <<"load_permanent_rooms_at_startup">> => false,
274 <<"hibernate_timeout">> => timer:seconds(90),
275 <<"hibernated_room_check_interval">> => infinity,
276 <<"hibernated_room_timeout">> => infinity,
277 <<"default_room">> => keys_as_atoms(default_room_opts())}.
278
279 keys_as_atoms(Map) ->
280 164 maps:from_list([{binary_to_atom(K), V} || {K, V} <- maps:to_list(Map)]).
281
282 default_room_config_spec() ->
283 164 #section{
284 format_items = map,
285 items = #{<<"title">> => #option{type = binary},
286 <<"description">> => #option{type = binary},
287 <<"allow_change_subj">> => #option{type = boolean},
288 <<"allow_query_users">> => #option{type = boolean},
289 <<"allow_private_messages">> => #option{type = boolean},
290 <<"allow_visitor_status">> => #option{type = boolean},
291 <<"allow_visitor_nickchange">> => #option{type = boolean},
292 <<"public">> => #option{type = boolean},
293 <<"public_list">> => #option{type = boolean},
294 <<"persistent">> => #option{type = boolean},
295 <<"moderated">> => #option{type = boolean},
296 <<"members_by_default">> => #option{type = boolean},
297 <<"members_only">> => #option{type = boolean},
298 <<"allow_user_invites">> => #option{type = boolean},
299 <<"allow_multiple_sessions">> => #option{type = boolean},
300 <<"password_protected">> => #option{type = boolean},
301 <<"password">> => #option{type = binary},
302 <<"anonymous">> => #option{type = boolean},
303 <<"max_users">> => #option{type = integer,
304 validate = positive},
305 <<"logging">> => #option{type = boolean},
306 <<"maygetmemberlist">> => #list{items = #option{type = atom,
307 validate = non_empty}},
308 <<"affiliations">> => #list{items = default_room_affiliations_spec()},
309 <<"subject">> => #option{type = binary},
310 <<"subject_author">> => #option{type = binary}
311 },
312 defaults = default_room_opts()
313 }.
314
315 default_room_opts() ->
316 328 X = #config{},
317 328 #{<<"title">> => X#config.title,
318 <<"description">> => X#config.description,
319 <<"allow_change_subj">> => X#config.allow_change_subj,
320 <<"allow_query_users">> => X#config.allow_query_users,
321 <<"allow_private_messages">> => X#config.allow_private_messages,
322 <<"allow_visitor_status">> => X#config.allow_visitor_status,
323 <<"allow_visitor_nickchange">> => X#config.allow_visitor_nickchange,
324 <<"public">> => X#config.public,
325 <<"public_list">> => X#config.public_list,
326 <<"persistent">> => X#config.persistent,
327 <<"moderated">> => X#config.moderated,
328 <<"members_by_default">> => X#config.members_by_default,
329 <<"members_only">> => X#config.members_only,
330 <<"allow_user_invites">> => X#config.allow_user_invites,
331 <<"allow_multiple_sessions">> => X#config.allow_multiple_sessions,
332 <<"password_protected">> => X#config.password_protected,
333 <<"password">> => X#config.password,
334 <<"anonymous">> => X#config.anonymous,
335 <<"max_users">> => X#config.max_users,
336 <<"logging">> => X#config.logging,
337 <<"maygetmemberlist">> => X#config.maygetmemberlist,
338 <<"affiliations">> => [],
339 <<"subject">> => <<>>,
340 <<"subject_author">> => <<>>}.
341
342 default_room_affiliations_spec() ->
343 164 #section{
344 items = #{<<"user">> => #option{type = binary,
345 validate = non_empty},
346 <<"server">> => #option{type = binary,
347 validate = domain},
348 <<"resource">> => #option{type = binary},
349 <<"affiliation">> => #option{type = atom,
350 validate = non_empty}},
351 required = all,
352 process = fun ?MODULE:process_room_affiliation/1
353 }.
354
355 process_room_affiliation(KVs) ->
356
:-(
{[[{user, User}], [{server, Server}], [{resource, Res}], [{affiliation, Aff}]], []} =
357 proplists:split(KVs, [user, server, resource, affiliation]),
358
:-(
{{User, Server, Res}, Aff}.
359
360 stop_gen_server(HostType) ->
361 11 Proc = gen_mod:get_module_proc(HostType, ?PROCNAME),
362 11 gen_server:call(Proc, stop),
363 %% Proc can still be alive because of a race condition
364 11 ejabberd_sup:stop_child(Proc).
365
366 %% @doc This function is called by a room in three situations:
367 %% A) The owner of the room destroyed it
368 %% B) The only participant of a temporary room leaves it
369 %% C) mod_muc:stop was called, and each room is being terminated
370 %% In this case, the mod_muc process died before the room processes
371 %% So the message sending must be catched
372 -spec room_destroyed(host_type(), jid:server(), room(), pid()) -> 'ok'.
373 room_destroyed(HostType, MucHost, Room, Pid) ->
374 141 Obj = #muc_online_room{name_host = {Room, MucHost},
375 host_type = HostType, pid = Pid},
376 141 F = fun() -> mnesia:delete_object(Obj) end,
377 141 {atomic, ok} = mnesia:transaction(F),
378 140 ok.
379
380 %% @doc Create a room.
381 %% If Opts = default, the default room options are used.
382 %% Else use the passed options as defined in mod_muc_room.
383 %% XXX Only used from tests.
384 -spec create_instant_room(jid:lserver(), MucHost :: jid:lserver(), Name :: room(),
385 From :: jid:jid(), Nick :: nick(), Opts :: list()) -> any().
386 create_instant_room(ServerHost, MucHost, Name, From, Nick, Opts) ->
387 121 {ok, HostType} = mongoose_domain_api:get_domain_host_type(ServerHost),
388 121 Proc = gen_mod:get_module_proc(HostType, ?PROCNAME),
389 121 gen_server:call(Proc, {create_instant, ServerHost, MucHost, Name, From, Nick, Opts}).
390
391 -spec store_room(host_type(), jid:server(), room(), list()) ->
392 {error, _} | ok.
393 store_room(HostType, MucHost, Name, Opts) ->
394 121 mod_muc_backend:store_room(HostType, MucHost, Name, Opts).
395
396 -spec restore_room(host_type(), muc_host(), room()) ->
397 {error, _} | {ok, _}.
398 restore_room(HostType, MucHost, Name) ->
399 12 mod_muc_backend:restore_room(HostType, MucHost, Name).
400
401 -spec forget_room(host_type(), jid:server(), room()) -> ok | {error, term()}.
402 forget_room(HostType, MucHost, Name) ->
403 %% Removes room from DB, even if it's already removed.
404 63 Result = mod_muc_backend:forget_room(HostType, MucHost, Name),
405 63 case Result of
406 ok ->
407 %% TODO This hook should be renamed to forget_room_hook.
408 %% We also need to think how to remove stopped rooms
409 %% (i.e. in case we want to expose room removal over REST or SQS).
410 %%
411 %% In some _rare_ cases this hook can be called more than once for the same room.
412 63 mongoose_hooks:forget_room(HostType, MucHost, Name);
413 _ ->
414 %% Room is not removed or we don't know.
415 %% XXX Handle this case better.
416
:-(
ok
417 end,
418 63 Result.
419
420 %% For rooms
421 -spec process_iq_disco_items(MucHost :: jid:server(), From :: jid:jid(),
422 To :: jid:jid(), jlib:iq()) -> mongoose_acc:t().
423 process_iq_disco_items(MucHost, From, To, #iq{lang = Lang} = IQ) ->
424 20 Rsm = jlib:rsm_decode(IQ),
425 20 Res = IQ#iq{type = result,
426 sub_el = [#xmlel{name = <<"query">>,
427 attrs = [{<<"xmlns">>, ?NS_DISCO_ITEMS}],
428 children = iq_disco_items(MucHost, From, Lang, Rsm)}]},
429 20 ejabberd_router:route(To, From, jlib:iq_to_xml(Res)).
430
431 -spec can_use_nick(host_type(), jid:server(), jid:jid(), nick()) -> boolean().
432 can_use_nick(_HostType, _Host, _JID, <<>>) ->
433
:-(
false;
434 can_use_nick(HostType, MucHost, JID, Nick) ->
435 203 mod_muc_backend:can_use_nick(HostType, MucHost, JID, Nick).
436
437 set_nick(_HostType, _MucHost, _From, <<>>) ->
438
:-(
{error, should_not_be_empty};
439 set_nick(HostType, MucHost, From, Nick) ->
440 14 mod_muc_backend:set_nick(HostType, MucHost, From, Nick).
441
442 unset_nick(HostType, MucHost, From) ->
443 6 mod_muc_backend:unset_nick(HostType, MucHost, From).
444
445 get_nick(HostType, MucHost, From) ->
446 22 mod_muc_backend:get_nick(HostType, MucHost, From).
447
448 %%====================================================================
449 %% gen_server callbacks
450 %%====================================================================
451
452 -spec init({host_type(), map()}) -> {ok, state()}.
453 init({HostType, Opts}) ->
454 11 mod_muc_backend:init(HostType, Opts),
455 11 mnesia:create_table(muc_online_room,
456 [{ram_copies, [node()]},
457 {attributes, record_info(fields, muc_online_room)}]),
458 11 mnesia:add_table_copy(muc_online_room, node(), ram_copies),
459 11 catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]),
460 11 clean_table_from_bad_node(node(), HostType),
461 11 mnesia:subscribe(system),
462 11 #{access := Access,
463 access_create := AccessCreate,
464 access_admin := AccessAdmin,
465 access_persistent := AccessPersistent,
466 http_auth_pool := HttpAuthPool,
467 history_size := HistorySize,
468 default_room := DefRoomOpts,
469 room_shaper := RoomShaper,
470 hibernated_room_check_interval := CheckInterval,
471 hibernated_room_timeout := HibernatedTimeout,
472 host := SubdomainPattern,
473 load_permanent_rooms_at_startup := LoadPermRoomsAtStartup} = Opts,
474 11 State = #muc_state{host_type = HostType,
475 subdomain_pattern = SubdomainPattern,
476 access = {Access, AccessCreate, AccessAdmin, AccessPersistent},
477 default_room_opts = maps:to_list(DefRoomOpts),
478 history_size = HistorySize,
479 room_shaper = RoomShaper,
480 http_auth_pool = HttpAuthPool,
481 hibernated_room_check_interval = CheckInterval,
482 hibernated_room_timeout = HibernatedTimeout},
483 %% Hooks
484 11 ejabberd_hooks:add(hooks(HostType)),
485 %% Handler
486 11 PacketHandler = mongoose_packet_handler:new(?MODULE, #{state => State}),
487 11 case SubdomainPattern of
488 11 {prefix, _} -> ok;
489
:-(
_ -> ?LOG_WARNING(#{what => muc_host_pattern_missing,
490 host_type => HostType,
491 subdomain_pattern => SubdomainPattern,
492
:-(
text => <<"Only one MUC domain would work with this host type">>})
493 end,
494 11 mongoose_domain_api:register_subdomain(HostType, SubdomainPattern, PacketHandler),
495 11 register_for_global_distrib(HostType),
496 %% Loading
497 11 case LoadPermRoomsAtStartup of
498 false ->
499 11 ?LOG_INFO(#{what => load_permanent_rooms_at_startup, skip => true,
500 text => <<"Skip loading permanent rooms at startup. "
501 11 "Each room is loaded when someone access the room">>});
502 true ->
503
:-(
?LOG_WARNING(#{what => load_permanent_rooms_at_startup_is_deprecated, skip => false,
504 text => <<"Loading permanent rooms at startup is deprecated. "
505
:-(
"The option is ignored.">>})
506 end,
507 11 set_persistent_rooms_timer(State),
508 11 {ok, State}.
509
510 set_persistent_rooms_timer(#muc_state{hibernated_room_check_interval = infinity}) ->
511 2 ok;
512 set_persistent_rooms_timer(#muc_state{hibernated_room_check_interval = Timeout}) ->
513 87 timer:send_after(Timeout, stop_hibernated_persistent_rooms).
514
515 handle_call(stop, _From, State) ->
516 11 ejabberd_hooks:delete(hooks(State#muc_state.host_type)),
517 11 {stop, normal, ok, State};
518 handle_call({create_instant, ServerHost, MucHost, Room, From, Nick, Opts},
519 _From,
520 #muc_state{host_type = HostType,
521 access = Access,
522 default_room_opts = DefOpts,
523 history_size = HistorySize,
524 room_shaper = RoomShaper,
525 http_auth_pool = HttpAuthPool} = State) ->
526 121 ?LOG_DEBUG(#{what => muc_create_instant, room => Room, sub_host => MucHost}),
527 121 NewOpts = case Opts of
528 1 default -> DefOpts;
529 120 _ -> Opts
530 end,
531 121 try
532 121 {ok, Pid} = mod_muc_room:start_new(HostType,
533 MucHost, ServerHost, Access,
534 Room, HistorySize,
535 RoomShaper, HttpAuthPool, From,
536 Nick, [{instant, true}|NewOpts]),
537 121 register_room_or_stop_if_duplicate(HostType, MucHost, Room, Pid),
538 121 {reply, ok, State}
539 catch Class:Reason:Stacktrace ->
540
:-(
Err = #{what => muc_create_instant_failed,
541 server => ServerHost, host_type => HostType,
542 room => Room, from_jid => From,
543 class => Class, reason => Reason,
544 stacktrace => Stacktrace},
545
:-(
?LOG_ERROR(Err),
546
:-(
{reply, {error, Err}, State}
547 end.
548
549 handle_cast(_Msg, State) ->
550
:-(
{noreply, State}.
551
552 handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
553
:-(
clean_table_from_bad_node(Node),
554
:-(
{noreply, State};
555 handle_info(stop_hibernated_persistent_rooms,
556 #muc_state{host_type = HostType,
557 hibernated_room_timeout = Timeout} = State)
558 when is_integer(Timeout) ->
559 78 handle_stop_hibernated_persistent_rooms(HostType, Timeout),
560 78 set_persistent_rooms_timer(State),
561 78 {noreply, State};
562 handle_info(_Info, State) ->
563
:-(
{noreply, State}.
564
565 handle_stop_hibernated_persistent_rooms(HostType, Timeout) ->
566 78 ?LOG_INFO(#{what => muc_stop_hibernated_persistent_rooms, host_type => HostType,
567 78 text => <<"Closing hibernated persistent rooms">>}),
568 78 try
569 78 Supervisor = gen_mod:get_module_proc(HostType, ejabberd_mod_muc_sup),
570 78 Now = os:timestamp(),
571 78 [stop_if_hibernated(Pid, Now, Timeout * 1000) ||
572 78 {undefined, Pid, worker, _} <- supervisor:which_children(Supervisor)]
573 catch Error:Reason:Stacktrace ->
574
:-(
?LOG_ERROR(#{what => stop_hibernated_persistent_rooms_failed,
575 error => Error, reason => Reason,
576 stacktrace => Stacktrace,
577
:-(
host_type => HostType})
578 end.
579
580 stop_if_hibernated(Pid, Now, Timeout) ->
581 880 stop_if_hibernated(Pid, Now, Timeout, erlang:process_info(Pid, current_function)).
582
583 stop_if_hibernated(Pid, Now, Timeout, {current_function, {erlang, hibernate, 3}}) ->
584 513 {dictionary, Dictionary} = erlang:process_info(Pid, dictionary),
585 513 LastHibernated = lists:keyfind(hibernated, 1, Dictionary),
586 513 stop_if_hibernated_for_specified_time(Pid, Now, Timeout, LastHibernated),
587 513 ok;
588 stop_if_hibernated(_, _, _, _) ->
589 367 ok.
590
591 stop_if_hibernated_for_specified_time(_Pid, _, _, false) ->
592
:-(
ok;
593 stop_if_hibernated_for_specified_time(Pid, Now, Timeout, {hibernated, LastHibernated}) ->
594 513 TimeDiff = timer:now_diff(Now, LastHibernated),
595 513 case TimeDiff >= Timeout of
596 true ->
597 151 Pid ! stop_persistent_room_process;
598 _ ->
599 362 ok
600 end.
601
602 terminate(_Reason, #muc_state{host_type = HostType,
603 subdomain_pattern = SubdomainPattern}) ->
604 11 mongoose_domain_api:unregister_subdomain(HostType, SubdomainPattern),
605 11 unregister_for_global_distrib(HostType),
606 11 ok.
607
608 code_change(_OldVsn, State, _Extra) ->
609
:-(
{ok, State}.
610
611 %%--------------------------------------------------------------------
612 %%% Internal functions
613 %%--------------------------------------------------------------------
614 -spec start_supervisor(host_type()) -> {error, _}
615 | {ok, undefined | pid()}
616 | {ok, undefined | pid(), _}.
617 start_supervisor(HostType) ->
618 11 ChildSpec = sup_spec(HostType),
619 11 ejabberd_sup:start_child(ChildSpec).
620
621 sup_spec(HostType) ->
622 11 Proc = gen_mod:get_module_proc(HostType, ejabberd_mod_muc_sup),
623 11 {Proc,
624 {ejabberd_tmp_sup, start_link, [Proc, mod_muc_room]},
625 permanent,
626 infinity,
627 supervisor,
628 [ejabberd_tmp_sup]}.
629
630 -spec stop_supervisor(jid:server()) -> ok | {error, Reason}
631 when Reason :: not_found | restarting | running | simple_one_for_one.
632 stop_supervisor(HostType) ->
633 11 Proc = gen_mod:get_module_proc(HostType, ejabberd_mod_muc_sup),
634 11 ejabberd_sup:stop_child(Proc).
635
636 -spec process_packet(Acc :: mongoose_acc:t(),
637 From :: jid:jid(),
638 To :: jid:simple_jid() | jid:jid(),
639 El :: exml:element(),
640 #{state := state()}) -> mongoose_acc:t().
641 process_packet(Acc, From, To, El, #{state := State}) ->
642 655 {AccessRoute, _, _, _} = State#muc_state.access,
643 655 ServerHost = make_server_host(To, State),
644 655 HostType = State#muc_state.host_type,
645 655 case acl:match_rule(HostType, ServerHost, AccessRoute, From) of
646 allow ->
647 655 {Room, MucHost, _} = jid:to_lower(To),
648 655 route_to_room(MucHost, Room, {From, To, Acc, El}, State),
649 652 Acc;
650 _ ->
651
:-(
#xmlel{attrs = Attrs} = El,
652
:-(
Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
653
:-(
ErrText = <<"Access denied by service policy">>,
654
:-(
ejabberd_router:route_error_reply(To, From, Acc,
655 mongoose_xmpp_errors:forbidden(Lang, ErrText))
656 end.
657
658
659 -spec route_to_room(jid:lserver(), room(), from_to_packet(), state()) -> ok | pid().
660 route_to_room(_MucHost, <<>>, {_, To, _Acc, _} = Routed, State) ->
661 63 {_, _, Nick} = jid:to_lower(To),
662 63 route_by_nick(Nick, Routed, State);
663 route_to_room(MucHost, Room, Routed, #muc_state{} = State) ->
664 592 case mnesia:dirty_read(muc_online_room, {Room, MucHost}) of
665 [] ->
666 49 case get_registered_room_or_route_error(MucHost, Room, Routed, State) of
667 {ok, Pid} ->
668 41 route_to_online_room(Pid, Routed);
669 {route_error, _ErrText} ->
670 8 ok
671 end;
672 [R] ->
673 543 Pid = R#muc_online_room.pid,
674 543 route_to_online_room(Pid, Routed)
675 end.
676
677 route_to_online_room(Pid, {From, To, Acc, Packet}) ->
678 584 ?LOG_DEBUG(#{what => muc_route_to_online_room, room_pid => Pid, acc => Acc}),
679 584 {_, _, Nick} = jid:to_lower(To),
680 584 ok = mod_muc_room:route(Pid, From, Nick, Acc, Packet).
681
682 -spec get_registered_room_or_route_error(muc_host(), room(), from_to_packet(), state()) -> {ok, pid()} | {route_error, binary()}.
683 get_registered_room_or_route_error(MucHost, Room, {From, To, Acc, Packet}, State) ->
684 49 #xmlel{name = Name, attrs = Attrs} = Packet,
685 49 Type = xml:get_attr_s(<<"type">>, Attrs),
686 49 case {Name, Type} of
687 {<<"presence">>, <<>>} ->
688 38 get_registered_room_or_route_error_from_presence(MucHost, Room, From, To, Acc, Packet, State);
689 _ ->
690 11 get_registered_room_or_route_error_from_packet(MucHost, Room, From, To, Acc, Packet, State)
691 end.
692
693 get_registered_room_or_route_error_from_presence(MucHost, Room, From, To, Acc,
694 Packet, #muc_state{host_type = HostType, access = Access} = State) ->
695 38 {_, AccessCreate, _, _} = Access,
696 38 ServerHost = make_server_host(To, State),
697 38 case check_user_can_create_room(HostType, ServerHost, AccessCreate, From, Room) of
698 ok ->
699 37 #muc_state{history_size = HistorySize,
700 room_shaper = RoomShaper,
701 http_auth_pool = HttpAuthPool,
702 default_room_opts = DefRoomOpts} = State,
703 37 {_, _, Nick} = jid:to_lower(To),
704 37 ServerHost = make_server_host(To, State),
705 37 Result = start_new_room(HostType, ServerHost, MucHost, Access, Room,
706 HistorySize, RoomShaper, HttpAuthPool,
707 From, Nick, DefRoomOpts, Acc),
708 37 case Result of
709 {ok, Pid} ->
710 37 register_room_or_stop_if_duplicate(HostType, MucHost, Room, Pid);
711 {error, {failed_to_restore, Reason}} ->
712 %% Notify user about our backend module error
713
:-(
?LOG_WARNING(#{what => muc_send_service_unavailable,
714 text => <<"Failed to restore room">>,
715 host_type => HostType,
716 room => Room, sub_host => MucHost,
717
:-(
reason => Reason, acc => Acc}),
718
:-(
Lang = exml_query:attr(Packet, <<"xml:lang">>, <<>>),
719
:-(
ErrText = <<"Service is temporary unavailable">>,
720
:-(
{Acc1, Err} = jlib:make_error_reply(
721 Acc, Packet, mongoose_xmpp_errors:service_unavailable(Lang, ErrText)),
722
:-(
ejabberd_router:route(To, From, Acc1, Err),
723
:-(
{route_error, ErrText};
724 _ ->
725 %% Unknown error, most likely a room process failed to start.
726 %% Do not notify user (we can send "internal server error").
727
:-(
erlang:error({start_new_room_failed, Room, Result})
728 end;
729 {error, Reason} ->
730 1 Lang = exml_query:attr(Packet, <<"xml:lang">>, <<>>),
731 1 Policy = iolist_to_binary(io_lib:format("~p", [Reason])),
732 1 ErrText = <<"Room creation is denied by service policy: ", Policy/binary>>,
733 1 {Acc1, Err} = jlib:make_error_reply(
734 Acc, Packet, mongoose_xmpp_errors:not_allowed(Lang, ErrText)),
735 1 ejabberd_router:route(To, From, Acc1, Err),
736 1 {route_error, ErrText}
737 end.
738
739 get_registered_room_or_route_error_from_packet(MucHost, Room, From, To, Acc, Packet,
740 #muc_state{host_type = HostType,
741 access = Access} = State) ->
742 11 ServerHost = make_server_host(To, State),
743 11 case restore_room(HostType, MucHost, Room) of
744 {error, room_not_found} ->
745 7 Lang = exml_query:attr(Packet, <<"xml:lang">>, <<>>),
746 7 ErrText = <<"Conference room does not exist">>,
747 7 {Acc1, Err} = jlib:make_error_reply(
748 Acc, Packet, mongoose_xmpp_errors:item_not_found(Lang, ErrText)),
749 7 ejabberd_router:route(To, From, Acc1, Err),
750 7 {route_error, ErrText};
751 {error, Reason} ->
752
:-(
?LOG_WARNING(#{what => muc_send_service_unavailable,
753 room => Room, host_type => HostType, sub_host => MucHost,
754
:-(
reason => Reason, acc => Acc}),
755
:-(
Lang = exml_query:attr(Packet, <<"xml:lang">>, <<>>),
756
:-(
ErrText = <<"Service is temporary unavailable">>,
757
:-(
{Acc1, Err} = jlib:make_error_reply(
758 Acc, Packet, mongoose_xmpp_errors:service_unavailable(Lang, ErrText)),
759
:-(
ejabberd_router:route(To, From, Acc1, Err),
760
:-(
{route_error, ErrText};
761 {ok, Opts} ->
762 4 ?LOG_DEBUG(#{what => muc_restore_room, room => Room, room_opts => Opts}),
763 4 #muc_state{history_size = HistorySize,
764 room_shaper = RoomShaper,
765 http_auth_pool = HttpAuthPool} = State,
766 4 {ok, Pid} = mod_muc_room:start_restored(HostType,
767 MucHost, ServerHost, Access,
768 Room, HistorySize,
769 RoomShaper, HttpAuthPool, Opts),
770 4 register_room_or_stop_if_duplicate(HostType, MucHost, Room, Pid)
771 end.
772
773 -spec route_by_nick(room(), from_to_packet(), state()) -> 'ok' | pid().
774 route_by_nick(<<>>, {_, _, _, Packet} = Routed, State) ->
775 63 #xmlel{name = Name} = Packet,
776 63 route_by_type(Name, Routed, State);
777 route_by_nick(_Nick, {From, To, Acc, Packet}, _State) ->
778
:-(
#xmlel{attrs = Attrs} = Packet,
779
:-(
case xml:get_attr_s(<<"type">>, Attrs) of
780 <<"error">> ->
781
:-(
Acc;
782 <<"result">> ->
783
:-(
Acc;
784 _ ->
785
:-(
{Acc1, Err} = jlib:make_error_reply(Acc, Packet, mongoose_xmpp_errors:item_not_found()),
786
:-(
ejabberd_router:route(To, From, Acc1, Err)
787 end.
788
789 -spec route_by_type(binary(), from_to_packet(), state()) -> ok | pid().
790 route_by_type(<<"iq">>, {From, To, Acc, Packet}, #muc_state{} = State) ->
791 63 HostType = State#muc_state.host_type,
792 63 MucHost = To#jid.lserver,
793 63 case jlib:iq_query_info(Packet) of
794 #iq{type = get, xmlns = ?NS_DISCO_INFO = XMLNS, lang = Lang} = IQ ->
795 1 IdentityXML = mongoose_disco:identities_to_xml([identity(Lang)]),
796 1 FeatureXML = mongoose_disco:get_muc_features(HostType, From, To, <<>>, Lang,
797 features()),
798 1 InfoXML = mongoose_disco:get_info(HostType, ?MODULE, <<>>, Lang),
799 1 Res = IQ#iq{type = result,
800 sub_el = [#xmlel{name = <<"query">>,
801 attrs = [{<<"xmlns">>, XMLNS}],
802 children = IdentityXML ++ FeatureXML ++ InfoXML}]},
803 1 ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
804 #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ ->
805 20 proc_lib:spawn(fun() -> process_iq_disco_items(MucHost, From, To, IQ) end);
806 #iq{type = get, xmlns = ?NS_REGISTER = XMLNS, lang = Lang} = IQ ->
807 22 Result = iq_get_register_info(HostType, MucHost, From, Lang),
808 22 Res = IQ#iq{type = result,
809 sub_el = [#xmlel{name = <<"query">>,
810 attrs = [{<<"xmlns">>, XMLNS}],
811 children = Result}]},
812 22 ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
813 #iq{type = set,
814 xmlns = ?NS_REGISTER = XMLNS,
815 lang = Lang,
816 sub_el = SubEl} = IQ ->
817 20 case process_iq_register_set(HostType, MucHost, From, SubEl, Lang) of
818 {result, IQRes} ->
819 20 Res = IQ#iq{type = result,
820 sub_el = [#xmlel{name = <<"query">>,
821 attrs = [{<<"xmlns">>, XMLNS}],
822 children = IQRes}]},
823 20 ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
824 {error, Error} ->
825
:-(
{Acc1, Err} = jlib:make_error_reply(Acc, Packet, Error),
826
:-(
ejabberd_router:route(To, From, Acc1, Err)
827 end;
828 #iq{type = get, xmlns = ?NS_VCARD = XMLNS, lang = Lang} = IQ ->
829
:-(
Res = IQ#iq{type = result,
830 sub_el = [#xmlel{name = <<"vCard">>,
831 attrs = [{<<"xmlns">>, XMLNS}],
832 children = iq_get_vcard(Lang)}]},
833
:-(
ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
834 #iq{type = get, xmlns = ?NS_MUC_UNIQUE} = IQ ->
835
:-(
Res = IQ#iq{type = result,
836 sub_el = [#xmlel{name = <<"unique">>,
837 attrs = [{<<"xmlns">>, ?NS_MUC_UNIQUE}],
838 children = [iq_get_unique(From)]}]},
839
:-(
ejabberd_router:route(To, From, jlib:iq_to_xml(Res));
840 #iq{} ->
841
:-(
?LOG_INFO(#{what => muc_ignore_unknown_iq, acc => Acc}),
842
:-(
{Acc1, Err} = jlib:make_error_reply(Acc, Packet,
843 mongoose_xmpp_errors:feature_not_implemented(<<"en">>, <<"From mod_muc">>)),
844
:-(
ejabberd_router:route(To, From, Acc1, Err);
845 Other ->
846
:-(
?LOG_INFO(#{what => muc_failed_to_parse_iq, acc => Acc, reason => Other}),
847
:-(
ok
848 end;
849 route_by_type(<<"message">>, {From, To, Acc, Packet},
850 #muc_state{host_type = HostType,
851 access = {_, _, AccessAdmin, _}} = State) ->
852
:-(
MucHost = To#jid.lserver,
853
:-(
ServerHost = make_server_host(To, State),
854
:-(
#xmlel{attrs = Attrs} = Packet,
855
:-(
case xml:get_attr_s(<<"type">>, Attrs) of
856 <<"error">> ->
857
:-(
ok;
858 _ ->
859
:-(
case acl:match_rule(HostType, ServerHost, AccessAdmin, From) of
860 allow ->
861
:-(
Msg = xml:get_path_s(Packet, [{elem, <<"body">>}, cdata]),
862
:-(
broadcast_service_message(MucHost, Msg);
863 _ ->
864
:-(
Lang = xml:get_attr_s(<<"xml:lang">>, Attrs),
865
:-(
ErrTxt = <<"Only service administrators are allowed to send service messages">>,
866
:-(
Err = mongoose_xmpp_errors:forbidden(Lang, ErrTxt),
867
:-(
{Acc1, ErrorReply} = jlib:make_error_reply(Acc, Packet, Err),
868
:-(
ejabberd_router:route(To, From, Acc1, ErrorReply)
869 end
870 end;
871 route_by_type(<<"presence">>, _Routed, _State) ->
872
:-(
ok.
873
874 -spec check_user_can_create_room(host_type(), jid:lserver(),
875 allow | atom(), jid:jid(), room()) -> ok | {error, term()}.
876 check_user_can_create_room(HostType, ServerHost, AccessCreate, From, RoomID) ->
877 38 case acl:match_rule(HostType, ServerHost, AccessCreate, From) of
878 allow ->
879 37 MaxLen = gen_mod:get_module_opt(HostType, mod_muc, max_room_id),
880 37 case (size(RoomID) =< MaxLen) of
881 37 true -> ok;
882
:-(
false -> {error, room_id_too_long}
883 end;
884 _ ->
885 1 ?LOG_WARNING(#{what => check_user_can_create_room_failed,
886 host_type => HostType,
887 server => ServerHost,
888 access_create => AccessCreate,
889 from_jid => From,
890
:-(
room_id => RoomID}),
891 1 {error, no_matching_acl_rule}
892 end.
893
894 -spec start_new_room(HostType :: host_type(), ServerHost :: jid:lserver(),
895 MucHost :: muc_host(), Access :: access(), room(),
896 HistorySize :: 'undefined' | integer(), RoomShaper :: shaper:shaper(),
897 HttpAuthPool :: none | mongoose_http_client:pool(), From :: jid:jid(), nick(),
898 DefRoomOpts :: 'undefined' | [any()], Acc :: mongoose_acc:t())
899 -> {'error', _}
900 | {'ok', 'undefined' | pid()}
901 | {'ok', 'undefined' | pid(), _}.
902 start_new_room(HostType, ServerHost, MucHost, Access, Room,
903 HistorySize, RoomShaper, HttpAuthPool, From,
904 Nick, DefRoomOpts, Acc) ->
905 37 case mod_muc_backend:restore_room(HostType, MucHost, Room) of
906 {error, room_not_found} ->
907 35 ?LOG_DEBUG(#{what => muc_start_new_room, acc => Acc,
908 35 room => Room, host_type => HostType, sub_host => MucHost}),
909 35 mod_muc_room:start_new(HostType,
910 MucHost, ServerHost, Access,
911 Room, HistorySize,
912 RoomShaper, HttpAuthPool, From,
913 Nick, DefRoomOpts);
914 {error, Reason} ->
915
:-(
{error, {failed_to_restore, Reason}};
916 {ok, Opts} ->
917 2 ?LOG_DEBUG(#{what => muc_restore_room, acc => Acc, room => Room,
918 2 host_type => HostType, sub_host => MucHost, room_opts => Opts}),
919 2 mod_muc_room:start_restored(HostType,
920 MucHost, ServerHost, Access,
921 Room, HistorySize,
922 RoomShaper, HttpAuthPool, Opts)
923 end.
924
925 register_room_or_stop_if_duplicate(HostType, MucHost, Room, Pid) ->
926 162 case register_room(HostType, MucHost, Room, Pid) of
927 {_, ok} ->
928 158 {ok, Pid};
929 {_, {exists, OldPid}} ->
930 4 mod_muc_room:stop(Pid),
931 4 {ok, OldPid}
932 end.
933
934 -spec register_room(HostType :: host_type(), jid:server(), room(),
935 pid()) -> {'aborted', _} | {'atomic', _}.
936 register_room(HostType, MucHost, Room, Pid) ->
937 165 F = fun() ->
938 165 case mnesia:read(muc_online_room, {Room, MucHost}, write) of
939 [] ->
940 161 mnesia:write(#muc_online_room{name_host = {Room, MucHost},
941 host_type = HostType,
942 pid = Pid});
943 [R] ->
944 4 {exists, R#muc_online_room.pid}
945 end
946 end,
947 165 mnesia:transaction(F).
948
949
950 -spec room_jid_to_pid(RoomJID :: jid:jid()) -> {ok, pid()} | {error, not_found}.
951 room_jid_to_pid(#jid{luser=RoomName, lserver=MucService}) ->
952 25 case mnesia:dirty_read(muc_online_room, {RoomName, MucService}) of
953 [R] ->
954 22 {ok, R#muc_online_room.pid};
955 [] ->
956 3 {error, not_found}
957 end.
958
959 -spec default_host() -> mongoose_subdomain_utils:subdomain_pattern().
960 default_host() ->
961 350 mongoose_subdomain_utils:make_subdomain_pattern(<<"conference.@HOST@">>).
962
963 identity(Lang) ->
964 1 #{category => <<"conference">>,
965 type => <<"text">>,
966 name => translate:translate(Lang, <<"Chatrooms">>)}.
967
968 features() ->
969 1 [?NS_DISCO_INFO, ?NS_DISCO_ITEMS, ?NS_MUC, ?NS_MUC_UNIQUE, ?NS_REGISTER, ?NS_RSM, ?NS_VCARD].
970
971 %% Disco for rooms
972 -spec iq_disco_items(muc_host(), jid:jid(), ejabberd:lang(),
973 Rsm :: none | jlib:rsm_in()) -> any().
974 iq_disco_items(MucHost, From, Lang, none) ->
975 6 AllRooms = get_vh_rooms(MucHost) ++ get_persistent_vh_rooms(MucHost),
976 6 Rooms = lists:ukeysort(1, lists:map(fun record_to_simple/1, AllRooms)),
977 6 BareRooms = lists:filtermap(fun(Room) -> room_to_item(Room, MucHost, From, Lang) end, Rooms),
978 6 lists:ukeysort(3, BareRooms);
979 iq_disco_items(MucHost, From, Lang, Rsm) ->
980 14 {Rooms, RsmO} = get_vh_rooms(MucHost, Rsm),
981 14 RsmOut = jlib:rsm_encode(RsmO),
982 14 lists:filtermap(fun(Room) -> room_to_item(Room, MucHost, From, Lang) end, Rooms) ++ RsmOut.
983
984 room_to_item({{Name, _}, Pid}, MucHost, From, Lang) when is_pid(Pid) ->
985 153 case catch gen_fsm_compat:sync_send_all_state_event(
986 Pid, {get_disco_item, From, Lang}, 100) of
987 {item, Desc} ->
988 153 {true,
989 #xmlel{name = <<"item">>,
990 attrs = [{<<"jid">>, jid:to_binary({Name, MucHost, <<>>})},
991 {<<"name">>, Desc}]}};
992 _ ->
993
:-(
false
994 end;
995 room_to_item({{ Name, _ }, _}, MucHost, _, _) ->
996 60 {true,
997 #xmlel{name = <<"item">>,
998 attrs = [{<<"jid">>, jid:to_binary({Name, MucHost, <<>>})},
999 {<<"name">>, Name}]}
1000 }.
1001 record_to_simple(#muc_online_room{name_host = Room, pid = Pid}) ->
1002 310 {Room, Pid};
1003 record_to_simple(#muc_room{name_host = Room, opts = Opts}) ->
1004 105 {Room, Opts}.
1005
1006 -spec get_vh_rooms(muc_host(), jlib:rsm_in()) -> {list(), jlib:rsm_out()}.
1007 get_vh_rooms(MucHost, #rsm_in{max=Max, direction=Direction, id=I, index=Index}) ->
1008 14 NonUndefMax = case Max of
1009 4 undefined -> 134217728;
1010 10 _ -> Max
1011 end,
1012 14 Rooms = get_vh_rooms(MucHost) ++ get_persistent_vh_rooms(MucHost),
1013 14 BareSortedRooms = lists:ukeysort(1, lists:map(fun record_to_simple/1, Rooms)),
1014 14 Count = erlang:length(BareSortedRooms),
1015 14 L2 = case {Index, Direction} of
1016 {undefined, undefined} ->
1017 2 lists:sublist(BareSortedRooms, 1, NonUndefMax);
1018 {undefined, aft} ->
1019 2 lists:sublist(
1020 lists:dropwhile(
1021 23 fun({{Id, _}, _}) -> Id =< I end,
1022 BareSortedRooms),
1023 1,
1024 NonUndefMax);
1025 {undefined,before} when I == <<>> ->
1026 1 lists:reverse(
1027 lists:sublist(
1028 lists:reverse(BareSortedRooms), 1, NonUndefMax));
1029 {undefined, before} ->
1030 2 L = lists:takewhile(
1031 21 fun({{Id, _}, _}) -> Id < I end,
1032 BareSortedRooms),
1033 2 lists:reverse(
1034 lists:sublist(
1035 lists:reverse(L), 1, NonUndefMax));
1036 2 {Index, _} when Index < 0 orelse Index > Count -> [];
1037 {Index, _} ->
1038 5 lists:sublist(BareSortedRooms, Index + 1, NonUndefMax);
1039 Input ->
1040
:-(
?LOG_ERROR(#{what => muc_get_rooms_with_pagination_failed,
1041 text => <<"Unexpected result in get_rooms_with_pagination">>,
1042
:-(
reason => Input}),
1043
:-(
[]
1044 end,
1045 14 case L2 of
1046 [] ->
1047 4 {L2, #rsm_out{count=Count}};
1048 _ ->
1049 10 H = hd(L2),
1050 10 NewIndex = get_room_pos(H, BareSortedRooms),
1051
1052 10 {{F, _},_} = H,
1053 10 {{Last, _}, _} = lists:last(L2),
1054 10 {L2, #rsm_out{first=F, last=Last, count=Count, index=NewIndex}}
1055 end.
1056
1057 %% @doc Return the position of desired room in the list of rooms.
1058 %% The room must exist in the list. The count starts in 0.
1059 -spec get_room_pos({{binary(), any()}, any()}, [{{binary(), any()}, any}]) -> non_neg_integer().
1060 get_room_pos(Desired, Rooms) ->
1061 10 get_room_pos(Desired, Rooms, 0).
1062 get_room_pos({{NameHost, _}, _}, [{{NameHost, _}, _} | _], HeadPosition) ->
1063 10 HeadPosition;
1064 get_room_pos(Desired, [_ | Rooms], HeadPosition) ->
1065 71 get_room_pos(Desired, Rooms, HeadPosition + 1).
1066
1067 -spec xfield(Type :: binary(), Label :: binary(), Var :: binary(),
1068 Val :: binary(), ejabberd:lang()) -> exml:element().
1069 xfield(Type, Label, Var, Val, Lang) ->
1070 22 #xmlel{name = <<"field">>,
1071 attrs = [{<<"type">>, Type},
1072 {<<"label">>, translate:translate(Lang, Label)},
1073 {<<"var">>, Var}],
1074 children = [#xmlel{name = <<"value">>,
1075 children = [#xmlcdata{content = Val}]}]}.
1076
1077
1078 %% @doc Get a pseudo unique Room Name. The Room Name is generated as a hash of
1079 %% the requester JID, the local time and a random salt.
1080 %%
1081 %% `<<"pseudo">>' because we don't verify that there is not a room
1082 %% with the returned Name already created, nor mark the generated Name
1083 %% as `<<"already used">>'. But in practice, it is unique enough. See
1084 %% http://xmpp.org/extensions/xep-0045.html#createroom-unique
1085 -spec iq_get_unique(jid:jid()) -> jlib:xmlcdata().
1086 iq_get_unique(From) ->
1087
:-(
#xmlcdata{content = sha:sha1_hex(term_to_binary([From, erlang:unique_integer(),
1088 mongoose_bin:gen_from_crypto()]))}.
1089
1090
1091 -spec iq_get_register_info(host_type(), jid:server(),
1092 jid:simple_jid() | jid:jid(), ejabberd:lang())
1093 -> [jlib:xmlel(), ...].
1094 iq_get_register_info(HostType, MucHost, From, Lang) ->
1095 22 {Nick, Registered} =
1096 case catch get_nick(HostType, MucHost, From) of
1097 {'EXIT', _Reason} ->
1098
:-(
{<<>>, []};
1099 {error, _} ->
1100 8 {<<>>, []};
1101 {ok, N} ->
1102 14 {N, [#xmlel{name = <<"registered">>}]}
1103 end,
1104 22 ClientReqText = translate:translate(
1105 Lang, <<"You need a client that supports x:data to register the nickname">>),
1106 22 ClientReqEl = #xmlel{name = <<"instructions">>,
1107 children = [#xmlcdata{content = ClientReqText}]},
1108 22 EnterNicknameText = translate:translate(Lang, <<"Enter nickname you want to register">>),
1109 22 EnterNicknameEl = #xmlel{name = <<"instructions">>,
1110 children = [#xmlcdata{content = EnterNicknameText}]},
1111 22 TitleText = <<(translate:translate(Lang, <<"Nickname Registration at ">>))/binary,
1112 MucHost/binary>>,
1113 22 TitleEl = #xmlel{name = <<"title">>, children = [#xmlcdata{content = TitleText}]},
1114 22 Registered ++
1115 [ClientReqEl,
1116 #xmlel{name = <<"x">>, attrs = [{<<"xmlns">>, ?NS_XDATA}],
1117 children = [TitleEl,
1118 EnterNicknameEl,
1119 xfield(<<"text-single">>, <<"Nickname">>, <<"nick">>, Nick, Lang)]}].
1120
1121
1122 -spec iq_set_register_info(host_type(), jid:server(),
1123 jid:simple_jid() | jid:jid(), nick(), ejabberd:lang())
1124 -> {'error', jlib:xmlel()} | {'result', []}.
1125 iq_set_register_info(HostType, MucHost, From, Nick, Lang) ->
1126 14 case set_nick(HostType, MucHost, From, Nick) of
1127 ok ->
1128 14 {result, []};
1129 {error, conflict} ->
1130
:-(
ErrText = <<"That nickname is registered by another person">>,
1131
:-(
{error, mongoose_xmpp_errors:conflict(Lang, ErrText)};
1132 {error, should_not_be_empty} ->
1133
:-(
ErrText = <<"You must fill in field \"Nickname\" in the form">>,
1134
:-(
{error, mongoose_xmpp_errors:not_acceptable(Lang, ErrText)};
1135 {error, ErrorReason} ->
1136
:-(
?LOG_ERROR(#{what => muc_iq_set_register_info_failed,
1137 host_type => HostType, sub_host => MucHost,
1138 from_jid => jid:to_binary(From), nick => Nick,
1139
:-(
reason => ErrorReason}),
1140
:-(
{error, mongoose_xmpp_errors:internal_server_error()}
1141 end.
1142
1143 -spec iq_set_unregister_info(host_type(), jid:server(),
1144 jid:simple_jid() | jid:jid(), ejabberd:lang())
1145 -> {'error', jlib:xmlel()} | {'result', []}.
1146 iq_set_unregister_info(HostType, MucHost, From, _Lang) ->
1147 6 case unset_nick(HostType, MucHost, From) of
1148 ok ->
1149 6 {result, []};
1150 {error, ErrorReason} ->
1151
:-(
?LOG_ERROR(#{what => muc_iq_set_unregister_info_failed,
1152 host_type => HostType, sub_host => MucHost,
1153
:-(
from_jid => jid:to_binary(From), reason => ErrorReason}),
1154
:-(
{error, mongoose_xmpp_errors:internal_server_error()}
1155 end.
1156
1157 -spec process_iq_register_set(host_type(), jid:server(),
1158 jid:jid(), jlib:xmlel(), ejabberd:lang())
1159 -> {'error', jlib:xmlel()} | {'result', []}.
1160 process_iq_register_set(HostType, MucHost, From, SubEl, Lang) ->
1161 20 #xmlel{children = Els} = SubEl,
1162 20 case xml:get_subtag(SubEl, <<"remove">>) of
1163 false ->
1164 14 case xml:remove_cdata(Els) of
1165 [#xmlel{name = <<"x">>} = XEl] ->
1166 14 process_register(xml:get_tag_attr_s(<<"xmlns">>, XEl),
1167 xml:get_tag_attr_s(<<"type">>, XEl),
1168 HostType, MucHost, From, Lang, XEl);
1169 _ ->
1170
:-(
{error, mongoose_xmpp_errors:bad_request()}
1171 end;
1172 _ ->
1173 6 iq_set_unregister_info(HostType, MucHost, From, Lang)
1174 end.
1175
1176 -spec process_register(XMLNS :: binary(), Type :: binary(),
1177 HostType :: host_type(), MucHost :: jid:server(),
1178 From :: jid:jid(), Lang :: ejabberd:lang(), XEl :: exml:element()) ->
1179 {error, exml:element()} | {result, []}.
1180 process_register(?NS_XDATA, <<"cancel">>, _HostType, _Host, _From, _Lang, _XEl) ->
1181
:-(
{result, []};
1182 process_register(?NS_XDATA, <<"submit">>, HostType, MucHost, From, Lang, XEl) ->
1183 14 XData = jlib:parse_xdata_submit(XEl),
1184 14 case XData of
1185 invalid ->
1186
:-(
{error, mongoose_xmpp_errors:bad_request()};
1187 _ ->
1188 14 case lists:keysearch(<<"nick">>, 1, XData) of
1189 {value, {_, [Nick]}} when Nick /= <<>> ->
1190 14 iq_set_register_info(HostType, MucHost, From, Nick, Lang);
1191 _ ->
1192
:-(
ErrText = <<"You must fill in field \"Nickname\" in the form">>,
1193
:-(
{error, mongoose_xmpp_errors:not_acceptable(Lang, ErrText)}
1194 end
1195 end;
1196 process_register(_, _, _HostType, _MucHost, _From, _Lang, _XEl) ->
1197
:-(
{error, mongoose_xmpp_errors:bad_request()}.
1198
1199 -spec iq_get_vcard(ejabberd:lang()) -> [exml:element(), ...].
1200 iq_get_vcard(Lang) ->
1201
:-(
[#xmlel{name = <<"FN">>,
1202 children = [#xmlcdata{content = <<"ejabberd/mod_muc">>}]},
1203 #xmlel{name = <<"URL">>, children = [#xmlcdata{content = ?MONGOOSE_URI}]},
1204 #xmlel{name = <<"DESC">>,
1205 children = [#xmlcdata{content =
1206 <<(translate:translate(Lang, <<"ejabberd MUC module">>))/binary,
1207 "\nCopyright (c) 2003-2011 ProcessOne">>}]}].
1208
1209 -spec broadcast_service_message(muc_host(), binary() | string()) -> ok.
1210 broadcast_service_message(MucHost, Msg) ->
1211
:-(
lists:foreach(
1212 fun(#muc_online_room{pid = Pid}) ->
1213
:-(
gen_fsm_compat:send_all_state_event(
1214 Pid, {service_message, Msg})
1215 end, get_vh_rooms(MucHost)).
1216
1217 -spec get_vh_rooms(muc_host()) -> [muc_online_room()].
1218 get_vh_rooms(MucHost) ->
1219 20 mnesia:dirty_select(muc_online_room,
1220 [{#muc_online_room{name_host = '$1', _ = '_'},
1221 [{'==', {element, 2, '$1'}, MucHost}],
1222 ['$_']}]).
1223
1224 -spec get_persistent_vh_rooms(muc_host()) -> [muc_room()].
1225 get_persistent_vh_rooms(MucHost) ->
1226 20 {ok, HostType} = mongoose_domain_api:get_subdomain_host_type(MucHost),
1227 20 case mod_muc_backend:get_rooms(HostType, MucHost) of
1228 {ok, List} ->
1229 20 List;
1230 {error, _} ->
1231
:-(
[]
1232 end.
1233
1234 -spec clean_table_from_bad_node(node()) -> any().
1235 clean_table_from_bad_node(Node) ->
1236
:-(
F = fun() ->
1237
:-(
Es = mnesia:select(
1238 muc_online_room,
1239 [{#muc_online_room{pid = '$1', _ = '_'},
1240 [{'==', {node, '$1'}, Node}],
1241 ['$_']}]),
1242
:-(
lists:foreach(fun(E) ->
1243
:-(
mnesia:delete_object(E)
1244 end, Es)
1245 end,
1246
:-(
mnesia:async_dirty(F).
1247
1248
1249 -spec clean_table_from_bad_node(node(), host_type()) -> any().
1250 clean_table_from_bad_node(Node, HostType) ->
1251 11 F = fun() ->
1252 11 Es = mnesia:select(
1253 muc_online_room,
1254 [{#muc_online_room{pid = '$1',
1255 host_type = HostType,
1256 _ = '_'},
1257 [{'==', {node, '$1'}, Node}],
1258 ['$_']}]),
1259 11 lists:foreach(fun(E) ->
1260 17 mnesia:delete_object(E)
1261 end, Es)
1262 end,
1263 11 mnesia:async_dirty(F).
1264
1265 %%====================================================================
1266 %% Hooks handlers
1267 %%====================================================================
1268
1269 -spec is_muc_room_owner(Acc :: boolean(), Acc :: mongoose_acc:t(),
1270 Room :: jid:jid(), User :: jid:jid()) -> boolean().
1271 is_muc_room_owner(true, _Acc, _Room, _User) ->
1272
:-(
true;
1273 is_muc_room_owner(_, _Acc, Room, User) ->
1274
:-(
mod_muc_room:is_room_owner(Room, User) =:= {ok, true}.
1275
1276 -spec can_access_room(Acc :: boolean(), Acc :: mongoose_acc:t(),
1277 Room :: jid:jid(), User :: jid:jid()) ->
1278 boolean().
1279 can_access_room(true, _Acc, _Room, _User) ->
1280
:-(
true;
1281 can_access_room(_, _Acc, Room, User) ->
1282
:-(
case mod_muc_room:can_access_room(Room, User) of
1283
:-(
{error, _} -> false;
1284
:-(
{ok, CanAccess} -> CanAccess
1285 end.
1286
1287 -spec remove_domain(mongoose_hooks:simple_acc(),
1288 mongooseim:host_type(), jid:lserver()) ->
1289 mongoose_hooks:simple_acc().
1290 remove_domain(Acc, HostType, Domain) ->
1291
:-(
MUCHost = server_host_to_muc_host(HostType, Domain),
1292
:-(
mod_muc_backend:remove_domain(HostType, MUCHost, Domain),
1293
:-(
Acc.
1294
1295 -spec acc_room_affiliations(mongoose_acc:t(), jid:jid()) -> mongoose_acc:t().
1296 acc_room_affiliations(Acc1, Room) ->
1297
:-(
case mongoose_acc:get(?MODULE, affiliations, {error, not_found}, Acc1) of
1298 {error, _} ->
1299
:-(
case mod_muc_room:get_room_users(Room) of
1300 {error, not_found} ->
1301
:-(
Acc1;
1302 {ok, _Affs} = Res ->
1303
:-(
mongoose_acc:set(?MODULE, affiliations, Res, Acc1)
1304 end;
1305 _Affs ->
1306
:-(
Acc1
1307 end.
1308
1309 -spec can_access_identity(Acc :: boolean(), HostType :: mongooseim:host_type(),
1310 Room :: jid:jid(), User :: jid:jid()) ->
1311 boolean().
1312 can_access_identity(true, _HostType, _Room, _User) ->
1313
:-(
true;
1314 can_access_identity(_, _HostType, Room, User) ->
1315
:-(
case mod_muc_room:can_access_identity(Room, User) of
1316
:-(
{error, _} -> false;
1317
:-(
{ok, CanAccess} -> CanAccess
1318 end.
1319
1320 online_rooms_number() ->
1321 10 lists:sum([online_rooms_number(HostType)
1322 10 || HostType <- gen_mod:hosts_with_module(?MODULE)]).
1323
1324 online_rooms_number(HostType) ->
1325 1 try
1326 1 Supervisor = gen_mod:get_module_proc(HostType, ejabberd_mod_muc_sup),
1327 1 Stats = supervisor:count_children(Supervisor),
1328 1 proplists:get_value(active, Stats)
1329 catch _:_ ->
1330
:-(
0
1331 end.
1332
1333 hibernated_rooms_number() ->
1334 10 lists:sum([hibernated_rooms_number(HostType)
1335 10 || HostType <- gen_mod:hosts_with_module(?MODULE)]).
1336
1337 hibernated_rooms_number(HostType) ->
1338 1 try
1339 1 count_hibernated_rooms(HostType)
1340 catch _:_ ->
1341
:-(
0
1342 end.
1343
1344 count_hibernated_rooms(HostType) ->
1345 1 AllRooms = all_room_pids(HostType),
1346 1 lists:foldl(fun count_hibernated_rooms/2, 0, AllRooms).
1347
1348 all_room_pids(HostType) ->
1349 1 Supervisor = gen_mod:get_module_proc(HostType, ejabberd_mod_muc_sup),
1350 1 [Pid || {undefined, Pid, worker, _} <- supervisor:which_children(Supervisor)].
1351
1352
1353 count_hibernated_rooms(Pid, Count) ->
1354 10 case erlang:process_info(Pid, current_function) of
1355 {current_function, {erlang, hibernate, _}} ->
1356 9 Count + 1;
1357 _ ->
1358 1 Count
1359 end.
1360
1361 -define(EX_EVAL_SINGLE_VALUE, {[{l, [{t, [value, {v, 'Value'}]}]}], [value]}).
1362 ensure_metrics(_Host) ->
1363 11 mongoose_metrics:ensure_metric(global, [mod_muc, deep_hibernations], spiral),
1364 11 mongoose_metrics:ensure_metric(global, [mod_muc, process_recreations], spiral),
1365 11 mongoose_metrics:ensure_metric(global, [mod_muc, hibernations], spiral),
1366 11 mongoose_metrics:ensure_metric(global, [mod_muc, hibernated_rooms],
1367 {function, mod_muc, hibernated_rooms_number, [],
1368 eval, ?EX_EVAL_SINGLE_VALUE}),
1369 11 mongoose_metrics:ensure_metric(global, [mod_muc, online_rooms],
1370 {function, mod_muc, online_rooms_number, [],
1371 eval, ?EX_EVAL_SINGLE_VALUE}).
1372
1373 config_metrics(HostType) ->
1374
:-(
OptsToReport = [{backend, mnesia}], %list of tuples {option, defualt_value}
1375
:-(
mongoose_module_metrics:opts_for_module(HostType, ?MODULE, OptsToReport).
1376
1377 hooks(HostType) ->
1378 22 [{is_muc_room_owner, HostType, ?MODULE, is_muc_room_owner, 50},
1379 {can_access_room, HostType, ?MODULE, can_access_room, 50},
1380 {remove_domain, HostType, ?MODULE, remove_domain, 50},
1381 {acc_room_affiliations, HostType, ?MODULE, acc_room_affiliations, 50},
1382 {can_access_identity, HostType, ?MODULE, can_access_identity, 50},
1383 {disco_local_items, HostType, ?MODULE, disco_local_items, 250}].
1384
1385 subdomain_pattern(HostType) ->
1386 16 gen_mod:get_module_opt(HostType, ?MODULE, host).
1387
1388 server_host_to_muc_host(HostType, ServerHost) ->
1389 16 mongoose_subdomain_utils:get_fqdn(subdomain_pattern(HostType), ServerHost).
1390
1391 -spec disco_local_items(mongoose_disco:item_acc()) -> mongoose_disco:item_acc().
1392 disco_local_items(Acc = #{host_type := HostType,
1393 to_jid := #jid{lserver = ServerHost},
1394 node := <<>>}) ->
1395 2 MUCHost = server_host_to_muc_host(HostType, ServerHost),
1396 2 Items = [#{jid => MUCHost, node => ?NS_MUC}],
1397 2 mongoose_disco:add_items(Items, Acc);
1398 disco_local_items(Acc) ->
1399
:-(
Acc.
1400
1401 make_server_host(To, #muc_state{host_type = HostType,
1402 subdomain_pattern = SubdomainPattern}) ->
1403 741 case SubdomainPattern of
1404 {prefix, _} ->
1405 741 mod_muc_light_utils:room_jid_to_server_host(To);
1406 {fqdn, _} ->
1407
:-(
HostType
1408 end.
1409
1410 register_for_global_distrib(HostType) ->
1411 %% Would not work for multitenancy
1412 11 SubHost = gen_mod:get_module_opt_subhost(HostType, ?MODULE, default_host()),
1413 11 mongoose_hooks:register_subhost(SubHost, false).
1414
1415 unregister_for_global_distrib(HostType) ->
1416 11 SubHost = gen_mod:get_module_opt_subhost(HostType, ?MODULE, default_host()),
1417 11 mongoose_hooks:unregister_subhost(SubHost).
Line Hits Source