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