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