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