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 |
12 |
Proc = gen_mod:get_module_proc(HostType, ?PROCNAME), |
165 |
12 |
gen_server:start_link({local, Proc}, ?MODULE, [HostType, Opts], []). |
166 |
|
|
167 |
|
-spec start(host_type(), _) -> ok. |
168 |
|
start(HostType, Opts) -> |
169 |
12 |
ensure_metrics(HostType), |
170 |
12 |
start_supervisor(HostType), |
171 |
12 |
start_server(HostType, Opts), |
172 |
12 |
assert_server_running(HostType), |
173 |
12 |
ok. |
174 |
|
|
175 |
|
-spec stop(host_type()) -> ok. |
176 |
|
stop(HostType) -> |
177 |
12 |
stop_supervisor(HostType), |
178 |
12 |
stop_gen_server(HostType), |
179 |
12 |
ok. |
180 |
|
|
181 |
|
-spec supported_features() -> [atom()]. |
182 |
|
supported_features() -> |
183 |
:-( |
[dynamic_domains]. |
184 |
|
|
185 |
|
start_server(HostType, Opts) -> |
186 |
12 |
Proc = gen_mod:get_module_proc(HostType, ?PROCNAME), |
187 |
12 |
ChildSpec = |
188 |
|
{Proc, |
189 |
|
{?MODULE, start_link, [HostType, Opts]}, |
190 |
|
temporary, |
191 |
|
1000, |
192 |
|
worker, |
193 |
|
[?MODULE]}, |
194 |
12 |
{ok, _} = ejabberd_sup:start_child(ChildSpec). |
195 |
|
|
196 |
|
assert_server_running(HostType) -> |
197 |
12 |
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 |
12 |
Proc = gen_mod:get_module_proc(HostType, ?PROCNAME), |
304 |
12 |
gen_server:call(Proc, stop), |
305 |
|
%% Proc can still be alive because of a race condition |
306 |
12 |
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 |
142 |
Obj = #muc_online_room{name_host = {Room, MucHost}, |
317 |
|
host_type = HostType, pid = Pid}, |
318 |
142 |
F = fun() -> mnesia:delete_object(Obj) end, |
319 |
142 |
{atomic, ok} = mnesia:transaction(F), |
320 |
142 |
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 |
125 |
{ok, HostType} = mongoose_domain_api:get_domain_host_type(ServerHost), |
330 |
125 |
Proc = gen_mod:get_module_proc(HostType, ?PROCNAME), |
331 |
125 |
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 |
125 |
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 |
209 |
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 |
12 |
mod_muc_backend:init(HostType, Opts), |
397 |
12 |
mnesia:create_table(muc_online_room, |
398 |
|
[{ram_copies, [node()]}, |
399 |
|
{attributes, record_info(fields, muc_online_room)}]), |
400 |
12 |
mnesia:add_table_copy(muc_online_room, node(), ram_copies), |
401 |
12 |
catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]), |
402 |
12 |
clean_table_from_bad_node(node(), HostType), |
403 |
12 |
mnesia:subscribe(system), |
404 |
12 |
Access = gen_mod:get_opt(access, Opts, all), |
405 |
12 |
AccessCreate = gen_mod:get_opt(access_create, Opts, all), |
406 |
12 |
AccessAdmin = gen_mod:get_opt(access_admin, Opts, none), |
407 |
12 |
AccessPersistent = gen_mod:get_opt(access_persistent, Opts, all), |
408 |
12 |
HttpAuthPool = gen_mod:get_opt(http_auth_pool, Opts, none), |
409 |
12 |
HistorySize = gen_mod:get_opt(history_size, Opts, 20), |
410 |
12 |
DefRoomOpts = gen_mod:get_opt(default_room_options, Opts, []), |
411 |
12 |
RoomShaper = gen_mod:get_opt(room_shaper, Opts, none), |
412 |
12 |
CheckInterval = gen_mod:get_opt(hibernated_room_check_interval, Opts, infinity), |
413 |
12 |
HibernatedTimeout = gen_mod:get_opt(hibernated_room_timeout, Opts, infinity), |
414 |
12 |
SubdomainPattern = gen_mod:get_opt(host, Opts, default_host()), |
415 |
12 |
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 |
12 |
ejabberd_hooks:add(hooks(HostType)), |
426 |
|
%% Handler |
427 |
12 |
PacketHandler = mongoose_packet_handler:new(?MODULE, #{state => State}), |
428 |
12 |
case SubdomainPattern of |
429 |
12 |
{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 |
12 |
mongoose_domain_api:register_subdomain(HostType, SubdomainPattern, PacketHandler), |
436 |
12 |
register_for_global_distrib(HostType), |
437 |
|
%% Loading |
438 |
12 |
case gen_mod:get_module_opt(HostType, mod_muc, load_permanent_rooms_at_startup, false) of |
439 |
|
false -> |
440 |
12 |
?LOG_INFO(#{what => load_permanent_rooms_at_startup, skip => true, |
441 |
|
text => <<"Skip loading permanent rooms at startup. " |
442 |
12 |
"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 |
12 |
set_persistent_rooms_timer(State), |
449 |
12 |
{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 |
105 |
timer:send_after(Timeout, stop_hibernated_persistent_rooms). |
455 |
|
|
456 |
|
handle_call(stop, _From, State) -> |
457 |
12 |
ejabberd_hooks:delete(hooks(State#state.host_type)), |
458 |
12 |
{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 |
125 |
?LOG_DEBUG(#{what => muc_create_instant, room => Room, sub_host => MucHost}), |
468 |
125 |
NewOpts = case Opts of |
469 |
1 |
default -> DefOpts; |
470 |
124 |
_ -> Opts |
471 |
|
end, |
472 |
125 |
try |
473 |
125 |
{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 |
125 |
register_room_or_stop_if_duplicate(HostType, MucHost, Room, Pid), |
479 |
125 |
{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 |
95 |
handle_stop_hibernated_persistent_rooms(HostType, Timeout), |
500 |
95 |
set_persistent_rooms_timer(State), |
501 |
95 |
{noreply, State}; |
502 |
|
handle_info(_Info, State) -> |
503 |
:-( |
{noreply, State}. |
504 |
|
|
505 |
|
handle_stop_hibernated_persistent_rooms(HostType, Timeout) -> |
506 |
95 |
?LOG_INFO(#{what => muc_stop_hibernated_persistent_rooms, host_type => HostType, |
507 |
95 |
text => <<"Closing hibernated persistent rooms">>}), |
508 |
95 |
try |
509 |
95 |
Supervisor = gen_mod:get_module_proc(HostType, ejabberd_mod_muc_sup), |
510 |
95 |
Now = os:timestamp(), |
511 |
95 |
[stop_if_hibernated(Pid, Now, Timeout * 1000) || |
512 |
95 |
{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 |
848 |
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 |
497 |
{dictionary, Dictionary} = erlang:process_info(Pid, dictionary), |
525 |
497 |
LastHibernated = lists:keyfind(hibernated, 1, Dictionary), |
526 |
497 |
stop_if_hibernated_for_specified_time(Pid, Now, Timeout, LastHibernated), |
527 |
497 |
ok; |
528 |
|
stop_if_hibernated(_, _, _, _) -> |
529 |
351 |
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 |
497 |
TimeDiff = timer:now_diff(Now, LastHibernated), |
535 |
497 |
case TimeDiff >= Timeout of |
536 |
|
true -> |
537 |
151 |
Pid ! stop_persistent_room_process; |
538 |
|
_ -> |
539 |
346 |
ok |
540 |
|
end. |
541 |
|
|
542 |
|
terminate(_Reason, #state{host_type = HostType, |
543 |
|
subdomain_pattern = SubdomainPattern}) -> |
544 |
12 |
mongoose_domain_api:unregister_subdomain(HostType, SubdomainPattern), |
545 |
12 |
unregister_for_global_distrib(HostType), |
546 |
12 |
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 |
12 |
ChildSpec = sup_spec(HostType), |
559 |
12 |
ejabberd_sup:start_child(ChildSpec). |
560 |
|
|
561 |
|
sup_spec(HostType) -> |
562 |
12 |
Proc = gen_mod:get_module_proc(HostType, ejabberd_mod_muc_sup), |
563 |
12 |
{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 |
12 |
Proc = gen_mod:get_module_proc(HostType, ejabberd_mod_muc_sup), |
574 |
12 |
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 |
678 |
{AccessRoute, _, _, _} = State#state.access, |
583 |
678 |
ServerHost = make_server_host(To, State), |
584 |
678 |
HostType = State#state.host_type, |
585 |
678 |
case acl:match_rule(HostType, ServerHost, AccessRoute, From) of |
586 |
|
allow -> |
587 |
678 |
{Room, MucHost, _} = jid:to_lower(To), |
588 |
678 |
route_to_room(MucHost, Room, {From, To, Acc, El}, State), |
589 |
675 |
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 |
614 |
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 |
565 |
Pid = R#muc_online_room.pid, |
614 |
565 |
route_to_online_room(Pid, Routed) |
615 |
|
end. |
616 |
|
|
617 |
|
route_to_online_room(Pid, {From, To, Acc, Packet}) -> |
618 |
606 |
?LOG_DEBUG(#{what => muc_route_to_online_room, room_pid => Pid, acc => Acc}), |
619 |
606 |
{_, _, Nick} = jid:to_lower(To), |
620 |
606 |
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 |
166 |
case register_room(HostType, MucHost, Room, Pid) of |
869 |
|
{_, ok} -> |
870 |
159 |
{ok, Pid}; |
871 |
|
{_, {exists, OldPid}} -> |
872 |
7 |
mod_muc_room:stop(Pid), |
873 |
7 |
{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 |
169 |
F = fun() -> |
880 |
169 |
case mnesia:read(muc_online_room, {Room, MucHost}, write) of |
881 |
|
[] -> |
882 |
162 |
mnesia:write(#muc_online_room{name_host = {Room, MucHost}, |
883 |
|
host_type = HostType, |
884 |
|
pid = Pid}); |
885 |
|
[R] -> |
886 |
7 |
{exists, R#muc_online_room.pid} |
887 |
|
end |
888 |
|
end, |
889 |
169 |
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 |
64 |
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 |
156 |
case catch gen_fsm_compat:sync_send_all_state_event( |
928 |
|
Pid, {get_disco_item, From, Lang}, 100) of |
929 |
|
{item, Desc} -> |
930 |
154 |
flush(), |
931 |
154 |
{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 |
313 |
{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 |
154 |
receive |
1014 |
|
_ -> |
1015 |
:-( |
flush() |
1016 |
|
after 0 -> |
1017 |
154 |
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 |
12 |
F = fun() -> |
1206 |
12 |
Es = mnesia:select( |
1207 |
|
muc_online_room, |
1208 |
|
[{#muc_online_room{pid = '$1', |
1209 |
|
host_type = HostType, |
1210 |
|
_ = '_'}, |
1211 |
|
[{'==', {node, '$1'}, Node}], |
1212 |
|
['$_']}]), |
1213 |
12 |
lists:foreach(fun(E) -> |
1214 |
19 |
mnesia:delete_object(E) |
1215 |
|
end, Es) |
1216 |
|
end, |
1217 |
12 |
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 |
10 |
case erlang:process_info(Pid, current_function) of |
1309 |
|
{current_function, {erlang, hibernate, _}} -> |
1310 |
9 |
Count + 1; |
1311 |
|
_ -> |
1312 |
1 |
Count |
1313 |
|
end. |
1314 |
|
|
1315 |
|
-define(EX_EVAL_SINGLE_VALUE, {[{l, [{t, [value, {v, 'Value'}]}]}], [value]}). |
1316 |
|
ensure_metrics(_Host) -> |
1317 |
12 |
mongoose_metrics:ensure_metric(global, [mod_muc, deep_hibernations], spiral), |
1318 |
12 |
mongoose_metrics:ensure_metric(global, [mod_muc, process_recreations], spiral), |
1319 |
12 |
mongoose_metrics:ensure_metric(global, [mod_muc, hibernations], spiral), |
1320 |
12 |
mongoose_metrics:ensure_metric(global, [mod_muc, hibernated_rooms], |
1321 |
|
{function, mod_muc, hibernated_rooms_number, [], |
1322 |
|
eval, ?EX_EVAL_SINGLE_VALUE}), |
1323 |
12 |
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 |
24 |
[{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 |
764 |
case SubdomainPattern of |
1358 |
|
{prefix, _} -> |
1359 |
764 |
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 |
12 |
SubHost = gen_mod:get_module_opt_subhost(HostType, ?MODULE, default_host()), |
1367 |
12 |
mongoose_hooks:register_subhost(SubHost, false). |
1368 |
|
|
1369 |
|
unregister_for_global_distrib(HostType) -> |
1370 |
12 |
SubHost = gen_mod:get_module_opt_subhost(HostType, ?MODULE, default_host()), |
1371 |
12 |
mongoose_hooks:unregister_subhost(SubHost). |