Move occupant_id storage and processing to mod_muc_room
This commit is contained in:
@@ -81,7 +81,8 @@
|
||||
role :: role(),
|
||||
%%is_subscriber = false :: boolean(),
|
||||
%%subscriptions = [] :: [binary()],
|
||||
last_presence :: presence() | undefined
|
||||
last_presence :: presence() | undefined,
|
||||
occupant_id :: binary()
|
||||
}).
|
||||
|
||||
-record(subscriber, {jid :: jid(),
|
||||
@@ -132,7 +133,8 @@
|
||||
activity = treap:empty() :: treap:treap(),
|
||||
room_shaper = none :: ejabberd_shaper:shaper(),
|
||||
room_queue :: p1_queue:queue({message | presence, jid()}) | undefined,
|
||||
hibernate_timer = none :: reference() | none | hibernating
|
||||
hibernate_timer = none :: reference() | none | hibernating,
|
||||
salt = <<>> :: binary()
|
||||
}).
|
||||
|
||||
-type users() :: #{ljid() => #user{}}.
|
||||
|
||||
@@ -73,7 +73,7 @@ transform(Host, Y, Acc) ->
|
||||
|
||||
transform(Host, modules, ModOpts, Acc) ->
|
||||
{ModOpts1, Acc2} =
|
||||
lists:mapfoldr(
|
||||
filtermapfoldr(
|
||||
fun({Mod, Opts}, Acc1) ->
|
||||
Opts1 = transform_module_options(Opts),
|
||||
transform_module(Host, Mod, Opts1, Acc1)
|
||||
@@ -446,7 +446,7 @@ transform_module(_Host, mod_blocking, Opts, Acc) ->
|
||||
(_) ->
|
||||
true
|
||||
end, Opts),
|
||||
{{mod_blocking, Opts1}, Acc};
|
||||
{{true, {mod_blocking, Opts1}}, Acc};
|
||||
transform_module(_Host, mod_carboncopy, Opts, Acc) ->
|
||||
Opts1 = lists:filter(
|
||||
fun({Opt, _}) when Opt == ram_db_type;
|
||||
@@ -459,7 +459,7 @@ transform_module(_Host, mod_carboncopy, Opts, Acc) ->
|
||||
(_) ->
|
||||
true
|
||||
end, Opts),
|
||||
{{mod_carboncopy, Opts1}, Acc};
|
||||
{{true, {mod_carboncopy, Opts1}}, Acc};
|
||||
transform_module(_Host, mod_http_api, Opts, Acc) ->
|
||||
Opts1 = lists:filter(
|
||||
fun({admin_ip_access, _}) ->
|
||||
@@ -468,7 +468,7 @@ transform_module(_Host, mod_http_api, Opts, Acc) ->
|
||||
(_) ->
|
||||
true
|
||||
end, Opts),
|
||||
{{mod_http_api, Opts1}, Acc};
|
||||
{{true, {mod_http_api, Opts1}}, Acc};
|
||||
transform_module(_Host, mod_http_upload, Opts, Acc) ->
|
||||
Opts1 = lists:filter(
|
||||
fun({service_url, _}) ->
|
||||
@@ -477,7 +477,7 @@ transform_module(_Host, mod_http_upload, Opts, Acc) ->
|
||||
(_) ->
|
||||
true
|
||||
end, Opts),
|
||||
{{mod_http_upload, Opts1}, Acc};
|
||||
{{true, {mod_http_upload, Opts1}}, Acc};
|
||||
transform_module(_Host, mod_pubsub, Opts, Acc) ->
|
||||
Opts1 = lists:map(
|
||||
fun({plugins, Plugins}) ->
|
||||
@@ -505,9 +505,11 @@ transform_module(_Host, mod_pubsub, Opts, Acc) ->
|
||||
(Opt) ->
|
||||
Opt
|
||||
end, Opts),
|
||||
{{mod_pubsub, Opts1}, Acc};
|
||||
{{true, {mod_pubsub, Opts1}}, Acc};
|
||||
transform_module(_Host, mod_muc_occupantid, _Opts, Acc) ->
|
||||
{false, Acc};
|
||||
transform_module(_Host, Mod, Opts, Acc) ->
|
||||
{{Mod, Opts}, Acc}.
|
||||
{{true, {Mod, Opts}}, Acc}.
|
||||
|
||||
strip_odbc_suffix(M) ->
|
||||
[_|T] = lists:reverse(string:tokens(atom_to_list(M), "_")),
|
||||
|
||||
+3
-6
@@ -27,6 +27,7 @@
|
||||
-protocol({xep, 45, '1.35.3', '0.5.0', "complete", ""}).
|
||||
-protocol({xep, 249, '1.2', '0.5.0', "complete", ""}).
|
||||
-protocol({xep, 486, '0.1.0', '24.07', "complete", ""}).
|
||||
-protocol({xep, 421, '1.0.1', '23.10', "complete", ""}).
|
||||
-ifndef(GEN_SERVER).
|
||||
-define(GEN_SERVER, gen_server).
|
||||
-endif.
|
||||
@@ -729,10 +730,6 @@ process_disco_info(#iq{type = get, from = From, to = To, lang = Lang,
|
||||
true -> [?NS_MAM_TMP, ?NS_MAM_0, ?NS_MAM_1, ?NS_MAM_2];
|
||||
false -> []
|
||||
end,
|
||||
OccupantIdFeatures = case gen_mod:is_loaded(ServerHost, mod_muc_occupantid) of
|
||||
true -> [?NS_OCCUPANT_ID];
|
||||
false -> []
|
||||
end,
|
||||
RSMFeatures = case RMod:rsm_supported() of
|
||||
true -> [?NS_RSM];
|
||||
false -> []
|
||||
@@ -743,8 +740,8 @@ process_disco_info(#iq{type = get, from = From, to = To, lang = Lang,
|
||||
end,
|
||||
Features = [?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
|
||||
?NS_MUC, ?NS_VCARD, ?NS_MUCSUB, ?NS_MUC_UNIQUE,
|
||||
?NS_MUC_STABLE_ID
|
||||
| RegisterFeatures ++ RSMFeatures ++ MAMFeatures ++ OccupantIdFeatures],
|
||||
?NS_MUC_STABLE_ID, ?NS_OCCUPANT_ID
|
||||
| RegisterFeatures ++ RSMFeatures ++ MAMFeatures],
|
||||
Name = mod_muc_opt:name(ServerHost),
|
||||
Identity = #identity{category = <<"conference">>,
|
||||
type = <<"text">>,
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
%%%----------------------------------------------------------------------
|
||||
%%% File : mod_muc_occupantid.erl
|
||||
%%% Author : Badlop <badlop@process-one.net>
|
||||
%%% Purpose : Add Occupant Ids to stanzas in anonymous MUC rooms (XEP-0421)
|
||||
%%% Created :
|
||||
%%%
|
||||
%%%
|
||||
%%% ejabberd, Copyright (C) 2002-2026 ProcessOne
|
||||
%%%
|
||||
%%% This program is free software; you can redistribute it and/or
|
||||
%%% modify it under the terms of the GNU General Public License as
|
||||
%%% published by the Free Software Foundation; either version 2 of the
|
||||
%%% License, or (at your option) any later version.
|
||||
%%%
|
||||
%%% This program is distributed in the hope that it will be useful,
|
||||
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
%%% General Public License for more details.
|
||||
%%%
|
||||
%%% You should have received a copy of the GNU General Public License along
|
||||
%%% with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
%%%
|
||||
%%%----------------------------------------------------------------------
|
||||
|
||||
-module(mod_muc_occupantid).
|
||||
|
||||
-author('badlop@process-one.net').
|
||||
|
||||
-protocol({xep, 421, '1.0.1', '23.10', "complete", ""}).
|
||||
|
||||
-behaviour(gen_mod).
|
||||
|
||||
-include_lib("xmpp/include/xmpp.hrl").
|
||||
-include("logger.hrl").
|
||||
-include("translate.hrl").
|
||||
-include("mod_muc_room.hrl").
|
||||
|
||||
-export([start/2, stop/1,
|
||||
mod_options/1, mod_doc/0, depends/2]).
|
||||
-export([filter_packet/3, clean_obsolete_salts/0]).
|
||||
|
||||
%%%
|
||||
%%% gen_mod
|
||||
%%%
|
||||
|
||||
start(_Host, _Opts) ->
|
||||
prepare_table(),
|
||||
{ok, [{hook, muc_filter_presence, filter_packet, 10},
|
||||
{hook, muc_filter_message, filter_packet, 10},
|
||||
{hook, config_reloaded, clean_obsolete_salts, 90, global}]}.
|
||||
|
||||
stop(_Host) ->
|
||||
ok.
|
||||
|
||||
%%%
|
||||
%%% Hooks
|
||||
%%%
|
||||
|
||||
filter_packet(Packet, State, _Nick) ->
|
||||
add_occupantid_packet(Packet, State#state.jid).
|
||||
|
||||
%%%
|
||||
%%% XEP-0421 Occupant-id
|
||||
%%%
|
||||
|
||||
add_occupantid_packet(Packet, RoomJid) ->
|
||||
From = xmpp:get_from(Packet),
|
||||
OccupantId = calculate_occupantid(From, RoomJid),
|
||||
OccupantElement = #occupant_id{id = OccupantId},
|
||||
xmpp:append_subtags(xmpp:remove_subtag(Packet, OccupantElement), [OccupantElement]).
|
||||
|
||||
calculate_occupantid(From, RoomJid) ->
|
||||
Term = {get_salt(RoomJid#jid.lserver), RoomJid, jid:remove_resource(From)},
|
||||
misc:term_to_base64(crypto:hash(sha256, io_lib:format("~p", [Term]))).
|
||||
|
||||
%%%
|
||||
%%% Table storing rooms' salt
|
||||
%%%
|
||||
|
||||
-record(muc_occupant_id, {service_jid, salt}).
|
||||
|
||||
prepare_table() ->
|
||||
try
|
||||
mnesia:table_info(muc_occupant_id, attributes),
|
||||
%% Wait some time to ensure mod_muc is started in all hosts
|
||||
timer:apply_after(60000, ?MODULE, clean_obsolete_salts, [])
|
||||
catch
|
||||
exit:{aborted, {no_exists, _, _}} ->
|
||||
create_table()
|
||||
end.
|
||||
|
||||
|
||||
create_table() ->
|
||||
ejabberd_mnesia:create(?MODULE, muc_occupant_id,
|
||||
[{disc_copies, [node()]},
|
||||
{attributes, record_info(fields, muc_occupant_id)},
|
||||
{type, set}]).
|
||||
|
||||
|
||||
get_salt(ServiceJid) ->
|
||||
case mnesia:dirty_read(muc_occupant_id, ServiceJid) of
|
||||
[] ->
|
||||
Salt = p1_rand:get_string(),
|
||||
ok = write_salt(ServiceJid, Salt),
|
||||
Salt;
|
||||
[#muc_occupant_id{salt = Salt}] ->
|
||||
Salt
|
||||
end.
|
||||
|
||||
write_salt(ServiceJid, Salt) ->
|
||||
mnesia:dirty_write(#muc_occupant_id{service_jid = ServiceJid, salt = Salt}).
|
||||
|
||||
%% @format-begin
|
||||
clean_obsolete_salts() ->
|
||||
Hosts = ejabberd_option:hosts(),
|
||||
MucHosts =
|
||||
lists:foldl(fun(Host, Acc) -> gen_mod:get_module_opt_hosts(Host, mod_muc) ++ Acc end,
|
||||
[],
|
||||
Hosts),
|
||||
|
||||
F = fun() ->
|
||||
mnesia:write_lock_table(muc_occupant_id),
|
||||
Ft = fun(#muc_occupant_id{service_jid = Service, salt = _Salt} = Rec, Acc) ->
|
||||
case lists:member(Service, MucHosts) of
|
||||
true ->
|
||||
Acc;
|
||||
false ->
|
||||
mnesia:delete_object(Rec),
|
||||
[Service | Acc]
|
||||
end
|
||||
end,
|
||||
mnesia:foldl(Ft, [], muc_occupant_id)
|
||||
end,
|
||||
case mnesia:transaction(F) of
|
||||
{atomic, Res} ->
|
||||
?DEBUG("Deleted Salts for occupant-id of MUC services: ~p", [Res]),
|
||||
ok;
|
||||
_ ->
|
||||
ok
|
||||
end.
|
||||
%% @format-end
|
||||
|
||||
%%%
|
||||
%%% Doc
|
||||
%%%
|
||||
|
||||
mod_options(_Host) ->
|
||||
[].
|
||||
|
||||
mod_doc() ->
|
||||
#{desc =>
|
||||
[?T("This module implements "
|
||||
"https://xmpp.org/extensions/xep-0421.html"
|
||||
"[XEP-0421: Anonymous unique occupant identifiers for MUCs]."), "",
|
||||
?T("When the module is enabled, the feature is enabled "
|
||||
"in all semi-anonymous rooms.")],
|
||||
note => "added in 23.10"
|
||||
}.
|
||||
|
||||
depends(_, _) ->
|
||||
[{mod_muc, hard}].
|
||||
+57
-34
@@ -310,6 +310,7 @@ init([Host, ServerHost, Access, Room, HistorySize,
|
||||
history = lqueue_new(HistorySize, QueueType),
|
||||
jid = jid:make(Room, Host),
|
||||
just_created = true,
|
||||
salt = p1_rand:get_string(),
|
||||
room_queue = RoomQueue,
|
||||
room_shaper = Shaper}),
|
||||
State1 = set_affiliation(Creator, owner, State),
|
||||
@@ -334,6 +335,7 @@ init([Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts, QueueType])
|
||||
room = Room,
|
||||
history = lqueue_new(HistorySize, QueueType),
|
||||
jid = Jid,
|
||||
salt = p1_rand:get_string(),
|
||||
room_queue = RoomQueue,
|
||||
room_shaper = Shaper}),
|
||||
add_to_log(room_existence, started, State),
|
||||
@@ -638,11 +640,9 @@ normal_state({route, ToNick,
|
||||
FromNick),
|
||||
X = #muc_user{},
|
||||
Packet2 = xmpp:set_subtag(Packet, X),
|
||||
case ejabberd_hooks:run_fold(muc_filter_message,
|
||||
StateData#state.server_host,
|
||||
xmpp:put_meta(Packet2, mam_ignore, true),
|
||||
[StateData, FromNick]) of
|
||||
drop ->
|
||||
case filter_message_hook(StateData, FromNick,
|
||||
xmpp:put_meta(Packet2, mam_ignore, true)) of
|
||||
drop ->
|
||||
ok;
|
||||
Packet3 ->
|
||||
PrivMsg = xmpp:set_from(xmpp:del_meta(Packet3, mam_ignore), FromNickJID),
|
||||
@@ -1092,12 +1092,8 @@ process_groupchat_message(#message{from = From, lang = Lang} = Packet, StateData
|
||||
end,
|
||||
case IsAllowed of
|
||||
true ->
|
||||
case
|
||||
ejabberd_hooks:run_fold(muc_filter_message,
|
||||
StateData#state.server_host,
|
||||
Packet,
|
||||
[StateData, FromNick])
|
||||
of
|
||||
case filter_message_hook(StateData, FromNick,
|
||||
Packet) of
|
||||
drop ->
|
||||
{next_state, normal_state, StateData};
|
||||
NewPacket1 ->
|
||||
@@ -1401,10 +1397,7 @@ process_presence(Nick, #presence{from = From, type = Type0} = Packet0, StateData
|
||||
IsOnline = is_user_online(From, StateData),
|
||||
if Type0 == available;
|
||||
IsOnline and ((Type0 == unavailable) or (Type0 == error)) ->
|
||||
case ejabberd_hooks:run_fold(muc_filter_presence,
|
||||
StateData#state.server_host,
|
||||
Packet0,
|
||||
[StateData, Nick]) of
|
||||
case filter_presence_hook(StateData, Nick, Packet0) of
|
||||
drop ->
|
||||
{next_state, normal_state, StateData};
|
||||
#presence{} = Packet ->
|
||||
@@ -1561,7 +1554,8 @@ get_users_and_subscribers_aux(Subscribers, StateData) ->
|
||||
#user{jid = jid:make(LBareJID),
|
||||
nick = Nick,
|
||||
role = none,
|
||||
last_presence = undefined},
|
||||
last_presence = undefined,
|
||||
occupant_id = <<>>},
|
||||
Acc);
|
||||
true ->
|
||||
Acc
|
||||
@@ -2130,10 +2124,44 @@ set_subscriber(JID, Nick, Nodes,
|
||||
end,
|
||||
NewStateData.
|
||||
|
||||
-spec calculate_occupant_id(jid(), state()) -> binary().
|
||||
calculate_occupant_id(Jid, #state{salt = Salt, jid = RoomJid}) ->
|
||||
JidS = jid:encode(jid:remove_resource(Jid)),
|
||||
RoomJidS = jid:encode(RoomJid),
|
||||
Term = <<Salt/binary, ":", RoomJidS/binary, ":", JidS/binary>>,
|
||||
misc:term_to_base64(crypto:hash(sha256, Term)).
|
||||
|
||||
-spec filter_message_hook(state(), binary(), #message{}) -> drop | #message{}.
|
||||
filter_message_hook(#state{users = Users} = StateData, Nick, #message{from = From} = Message) ->
|
||||
OccupantId = case maps:find(jid:tolower(From), Users) of
|
||||
{ok, #user{occupant_id = Id}} -> Id;
|
||||
_ -> calculate_occupant_id(From, StateData)
|
||||
end,
|
||||
Message2 = xmpp:append_subtags(xmpp:remove_subtag(Message, #occupant_id{}),
|
||||
[#occupant_id{id = OccupantId}]),
|
||||
ejabberd_hooks:run_fold(muc_filter_message,
|
||||
StateData#state.server_host,
|
||||
Message2,
|
||||
[StateData, Nick]).
|
||||
|
||||
-spec filter_presence_hook(state(), binary(), #presence{}) -> drop | #presence{}.
|
||||
filter_presence_hook(#state{users = Users} = StateData, Nick, #presence{from = From} = Pres) ->
|
||||
OccupantId = case maps:find(jid:tolower(From), Users) of
|
||||
{ok, #user{occupant_id = Id}} -> Id;
|
||||
_ -> calculate_occupant_id(From, StateData)
|
||||
end,
|
||||
Pres2 = xmpp:append_subtags(xmpp:remove_subtag(Pres, #occupant_id{}),
|
||||
[#occupant_id{id = OccupantId}]),
|
||||
ejabberd_hooks:run_fold(muc_filter_message,
|
||||
StateData#state.server_host,
|
||||
Pres2,
|
||||
[StateData, Nick]).
|
||||
|
||||
|
||||
-spec add_online_user(jid(), binary(), role(), state()) -> state().
|
||||
add_online_user(JID, Nick, Role, StateData) ->
|
||||
tab_add_online_user(JID, StateData),
|
||||
User = #user{jid = JID, nick = Nick, role = Role},
|
||||
User = #user{jid = JID, nick = Nick, role = Role, occupant_id = calculate_occupant_id(JID, StateData)},
|
||||
reset_hibernate_timer(update_online_user(JID, User, StateData)).
|
||||
|
||||
-spec remove_online_user(jid(), state()) -> state().
|
||||
@@ -3043,10 +3071,8 @@ send_subject(JID, #state{subject_author = {Nick, AuthorJID}} = StateData) ->
|
||||
end,
|
||||
Packet = #message{from = AuthorJID,
|
||||
to = JID, type = groupchat, subject = Subject},
|
||||
case ejabberd_hooks:run_fold(muc_filter_message,
|
||||
StateData#state.server_host,
|
||||
xmpp:put_meta(Packet, mam_ignore, true),
|
||||
[StateData, Nick]) of
|
||||
case filter_message_hook(StateData, Nick,
|
||||
xmpp:put_meta(Packet, mam_ignore, true)) of
|
||||
drop ->
|
||||
ok;
|
||||
NewPacket1 ->
|
||||
@@ -4271,6 +4297,8 @@ set_opts2([{Opt, Val} | Opts], StateData) ->
|
||||
hats_users ->
|
||||
StateData#state{hats_users = maps:from_list(Val)};
|
||||
hibernation_time -> StateData;
|
||||
salt ->
|
||||
StateData#state{salt = Val};
|
||||
Other ->
|
||||
?INFO_MSG("Unknown MUC room option, will be discarded: ~p", [Other]),
|
||||
StateData
|
||||
@@ -4353,6 +4381,7 @@ make_opts(StateData, Hibernation) ->
|
||||
{hats_defs, maps:to_list(StateData#state.hats_defs)},
|
||||
{hats_users, maps:to_list(StateData#state.hats_users)},
|
||||
{hibernation_time, if Hibernation -> erlang:system_time(microsecond); true -> undefined end},
|
||||
{salt, StateData#state.salt},
|
||||
{subscribers, Subscribers}].
|
||||
|
||||
expand_opts(CompactOpts) ->
|
||||
@@ -4377,14 +4406,16 @@ expand_opts(CompactOpts) ->
|
||||
Subject = proplists:get_value(subject, CompactOpts, <<"">>),
|
||||
Subscribers = proplists:get_value(subscribers, CompactOpts, []),
|
||||
HibernationTime = proplists:get_value(hibernation_time, CompactOpts, 0),
|
||||
Salt = proplists:get_value(hibernation_time, CompactOpts, <<>>),
|
||||
[{subject, Subject},
|
||||
{subject_author, SubjectAuthor},
|
||||
{subscribers, Subscribers},
|
||||
{hibernation_time, HibernationTime}
|
||||
{hibernation_time, HibernationTime},
|
||||
{salt, Salt}
|
||||
| lists:reverse(Opts1)].
|
||||
|
||||
config_fields() ->
|
||||
[subject, subject_author, subscribers, hibernate_time | record_info(fields, config)].
|
||||
[subject, subject_author, subscribers, hibernate_time, salt | record_info(fields, config)].
|
||||
|
||||
-spec destroy_room(muc_destroy(), state()) -> {result, undefined, stop}.
|
||||
destroy_room(DEl, StateData) ->
|
||||
@@ -4447,7 +4478,7 @@ make_disco_info(From, StateData) ->
|
||||
?NS_DISCO_INFO, ?NS_DISCO_ITEMS,
|
||||
?NS_COMMANDS,
|
||||
?NS_MESSAGE_MODERATE_0, ?NS_MESSAGE_MODERATE_1,
|
||||
?NS_MESSAGE_RETRACT,
|
||||
?NS_MESSAGE_RETRACT, ?NS_OCCUPANT_ID,
|
||||
?CONFIG_OPT_TO_FEATURE((Config#config.public),
|
||||
<<"muc_public">>, <<"muc_hidden">>),
|
||||
?CONFIG_OPT_TO_FEATURE((Config#config.persistent),
|
||||
@@ -4472,12 +4503,6 @@ make_disco_info(From, StateData) ->
|
||||
true -> [?NS_HATS];
|
||||
false -> []
|
||||
end
|
||||
++ case gen_mod:is_loaded(StateData#state.server_host, mod_muc_occupantid) of
|
||||
true ->
|
||||
[?NS_OCCUPANT_ID];
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
++ case {gen_mod:is_loaded(StateData#state.server_host, mod_mam),
|
||||
Config#config.mam} of
|
||||
{true, true} ->
|
||||
@@ -5423,10 +5448,8 @@ process_iq_moderate(From, #iq{type = set, lang = Lang}, Id, Reason,
|
||||
from = From,
|
||||
sub_els = SubEl},
|
||||
{FromNick, _Role} = get_participant_data(From, StateData),
|
||||
Packet = ejabberd_hooks:run_fold(muc_filter_message,
|
||||
StateData#state.server_host,
|
||||
xmpp:put_meta(Packet0, mam_ignore, true),
|
||||
[StateData, FromNick]),
|
||||
Packet = filter_message_hook(StateData, FromNick,
|
||||
xmpp:put_meta(Packet0, mam_ignore, true)),
|
||||
send_wrapped_multiple(JID,
|
||||
get_users_and_subscribers_with_node(?NS_MUCSUB_NODES_MESSAGES, StateData),
|
||||
Packet, ?NS_MUCSUB_NODES_MESSAGES, StateData),
|
||||
|
||||
Reference in New Issue
Block a user