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