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