aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHolger Weiss <holger@zedat.fu-berlin.de>2019-02-20 17:01:34 +0100
committerHolger Weiss <holger@zedat.fu-berlin.de>2019-02-20 17:01:34 +0100
commit9c66cc58857f8854654a0edb3bcbd161fbf95bcb (patch)
tree218046fd5144b47327af8bd890887fc5cf882aca /src
parentDon't crash on malformed 'modules' section (diff)
parentdisallow room creation if archive not empty and (diff)
Merge remote-tracking branch 'processone/pr/2763'
* processone/pr/2763: disallow room creation if archive not empty and clear_archive_on_room_destroy is false check if mod_mam is loaded before calling mod_mam:is_empty_for_room added cmds to list and destroy empty rooms by ejabberdctl allow check if archive is empty for or user or room option to prevent archive removal on room destroy
Diffstat (limited to 'src')
-rw-r--r--src/mod_mam.erl49
-rw-r--r--src/mod_mam_mnesia.erl10
-rw-r--r--src/mod_mam_sql.erl20
-rw-r--r--src/mod_muc.erl14
-rw-r--r--src/mod_muc_admin.erl73
5 files changed, 137 insertions, 29 deletions
diff --git a/src/mod_mam.erl b/src/mod_mam.erl
index 294d4f401..7dc80e462 100644
--- a/src/mod_mam.erl
+++ b/src/mod_mam.erl
@@ -40,7 +40,8 @@
muc_process_iq/2, muc_filter_message/3, message_is_archived/3,
delete_old_messages/2, get_commands_spec/0, msg_to_el/4,
get_room_config/4, set_room_option/3, offline_message/1, export/1,
- mod_options/1, remove_mam_for_user_with_peer/3, remove_mam_for_user/2]).
+ mod_options/1, remove_mam_for_user_with_peer/3, remove_mam_for_user/2,
+ is_empty_for_user/2, is_empty_for_room/3, check_create_room/4]).
-include("xmpp.hrl").
-include("logger.hrl").
@@ -72,6 +73,8 @@
-callback use_cache(binary()) -> boolean().
-callback cache_nodes(binary()) -> [node()].
-callback remove_from_archive(binary(), binary(), jid() | none) -> ok | {error, any()}.
+-callback is_empty_for_user(binary(), binary()) -> boolean().
+-callback is_empty_for_room(binary(), binary(), binary()) -> boolean().
-optional_callbacks([use_cache/1, cache_nodes/1]).
@@ -113,8 +116,6 @@ start(Host, Opts) ->
disco_sm_features, 50),
ejabberd_hooks:add(remove_user, Host, ?MODULE,
remove_user, 50),
- ejabberd_hooks:add(remove_room, Host, ?MODULE,
- remove_room, 50),
ejabberd_hooks:add(get_room_config, Host, ?MODULE,
get_room_config, 50),
ejabberd_hooks:add(set_room_option, Host, ?MODULE,
@@ -126,11 +127,22 @@ start(Host, Opts) ->
false ->
ok
end,
+ case gen_mod:get_opt(clear_archive_on_room_destroy, Opts) of
+ true ->
+ ejabberd_hooks:add(remove_room, Host, ?MODULE,
+ remove_room, 50);
+ false ->
+ ejabberd_hooks:add(check_create_room, Host, ?MODULE,
+ check_create_room, 50)
+ end,
ejabberd_commands:register_commands(get_commands_spec());
Err ->
Err
end.
+
+
+
use_cache(Mod, Host) ->
case erlang:function_exported(Mod, use_cache, 2) of
true -> Mod:use_cache(Host);
@@ -180,8 +192,6 @@ stop(Host) ->
disco_sm_features, 50),
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
remove_user, 50),
- ejabberd_hooks:delete(remove_room, Host, ?MODULE,
- remove_room, 50),
ejabberd_hooks:delete(get_room_config, Host, ?MODULE,
get_room_config, 50),
ejabberd_hooks:delete(set_room_option, Host, ?MODULE,
@@ -193,6 +203,14 @@ stop(Host) ->
false ->
ok
end,
+ case gen_mod:get_module_opt(Host, ?MODULE, clear_archive_on_room_destroy) of
+ true ->
+ ejabberd_hooks:delete(remove_room, Host, ?MODULE,
+ remove_room, 50);
+ false ->
+ ejabberd_hooks:delete(check_create_room, Host, ?MODULE,
+ check_create_room, 50)
+ end,
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
false ->
ejabberd_commands:unregister_commands(get_commands_spec());
@@ -559,6 +577,24 @@ export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
+-spec is_empty_for_user(binary(), binary()) -> boolean().
+is_empty_for_user(User, Server) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:is_empty_for_user(LUser, LServer).
+
+-spec is_empty_for_room(binary(), binary(), binary()) -> boolean().
+is_empty_for_room(LServer, Name, Host) ->
+ LName = jid:nodeprep(Name),
+ LHost = jid:nameprep(Host),
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:is_empty_for_room(LServer, LName, LHost).
+
+-spec check_create_room(boolean(), binary(), binary(), binary()) -> boolean().
+check_create_room(Acc, ServerHost, RoomID, Host) ->
+ Acc and is_empty_for_room(ServerHost, RoomID, Host).
+
%%%===================================================================
%%% Internal functions
%%%===================================================================
@@ -1211,6 +1247,8 @@ mod_opt_type(default) ->
(roster) -> roster
end;
mod_opt_type(request_activates_archiving) ->
+ fun (B) when is_boolean(B) -> B end;
+mod_opt_type(clear_archive_on_room_destroy) ->
fun (B) when is_boolean(B) -> B end.
mod_options(Host) ->
@@ -1218,6 +1256,7 @@ mod_options(Host) ->
{default, never},
{request_activates_archiving, false},
{compress_xml, false},
+ {clear_archive_on_room_destroy, true},
{db_type, ejabberd_config:default_db(Host, ?MODULE)},
{use_cache, ejabberd_config:use_cache(Host)},
{cache_size, ejabberd_config:cache_size(Host)},
diff --git a/src/mod_mam_mnesia.erl b/src/mod_mam_mnesia.erl
index 08d551585..f94dd2e49 100644
--- a/src/mod_mam_mnesia.erl
+++ b/src/mod_mam_mnesia.erl
@@ -28,7 +28,8 @@
%% API
-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
- extended_fields/0, store/8, write_prefs/4, get_prefs/2, select/6, remove_from_archive/3]).
+ extended_fields/0, store/8, write_prefs/4, get_prefs/2, select/6, remove_from_archive/3,
+ is_empty_for_user/2, is_empty_for_room/3]).
-include_lib("stdlib/include/ms_transform.hrl").
-include("xmpp.hrl").
@@ -198,6 +199,13 @@ select(_LServer, JidRequestor,
erlang:garbage_collect(),
Result.
+is_empty_for_user(LUser, LServer) ->
+ not lists:member({LUser, LServer},
+ mnesia:dirty_all_keys(archive_msg)).
+
+is_empty_for_room(_LServer, LName, LHost) ->
+ is_empty_for_user(LName, LHost).
+
%%%===================================================================
%%% Internal functions
%%%===================================================================
diff --git a/src/mod_mam_sql.erl b/src/mod_mam_sql.erl
index bcfa06708..6242de3c6 100644
--- a/src/mod_mam_sql.erl
+++ b/src/mod_mam_sql.erl
@@ -30,7 +30,8 @@
%% API
-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3,
- extended_fields/0, store/8, write_prefs/4, get_prefs/2, select/6, export/1, remove_from_archive/3]).
+ extended_fields/0, store/8, write_prefs/4, get_prefs/2, select/6, export/1, remove_from_archive/3,
+ is_empty_for_user/2, is_empty_for_room/3]).
-include_lib("stdlib/include/ms_transform.hrl").
-include("xmpp.hrl").
@@ -264,6 +265,23 @@ export(_Server) ->
[]
end}].
+is_empty_for_user(LUser, LServer) ->
+ case ejabberd_sql:sql_query(
+ LServer,
+ ?SQL("select @(count(*))d from archive"
+ " where username=%(LUser)s and %(LServer)H")) of
+ {selected, [{Res}]} ->
+ case Res of
+ 0 -> true;
+ _ -> false
+ end;
+ _ -> false
+ end.
+
+is_empty_for_room(LServer, LName, LHost) ->
+ LUser = jid:encode({LName, LHost, <<>>}),
+ is_empty_for_user(LUser, LServer).
+
%%%===================================================================
%%% Internal functions
%%%===================================================================
diff --git a/src/mod_muc.erl b/src/mod_muc.erl
index 29474e511..ae3c6f9f7 100644
--- a/src/mod_muc.erl
+++ b/src/mod_muc.erl
@@ -65,7 +65,8 @@
iq_set_register_info/5,
count_online_rooms_by_user/3,
get_online_rooms_by_user/3,
- can_use_nick/4]).
+ can_use_nick/4,
+ check_create_room/4]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3,
@@ -112,10 +113,14 @@
%% API
%%====================================================================
start(Host, Opts) ->
+ ejabberd_hooks:add(check_create_room, Host, ?MODULE,
+ check_create_room, 50),
gen_mod:start_child(?MODULE, Host, Opts).
stop(Host) ->
Rooms = shutdown_rooms(Host),
+ ejabberd_hooks:delete(check_create_room, Host, ?MODULE,
+ check_create_room, 50),
gen_mod:stop_child(?MODULE, Host),
{wait, Rooms}.
@@ -442,7 +447,9 @@ do_route1(Host, ServerHost, Access, HistorySize, RoomShaper,
true ->
case check_user_can_create_room(
ServerHost, AccessCreate, From, Room) and
- check_create_roomid(ServerHost, Room) of
+ ejabberd_hooks:run_fold(check_create_room,
+ ServerHost, true,
+ [ServerHost, Room, Host]) of
true ->
{ok, Pid} = start_new_room(
Host, ServerHost, Access,
@@ -611,9 +618,10 @@ check_user_can_create_room(ServerHost, AccessCreate,
_ -> false
end.
-check_create_roomid(ServerHost, RoomID) ->
+check_create_room(Acc, ServerHost, RoomID, _Host) ->
Max = gen_mod:get_module_opt(ServerHost, ?MODULE, max_room_id),
Regexp = gen_mod:get_module_opt(ServerHost, ?MODULE, regexp_room_id),
+ Acc and
(byte_size(RoomID) =< Max) and
(re:run(RoomID, Regexp, [unicode, {capture, none}]) == match).
diff --git a/src/mod_muc_admin.erl b/src/mod_muc_admin.erl
index 0dac45e8b..494a4ef3f 100644
--- a/src/mod_muc_admin.erl
+++ b/src/mod_muc_admin.erl
@@ -34,6 +34,7 @@
create_room_with_opts/4, create_room/3, destroy_room/2,
create_rooms_file/1, destroy_rooms_file/1,
rooms_unused_list/2, rooms_unused_destroy/2,
+ rooms_empty_list/1, rooms_empty_destroy/1,
get_user_rooms/2, get_room_occupants/2,
get_room_occupants_number/2, send_direct_invitation/5,
change_room_option/4, get_room_options/2,
@@ -194,6 +195,25 @@ get_commands_spec() ->
args = [{host, binary}, {days, integer}],
result = {rooms, {list, {room, string}}}},
+ #ejabberd_commands{name = rooms_empty_list, tags = [muc],
+ desc = "List the rooms that have no messages in archive",
+ module = ?MODULE, function = rooms_empty_list,
+ args_desc = ["Server host"],
+ args_example = ["example.com"],
+ result_desc = "List of empty rooms",
+ result_example = ["room1@muc.example.com", "room2@muc.example.com"],
+ args = [{host, binary}],
+ result = {rooms, {list, {room, string}}}},
+ #ejabberd_commands{name = rooms_empty_destroy, tags = [muc],
+ desc = "Destroy the rooms that have no messages in archive",
+ module = ?MODULE, function = rooms_empty_destroy,
+ args_desc = ["Server host"],
+ args_example = ["example.com"],
+ result_desc = "List of empty rooms that have been destroyed",
+ result_example = ["room1@muc.example.com", "room2@muc.example.com"],
+ args = [{host, binary}],
+ result = {rooms, {list, {room, string}}}},
+
#ejabberd_commands{name = get_user_rooms, tags = [muc],
desc = "Get the list of rooms where this user is occupant",
module = ?MODULE, function = get_user_rooms,
@@ -718,35 +738,41 @@ create_rooms_file(Filename) ->
ok.
-%%----------------------------
-%% List/Delete Unused Rooms
-%%----------------------------
+%%---------------------------------
+%% List/Delete Unused/Empty Rooms
+%%---------------------------------
%%---------------
%% Control
rooms_unused_list(ServerHost, Days) ->
- rooms_unused_report(list, ServerHost, Days).
+ rooms_report(unused, list, ServerHost, Days).
rooms_unused_destroy(ServerHost, Days) ->
- rooms_unused_report(destroy, ServerHost, Days).
+ rooms_report(unused, destroy, ServerHost, Days).
-rooms_unused_report(Action, ServerHost, Days) ->
- {NA, NP, RP} = muc_unused(Action, ServerHost, Days),
- io:format("Unused rooms: ~p out of ~p~n", [NP, NA]),
+rooms_empty_list(ServerHost) ->
+ rooms_report(empty, list, ServerHost, 0).
+rooms_empty_destroy(ServerHost) ->
+ rooms_report(empty, destroy, ServerHost, 0).
+
+
+rooms_report(Method, Action, ServerHost, Days) ->
+ {NA, NP, RP} = muc_unused(Method, Action, ServerHost, Days),
+ io:format("rooms ~s: ~p out of ~p~n", [Method, NP, NA]),
[<<R/binary, "@", H/binary>> || {R, H, _P} <- RP].
-muc_unused(Action, ServerHost, Last_allowed) ->
+muc_unused(Method, Action, ServerHost, Last_allowed) ->
%% Get all required info about all existing rooms
Rooms_all = get_rooms(ServerHost),
%% Decide which ones pass the requirements
- Rooms_pass = decide_rooms(Rooms_all, ServerHost, Last_allowed),
+ Rooms_pass = decide_rooms(Method, Rooms_all, ServerHost, Last_allowed),
Num_rooms_all = length(Rooms_all),
Num_rooms_pass = length(Rooms_pass),
%% Perform the desired action for matching rooms
- act_on_rooms(Action, Rooms_pass, ServerHost),
+ act_on_rooms(Method, Action, Rooms_pass, ServerHost),
{Num_rooms_all, Num_rooms_pass, Rooms_pass}.
@@ -771,11 +797,11 @@ get_room_state(Room_pid) ->
%%---------------
%% Decide
-decide_rooms(Rooms, ServerHost, Last_allowed) ->
- Decide = fun(R) -> decide_room(R, ServerHost, Last_allowed) end,
+decide_rooms(Method, Rooms, ServerHost, Last_allowed) ->
+ Decide = fun(R) -> decide_room(Method, R, ServerHost, Last_allowed) end,
lists:filter(Decide, Rooms).
-decide_room({_Room_name, _Host, Room_pid}, ServerHost, Last_allowed) ->
+decide_room(unused, {_Room_name, _Host, Room_pid}, ServerHost, Last_allowed) ->
C = get_room_config(Room_pid),
Persistent = C#config.persistent,
@@ -811,6 +837,13 @@ decide_room({_Room_name, _Host, Room_pid}, ServerHost, Last_allowed) ->
true;
_ ->
false
+ end;
+decide_room(empty, {Room_name, Host, _Room_pid}, ServerHost, _Last_allowed) ->
+ case gen_mod:is_loaded(ServerHost, mod_mam) of
+ true ->
+ mod_mam:is_empty_for_room(ServerHost, Room_name, Host);
+ _ ->
+ false
end.
seconds_to_days(S) ->
@@ -819,7 +852,7 @@ seconds_to_days(S) ->
%%---------------
%% Act
-act_on_rooms(Action, Rooms, ServerHost) ->
+act_on_rooms(Method, Action, Rooms, ServerHost) ->
ServerHosts = [ {A, find_host(A)} || A <- ejabberd_config:get_myhosts() ],
Delete = fun({_N, H, _Pid} = Room) ->
SH = case ServerHost of
@@ -827,7 +860,7 @@ act_on_rooms(Action, Rooms, ServerHost) ->
O -> O
end,
- act_on_room(Action, Room, SH)
+ act_on_room(Method, Action, Room, SH)
end,
lists:foreach(Delete, Rooms).
@@ -835,13 +868,15 @@ find_serverhost(Host, ServerHosts) ->
{value, {ServerHost, Host}} = lists:keysearch(Host, 2, ServerHosts),
ServerHost.
-act_on_room(destroy, {N, H, Pid}, SH) ->
+act_on_room(Method, destroy, {N, H, Pid}, SH) ->
+ Message = iolist_to_binary(io_lib:format(
+ <<"Room destroyed by rooms_~s_destroy.">>, [Method])),
p1_fsm:send_all_state_event(
- Pid, {destroy, <<"Room destroyed by rooms_unused_destroy.">>}),
+ Pid, {destroy, Message}),
mod_muc:room_destroyed(H, N, Pid, SH),
mod_muc:forget_room(SH, H, N);
-act_on_room(list, _, _) ->
+act_on_room(_Method, list, _, _) ->
ok.