./ct_report/coverage/mod_muc.COVER.html

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